diff --git a/packages/types/src/issues/base.d.ts b/packages/types/src/issues/base.d.ts index ae210d3b1..a0c7f5f79 100644 --- a/packages/types/src/issues/base.d.ts +++ b/packages/types/src/issues/base.d.ts @@ -1,3 +1,6 @@ +import { StateGroup } from "components/states"; +import { TIssuePriorities } from "../issues"; + // issues export * from "./issue"; export * from "./issue_reaction"; @@ -7,16 +10,17 @@ export * from "./issue_relation"; export * from "./issue_sub_issues"; export * from "./activity/base"; -export type TLoader = "init-loader" | "mutation" | undefined; +export type TLoader = "init-loader" | "mutation" | "pagination" | undefined; export type TGroupedIssues = { - [group_id: string]: string[]; + [group_id: string]: { issueIds: string[]; issueCount: number }; }; export type TSubGroupedIssues = { - [sub_grouped_id: string]: { - [group_id: string]: string[]; - }; + [sub_grouped_id: string]: TGroupedIssues; +}; +export type TUnGroupedIssues = { + "All Issues": { issueIds: string[]; issueCount: number }; }; -export type TUnGroupedIssues = string[]; +export type TIssues = TGroupedIssues | TUnGroupedIssues; diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 42c95dc4e..350555358 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -4,15 +4,15 @@ import { TIssueLink } from "./issue_link"; import { TIssueReaction } from "./issue_reaction"; // new issue structure types -export type TIssue = { + +export type TBaseIssue = { id: string; sequence_id: number; name: string; - description_html: string; sort_order: number; - state_id: string; - priority: TIssuePriorities; + state_id: string | null; + priority: TIssuePriorities | null; label_ids: string[]; assignee_ids: string[]; estimate_point: number | null; @@ -21,10 +21,10 @@ export type TIssue = { attachment_count: number; link_count: number; - project_id: string; + project_id: string | null; parent_id: string | null; cycle_id: string | null; - module_ids: string[] | null; + module_ids: string[]; created_at: string; updated_at: string; @@ -37,9 +37,14 @@ export type TIssue = { updated_by: string; is_draft: boolean; +}; + +export type TIssue = TBaseIssue & { + description_html?: string; is_subscribed?: boolean; parent?: partial; + issue_reactions?: TIssueReaction[]; issue_attachment?: TIssueAttachment[]; issue_link?: TIssueLink[]; @@ -51,3 +56,24 @@ export type TIssue = { export type TIssueMap = { [issue_id: string]: TIssue; }; + +type TIssueResponseResults = + | TBaseIssue[] + | { + [key: string]: { + results: TBaseIssue[]; + total_results: number; + }; + }; + +export type TIssuesResponse = { + grouped_by: string; + next_cursor: string; + prev_cursor: string; + next_page_results: boolean; + prev_page_results: boolean; + count: number; + total_pages: number; + extra_stats: null; + results: TIssueResponseResults; +}; diff --git a/packages/types/src/view-props.d.ts b/packages/types/src/view-props.d.ts index c2c98def3..a1c858bee 100644 --- a/packages/types/src/view-props.d.ts +++ b/packages/types/src/view-props.d.ts @@ -13,7 +13,6 @@ export type TIssueGroupByOptions = | "state_detail.group" | "project" | "assignees" - | "mentions" | "cycle" | "module" | null; @@ -51,143 +50,153 @@ export type TIssueOrderByOptions = export type TIssueTypeFilters = "active" | "backlog" | null; -export type TIssueExtraOptions = "show_empty_groups" | "sub_issue"; + export type TIssueExtraOptions = "show_empty_groups" | "sub_issue"; -export type TIssueParams = - | "priority" - | "state_group" - | "state" - | "assignees" - | "mentions" - | "created_by" - | "subscriber" - | "labels" - | "cycle" - | "module" - | "start_date" - | "target_date" - | "project" - | "group_by" - | "sub_group_by" - | "order_by" - | "type" - | "sub_issue" - | "show_empty_groups"; + export type TIssueParams = + | "priority" + | "state_group" + | "state" + | "assignees" + | "mentions" + | "created_by" + | "subscriber" + | "labels" + | "cycle" + | "module" + | "start_date" + | "target_date" + | "project" + | "group_by" + | "sub_group_by" + | "order_by" + | "type" + | "sub_issue" + | "show_empty_groups" + | "cursor" + | "per_page"; -export type TCalendarLayouts = "month" | "week"; + export type TCalendarLayouts = "month" | "week"; -export interface IIssueFilterOptions { - assignees?: string[] | null; - mentions?: string[] | null; - created_by?: string[] | null; - labels?: string[] | null; - priority?: string[] | null; - project?: string[] | null; - cycle?: string[] | null; - module?: string[] | null; - start_date?: string[] | null; - state?: string[] | null; - state_group?: string[] | null; - subscriber?: string[] | null; - target_date?: string[] | null; -} + export interface IIssueFilterOptions { + assignees?: string[] | null; + mentions?: string[] | null; + created_by?: string[] | null; + labels?: string[] | null; + priority?: string[] | null; + cycle?: string[] | null; + module?: string[] | null; + project?: string[] | null; + start_date?: string[] | null; + state?: string[] | null; + state_group?: string[] | null; + subscriber?: string[] | null; + target_date?: string[] | null; + } -export interface IIssueDisplayFilterOptions { - calendar?: { - show_weekends?: boolean; - layout?: TCalendarLayouts; + export interface IIssueDisplayFilterOptions { + calendar?: { + show_weekends?: boolean; + layout?: TCalendarLayouts; + }; + group_by?: TIssueGroupByOptions; + sub_group_by?: TIssueGroupByOptions; + layout?: TIssueLayouts; + order_by?: TIssueOrderByOptions; + show_empty_groups?: boolean; + sub_issue?: boolean; + type?: TIssueTypeFilters; + } + export interface IIssueDisplayProperties { + assignee?: boolean; + start_date?: boolean; + due_date?: boolean; + labels?: boolean; + key?: boolean; + priority?: boolean; + state?: boolean; + sub_issue_count?: boolean; + link?: boolean; + attachment_count?: boolean; + estimate?: boolean; + created_on?: boolean; + updated_on?: boolean; + modules?: boolean; + cycle?: boolean; + } + + export type TIssueKanbanFilters = { + group_by: string[]; + sub_group_by: string[]; }; - group_by?: TIssueGroupByOptions; - sub_group_by?: TIssueGroupByOptions; - layout?: TIssueLayouts; - order_by?: TIssueOrderByOptions; - show_empty_groups?: boolean; - sub_issue?: boolean; - type?: TIssueTypeFilters; -} -export interface IIssueDisplayProperties { - assignee?: boolean; - start_date?: boolean; - due_date?: boolean; - labels?: boolean; - key?: boolean; - priority?: boolean; - state?: boolean; - sub_issue_count?: boolean; - link?: boolean; - attachment_count?: boolean; - estimate?: boolean; - created_on?: boolean; - updated_on?: boolean; - modules?: boolean; - cycle?: boolean; -} -export type TIssueKanbanFilters = { - group_by: string[]; - sub_group_by: string[]; -}; + export interface IIssueFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; + kanbanFilters: TIssueKanbanFilters | undefined; + } -export interface IIssueFilters { - filters: IIssueFilterOptions | undefined; - displayFilters: IIssueDisplayFilterOptions | undefined; - displayProperties: IIssueDisplayProperties | undefined; - kanbanFilters: TIssueKanbanFilters | undefined; -} + export interface IIssueFiltersResponse { + filters: IIssueFilterOptions; + display_filters: IIssueDisplayFilterOptions; + display_properties: IIssueDisplayProperties; + } -export interface IIssueFiltersResponse { - filters: IIssueFilterOptions; - display_filters: IIssueDisplayFilterOptions; - display_properties: IIssueDisplayProperties; -} + export interface IWorkspaceIssueFilterOptions { + assignees?: string[] | null; + created_by?: string[] | null; + labels?: string[] | null; + priority?: string[] | null; + state_group?: string[] | null; + subscriber?: string[] | null; + start_date?: string[] | null; + target_date?: string[] | null; + project?: string[] | null; + } -export interface IWorkspaceIssueFilterOptions { - assignees?: string[] | null; - created_by?: string[] | null; - labels?: string[] | null; - priority?: string[] | null; - state_group?: string[] | null; - subscriber?: string[] | null; - start_date?: string[] | null; - target_date?: string[] | null; - project?: string[] | null; -} + export interface IWorkspaceGlobalViewDisplayFilterOptions { + order_by?: string | undefined; + type?: "active" | "backlog" | null; + sub_issue?: boolean; + layout?: TIssueViewOptions; + } -export interface IWorkspaceGlobalViewDisplayFilterOptions { - order_by?: string | undefined; - type?: "active" | "backlog" | null; - sub_issue?: boolean; - layout?: TIssueViewOptions; -} + export interface IWorkspaceViewIssuesParams { + assignees?: string | undefined; + created_by?: string | undefined; + labels?: string | undefined; + priority?: string | undefined; + start_date?: string | undefined; + state?: string | undefined; + state_group?: string | undefined; + subscriber?: string | undefined; + target_date?: string | undefined; + project?: string | undefined; + order_by?: string | undefined; + type?: "active" | "backlog" | undefined; + sub_issue?: boolean; + } -export interface IWorkspaceViewIssuesParams { - assignees?: string | undefined; - created_by?: string | undefined; - labels?: string | undefined; - priority?: string | undefined; - start_date?: string | undefined; - state?: string | undefined; - state_group?: string | undefined; - subscriber?: string | undefined; - target_date?: string | undefined; - project?: string | undefined; - order_by?: string | undefined; - type?: "active" | "backlog" | undefined; - sub_issue?: boolean; -} + export interface IProjectViewProps { + display_filters: IIssueDisplayFilterOptions | undefined; + filters: IIssueFilterOptions; + } -export interface IProjectViewProps { - display_filters: IIssueDisplayFilterOptions | undefined; - filters: IIssueFilterOptions; -} + export interface IWorkspaceViewProps { + filters: IIssueFilterOptions; + display_filters: IIssueDisplayFilterOptions | undefined; + display_properties: IIssueDisplayProperties; + } + export interface IWorkspaceGlobalViewProps { + filters: IWorkspaceIssueFilterOptions; + display_filters: IWorkspaceIssueDisplayFilterOptions | undefined; + display_properties: IIssueDisplayProperties; + } -export interface IWorkspaceViewProps { - filters: IIssueFilterOptions; - display_filters: IIssueDisplayFilterOptions | undefined; - display_properties: IIssueDisplayProperties; -} -export interface IWorkspaceGlobalViewProps { - filters: IWorkspaceIssueFilterOptions; - display_filters: IWorkspaceIssueDisplayFilterOptions | undefined; - display_properties: IIssueDisplayProperties; -} + export interface IssuePaginationOptions { + canGroup: boolean; + perPageCount: number; + greaterThanDate?: Date; + lessThanDate?: Date; + groupedBy?: TIssueGroupByOptions; + } diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 7e0fd7a06..d474d2a49 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -427,3 +427,17 @@ export const groupReactionEmojis = (reactions: any) => { return _groupedEmojis; }; + + +export enum IssueGroupByOptions { + "state" = "state_id", + "priority" = "priority", + "labels" = "labels__id", + "state_detail.group" = "state__group", + "assignees" = "assignees__id", + "cycle" = "cycle_id", + "module" = "modules__id", + "target_date" = "target_date", + "project" = "project_id", + "created_by" = "created_by", +} diff --git a/web/hooks/use-issues-actions.tsx b/web/hooks/use-issues-actions.tsx index 94add6285..e9e9a2c78 100644 --- a/web/hooks/use-issues-actions.tsx +++ b/web/hooks/use-issues-actions.tsx @@ -4,14 +4,21 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, + IssuePaginationOptions, TIssue, TIssueKanbanFilters, + TIssuesResponse, TLoader, } from "@plane/types"; import { useCallback, useMemo } from "react"; interface IssueActions { - fetchIssues?: (projectId: string, loadType: TLoader) => Promise; + fetchIssues: ( + loadType: TLoader, + options: IssuePaginationOptions, + userViewId?: "assigned" | "created" | "subscribed" + ) => Promise; + fetchNextIssues: () => Promise; removeIssue: (projectId: string, issueId: string) => Promise; createIssue?: (projectId: string, data: Partial) => Promise; updateIssue?: (projectId: string, issueId: string, data: Partial) => Promise; @@ -29,25 +36,25 @@ export const useIssuesActions = (storeType: EIssuesStoreType): IssueActions => { const projectIssueActions = useProjectIssueActions(); const cycleIssueActions = useCycleIssueActions(); const moduleIssueActions = useModuleIssueActions(); - const profileIssueActions = useProfileIssueActions(); const projectViewIssueActions = useProjectViewIssueActions(); + const globalIssueActions = useGlobalIssueActions(); + const profileIssueActions = useProfileIssueActions(); const draftIssueActions = useDraftIssueActions(); const archivedIssueActions = useArchivedIssueActions(); - const globalIssueActions = useGlobalIssueActions(); switch (storeType) { case EIssuesStoreType.PROJECT_VIEW: return projectViewIssueActions; case EIssuesStoreType.PROFILE: return profileIssueActions; - case EIssuesStoreType.CYCLE: - return cycleIssueActions; - case EIssuesStoreType.MODULE: - return moduleIssueActions; case EIssuesStoreType.ARCHIVED: return archivedIssueActions; case EIssuesStoreType.DRAFT: return draftIssueActions; + case EIssuesStoreType.CYCLE: + return cycleIssueActions; + case EIssuesStoreType.MODULE: + return moduleIssueActions; case EIssuesStoreType.GLOBAL: return globalIssueActions; case EIssuesStoreType.PROJECT: @@ -60,16 +67,21 @@ const useProjectIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT); const { - router: { workspaceSlug }, + router: { workspaceSlug, projectId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options); }, - [issues.fetchIssues, workspaceSlug] + [issues.fetchIssues, workspaceSlug, projectId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { if (!workspaceSlug) return; @@ -114,13 +126,14 @@ const useProjectIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters, }), - [fetchIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters] + [fetchIssues, fetchNextIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters] ); }; @@ -128,16 +141,21 @@ const useCycleIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE); const { - router: { workspaceSlug, cycleId }, + router: { workspaceSlug, projectId, cycleId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!cycleId || !workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType, cycleId); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId || !cycleId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options, cycleId.toString()); }, - [issues.fetchIssues, cycleId, workspaceSlug] + [issues.fetchIssues, workspaceSlug, projectId, cycleId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId || !cycleId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), cycleId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId, cycleId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { if (!cycleId || !workspaceSlug) return; @@ -147,17 +165,17 @@ const useCycleIssueActions = () => { ); const updateIssue = useCallback( async (projectId: string, issueId: string, data: Partial) => { - if (!cycleId || !workspaceSlug) return; - return await issues.updateIssue(workspaceSlug, projectId, issueId, data, cycleId); + if (!workspaceSlug) return; + return await issues.updateIssue(workspaceSlug, projectId, issueId, data); }, - [issues.updateIssue, cycleId, workspaceSlug] + [issues.updateIssue, workspaceSlug] ); const removeIssue = useCallback( async (projectId: string, issueId: string) => { - if (!cycleId || !workspaceSlug) return; - return await issues.removeIssue(workspaceSlug, projectId, issueId, cycleId); + if (!workspaceSlug) return; + return await issues.removeIssue(workspaceSlug, projectId, issueId); }, - [issues.removeIssue, cycleId, workspaceSlug] + [issues.removeIssue, workspaceSlug] ); const removeIssueFromView = useCallback( async (projectId: string, issueId: string) => { @@ -168,10 +186,10 @@ const useCycleIssueActions = () => { ); const archiveIssue = useCallback( async (projectId: string, issueId: string) => { - if (!cycleId || !workspaceSlug) return; - return await issues.archiveIssue(workspaceSlug, projectId, issueId, cycleId); + if (!workspaceSlug) return; + return await issues.archiveIssue(workspaceSlug, projectId, issueId); }, - [issues.archiveIssue, cycleId, workspaceSlug] + [issues.archiveIssue, workspaceSlug] ); const updateFilters = useCallback( @@ -189,6 +207,7 @@ const useCycleIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, @@ -196,7 +215,16 @@ const useCycleIssueActions = () => { archiveIssue, updateFilters, }), - [fetchIssues, createIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, updateFilters] + [ + fetchIssues, + fetchNextIssues, + createIssue, + updateIssue, + removeIssue, + removeIssueFromView, + archiveIssue, + updateFilters, + ] ); }; @@ -204,16 +232,21 @@ const useModuleIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE); const { - router: { workspaceSlug, moduleId }, + router: { workspaceSlug, projectId, moduleId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!moduleId || !workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType, moduleId); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId || !moduleId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options, moduleId.toString()); }, - [issues.fetchIssues, moduleId, workspaceSlug] + [issues.fetchIssues, workspaceSlug, projectId, moduleId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId || !moduleId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), moduleId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId, moduleId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { if (!moduleId || !workspaceSlug) return; @@ -223,17 +256,17 @@ const useModuleIssueActions = () => { ); const updateIssue = useCallback( async (projectId: string, issueId: string, data: Partial) => { - if (!moduleId || !workspaceSlug) return; - return await issues.updateIssue(workspaceSlug, projectId, issueId, data, moduleId); + if (!workspaceSlug) return; + return await issues.updateIssue(workspaceSlug, projectId, issueId, data); }, - [issues.updateIssue, moduleId, workspaceSlug] + [issues.updateIssue, workspaceSlug] ); const removeIssue = useCallback( async (projectId: string, issueId: string) => { - if (!moduleId || !workspaceSlug) return; - return await issues.removeIssue(workspaceSlug, projectId, issueId, moduleId); + if (!workspaceSlug) return; + return await issues.removeIssue(workspaceSlug, projectId, issueId); }, - [issues.removeIssue, moduleId, workspaceSlug] + [issues.removeIssue, workspaceSlug] ); const removeIssueFromView = useCallback( async (projectId: string, issueId: string) => { @@ -244,8 +277,8 @@ const useModuleIssueActions = () => { ); const archiveIssue = useCallback( async (projectId: string, issueId: string) => { - if (!moduleId || !workspaceSlug) return; - return await issues.archiveIssue(workspaceSlug, projectId, issueId, moduleId); + if (!workspaceSlug) return; + return await issues.archiveIssue(workspaceSlug, projectId, issueId); }, [issues.archiveIssue, moduleId, workspaceSlug] ); @@ -265,6 +298,7 @@ const useModuleIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, @@ -284,39 +318,44 @@ const useProfileIssueActions = () => { } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!userId || !workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType, userId); + async (loadType: TLoader, options: IssuePaginationOptions, viewId?: "assigned" | "created" | "subscribed") => { + if (!workspaceSlug || !userId || !viewId) return; + return issues.fetchIssues(workspaceSlug.toString(), userId.toString(), loadType, options, viewId); }, - [issues.fetchIssues, userId, workspaceSlug] + [issues.fetchIssues, workspaceSlug, userId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !userId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), userId.toString()); + }, [issues.fetchIssues, workspaceSlug, userId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { - if (!userId || !workspaceSlug) return; - return await issues.createIssue(workspaceSlug, projectId, data, userId); + if (!workspaceSlug) return; + return await issues.createIssue(workspaceSlug, projectId, data); }, - [issues.createIssue, userId, workspaceSlug] + [issues.createIssue, workspaceSlug] ); const updateIssue = useCallback( async (projectId: string, issueId: string, data: Partial) => { - if (!userId || !workspaceSlug) return; - return await issues.updateIssue(workspaceSlug, projectId, issueId, data, userId); + if (!workspaceSlug) return; + return await issues.updateIssue(workspaceSlug, projectId, issueId, data); }, - [issues.updateIssue, userId, workspaceSlug] + [issues.updateIssue, workspaceSlug] ); const removeIssue = useCallback( async (projectId: string, issueId: string) => { - if (!userId || !workspaceSlug) return; - return await issues.removeIssue(workspaceSlug, projectId, issueId, userId); + if (!workspaceSlug) return; + return await issues.removeIssue(workspaceSlug, projectId, issueId); }, - [issues.removeIssue, userId, workspaceSlug] + [issues.removeIssue, workspaceSlug] ); const archiveIssue = useCallback( async (projectId: string, issueId: string) => { - if (!userId || !workspaceSlug) return; - return await issues.archiveIssue(workspaceSlug, projectId, issueId, userId); + if (!workspaceSlug) return; + return await issues.archiveIssue(workspaceSlug, projectId, issueId); }, - [issues.archiveIssue, userId, workspaceSlug] + [issues.archiveIssue, workspaceSlug] ); const updateFilters = useCallback( @@ -334,6 +373,7 @@ const useProfileIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, @@ -348,43 +388,48 @@ const useProjectViewIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW); const { - router: { workspaceSlug, viewId }, + router: { workspaceSlug, projectId, viewId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!viewId || !workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType, viewId); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options); }, - [issues.fetchIssues, viewId, workspaceSlug] + [issues.fetchIssues, workspaceSlug, projectId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { - if (!viewId || !workspaceSlug) return; - return await issues.createIssue(workspaceSlug, projectId, data, viewId); + if (!workspaceSlug) return; + return await issues.createIssue(workspaceSlug, projectId, data); }, - [issues.createIssue, viewId, workspaceSlug] + [issues.createIssue, workspaceSlug] ); const updateIssue = useCallback( async (projectId: string, issueId: string, data: Partial) => { - if (!viewId || !workspaceSlug) return; - return await issues.updateIssue(workspaceSlug, projectId, issueId, data, viewId); + if (!workspaceSlug) return; + return await issues.updateIssue(workspaceSlug, projectId, issueId, data); }, - [issues.updateIssue, viewId, workspaceSlug] + [issues.updateIssue, workspaceSlug] ); const removeIssue = useCallback( async (projectId: string, issueId: string) => { - if (!viewId || !workspaceSlug) return; - return await issues.removeIssue(workspaceSlug, projectId, issueId, viewId); + if (!workspaceSlug) return; + return await issues.removeIssue(workspaceSlug, projectId, issueId); }, - [issues.removeIssue, viewId, workspaceSlug] + [issues.removeIssue, workspaceSlug] ); const archiveIssue = useCallback( async (projectId: string, issueId: string) => { - if (!viewId || !workspaceSlug) return; - return await issues.archiveIssue(workspaceSlug, projectId, issueId, viewId); + if (!workspaceSlug) return; + return await issues.archiveIssue(workspaceSlug, projectId, issueId); }, - [issues.archiveIssue, viewId, workspaceSlug] + [issues.archiveIssue, workspaceSlug] ); const updateFilters = useCallback( @@ -402,13 +447,14 @@ const useProjectViewIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters, }), - [fetchIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters] + [fetchIssues, fetchNextIssues, createIssue, updateIssue, removeIssue, archiveIssue, updateFilters] ); }; @@ -416,16 +462,21 @@ const useDraftIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT); const { - router: { workspaceSlug }, + router: { workspaceSlug, projectId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options); }, - [issues.fetchIssues, workspaceSlug] + [issues.fetchIssues, workspaceSlug, projectId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { if (!workspaceSlug) return; @@ -463,6 +514,7 @@ const useDraftIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, @@ -476,16 +528,21 @@ const useArchivedIssueActions = () => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED); const { - router: { workspaceSlug }, + router: { workspaceSlug, projectId }, } = useApplication(); const fetchIssues = useCallback( - async (projectId: string, loadType: TLoader) => { - if (!workspaceSlug) return; - return await issues.fetchIssues(workspaceSlug, projectId, loadType); + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !projectId) return; + return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options); }, - [issues.fetchIssues] + [issues.fetchIssues, workspaceSlug, projectId] ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !projectId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString()); + }, [issues.fetchIssues, workspaceSlug, projectId]); + const removeIssue = useCallback( async (projectId: string, issueId: string) => { if (!workspaceSlug) return; @@ -516,11 +573,12 @@ const useArchivedIssueActions = () => { return useMemo( () => ({ fetchIssues, + fetchNextIssues, removeIssue, restoreIssue, updateFilters, }), - [fetchIssues, removeIssue, restoreIssue, updateFilters] + [fetchIssues, fetchNextIssues, removeIssue, restoreIssue, updateFilters] ); }; @@ -530,26 +588,38 @@ const useGlobalIssueActions = () => { const { router: { workspaceSlug, globalViewId }, } = useApplication(); + const fetchIssues = useCallback( + async (loadType: TLoader, options: IssuePaginationOptions) => { + if (!workspaceSlug || !globalViewId) return; + return issues.fetchIssues(workspaceSlug.toString(), globalViewId.toString(), loadType, options); + }, + [issues.fetchIssues, workspaceSlug, globalViewId] + ); + const fetchNextIssues = useCallback(async () => { + if (!workspaceSlug || !globalViewId) return; + return issues.fetchNextIssues(workspaceSlug.toString(), globalViewId.toString()); + }, [issues.fetchIssues, workspaceSlug, globalViewId]); + const createIssue = useCallback( async (projectId: string, data: Partial) => { - if (!globalViewId || !workspaceSlug) return; - return await issues.createIssue(workspaceSlug, projectId, data, globalViewId); + if (!workspaceSlug) return; + return await issues.createIssue(workspaceSlug, projectId, data); }, - [issues.createIssue, globalViewId, workspaceSlug] + [issues.createIssue, workspaceSlug] ); const updateIssue = useCallback( async (projectId: string, issueId: string, data: Partial) => { - if (!globalViewId || !workspaceSlug) return; - return await issues.updateIssue(workspaceSlug, projectId, issueId, data, globalViewId); + if (!workspaceSlug) return; + return await issues.updateIssue(workspaceSlug, projectId, issueId, data); }, - [issues.updateIssue, globalViewId, workspaceSlug] + [issues.updateIssue, workspaceSlug] ); const removeIssue = useCallback( async (projectId: string, issueId: string) => { - if (!globalViewId || !workspaceSlug) return; - return await issues.removeIssue(workspaceSlug, projectId, issueId, globalViewId); + if (!workspaceSlug) return; + return await issues.removeIssue(workspaceSlug, projectId, issueId); }, - [issues.removeIssue, globalViewId, workspaceSlug] + [issues.removeIssue, workspaceSlug] ); const updateFilters = useCallback( @@ -566,6 +636,8 @@ const useGlobalIssueActions = () => { return useMemo( () => ({ + fetchIssues, + fetchNextIssues, createIssue, updateIssue, removeIssue, diff --git a/web/services/cycle.service.ts b/web/services/cycle.service.ts index f7ee8a0ab..0d3be5f6e 100644 --- a/web/services/cycle.service.ts +++ b/web/services/cycle.service.ts @@ -2,7 +2,7 @@ import { API_BASE_URL } from "helpers/common.helper"; import { APIService } from "services/api.service"; // types -import type { CycleDateCheckData, ICycle, TIssue } from "@plane/types"; +import type { CycleDateCheckData, ICycle, TIssue, TIssuesResponse } from "@plane/types"; // helpers export class CycleService extends APIService { @@ -46,20 +46,12 @@ export class CycleService extends APIService { }); } - async getCycleIssues(workspaceSlug: string, projectId: string, cycleId: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async getCycleIssuesWithParams( + async getCycleIssues( workspaceSlug: string, projectId: string, cycleId: string, queries?: any - ): Promise { + ): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, { params: queries, }) diff --git a/web/services/issue/issue.service.ts b/web/services/issue/issue.service.ts index d7f92f792..c8e17c911 100644 --- a/web/services/issue/issue.service.ts +++ b/web/services/issue/issue.service.ts @@ -2,7 +2,14 @@ import { API_BASE_URL } from "helpers/common.helper"; import { APIService } from "services/api.service"; // type -import type { TIssue, IIssueDisplayProperties, TIssueLink, TIssueSubIssues, TIssueActivity } from "@plane/types"; +import type { + TIssue, + IIssueDisplayProperties, + TIssueLink, + TIssueSubIssues, + TIssueActivity, + TIssuesResponse, +} from "@plane/types"; // helper export class IssueService extends APIService { @@ -10,7 +17,7 @@ export class IssueService extends APIService { super(API_BASE_URL); } - async createIssue(workspaceSlug: string, projectId: string, data: any): Promise { + async createIssue(workspaceSlug: string, projectId: string, data: Partial): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data) .then((response) => response?.data) .catch((error) => { @@ -18,7 +25,7 @@ export class IssueService extends APIService { }); } - async getIssues(workspaceSlug: string, projectId: string, queries?: any): Promise { + async getIssues(workspaceSlug: string, projectId: string, queries?: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, { params: queries, }) diff --git a/web/services/issue/issue_draft.service.ts b/web/services/issue/issue_draft.service.ts index 3ccd43f56..ee8fc9aa9 100644 --- a/web/services/issue/issue_draft.service.ts +++ b/web/services/issue/issue_draft.service.ts @@ -1,14 +1,14 @@ import { API_BASE_URL } from "helpers/common.helper"; import { APIService } from "services/api.service"; // helpers -import { TIssue } from "@plane/types"; +import { TIssue, TIssuesResponse } from "@plane/types"; export class IssueDraftService extends APIService { constructor() { super(API_BASE_URL); } - async getDraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise { + async getDraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, { params: { ...query }, }) @@ -18,7 +18,7 @@ export class IssueDraftService extends APIService { }); } - async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise { + async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data) .then((response) => response?.data) .catch((error) => { @@ -26,7 +26,7 @@ export class IssueDraftService extends APIService { }); } - async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise { + async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, data) .then((response) => response?.data) .catch((error) => { @@ -34,7 +34,7 @@ export class IssueDraftService extends APIService { }); } - async deleteDraftIssue(workspaceSlug: string, projectId: string, issueId: string): Promise { + async deleteDraftIssue(workspaceSlug: string, projectId: string, issueId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`) .then((response) => response?.data) .catch((error) => { @@ -42,7 +42,7 @@ export class IssueDraftService extends APIService { }); } - async getDraftIssueById(workspaceSlug: string, projectId: string, issueId: string, queries?: any): Promise { + async getDraftIssueById(workspaceSlug: string, projectId: string, issueId: string, queries?: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, { params: queries, }) diff --git a/web/services/module.service.ts b/web/services/module.service.ts index 9942f691c..bf07d07ff 100644 --- a/web/services/module.service.ts +++ b/web/services/module.service.ts @@ -2,7 +2,7 @@ import { API_BASE_URL } from "helpers/common.helper"; import { APIService } from "services/api.service"; // types -import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types"; +import type { IModule, ILinkDetails, ModuleLink, TIssuesResponse } from "@plane/types"; export class ModuleService extends APIService { constructor() { @@ -70,7 +70,12 @@ export class ModuleService extends APIService { }); } - async getModuleIssues(workspaceSlug: string, projectId: string, moduleId: string, queries?: any): Promise { + async getModuleIssues( + workspaceSlug: string, + projectId: string, + moduleId: string, + queries?: any + ): Promise { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, { params: queries, }) @@ -111,7 +116,7 @@ export class ModuleService extends APIService { projectId: string, moduleId: string, issueId: string - ): Promise { + ): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) .then((response) => response?.data) .catch((error) => { @@ -124,14 +129,14 @@ export class ModuleService extends APIService { projectId: string, moduleId: string, issueIds: string[] - ): Promise { + ): Promise { const promiseDataUrls: any = []; issueIds.forEach((issueId) => { promiseDataUrls.push( this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) ); }); - return await Promise.all(promiseDataUrls) + await Promise.all(promiseDataUrls) .then((response) => response) .catch((error) => { throw error?.response?.data; @@ -143,14 +148,14 @@ export class ModuleService extends APIService { projectId: string, issueId: string, moduleIds: string[] - ): Promise { + ): Promise { const promiseDataUrls: any = []; moduleIds.forEach((moduleId) => { promiseDataUrls.push( this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) ); }); - return await Promise.all(promiseDataUrls) + await Promise.all(promiseDataUrls) .then((response) => response) .catch((error) => { throw error?.response?.data; diff --git a/web/services/user.service.ts b/web/services/user.service.ts index 691e6c028..43ea80b68 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -11,6 +11,7 @@ import type { IUserProfileProjectSegregation, IUserSettings, IUserEmailNotificationSettings, + TIssuesResponse, } from "@plane/types"; // helpers @@ -178,7 +179,7 @@ export class UserService extends APIService { }); } - async getUserProfileIssues(workspaceSlug: string, userId: string, params: any): Promise { + async getUserProfileIssues(workspaceSlug: string, userId: string, params: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/user-issues/${userId}/`, { params, }) diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index bfeadad03..496dc28da 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -14,8 +14,8 @@ import { IWorkspaceBulkInviteFormData, IWorkspaceViewProps, IUserProjectsRole, - TIssue, IWorkspaceView, + TIssuesResponse, } from "@plane/types"; export class WorkspaceService extends APIService { @@ -257,7 +257,7 @@ export class WorkspaceService extends APIService { }); } - async getViewIssues(workspaceSlug: string, params: any): Promise { + async getViewIssues(workspaceSlug: string, params: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/issues/`, { params, }) diff --git a/web/store/issue/archived/filter.store.ts b/web/store/issue/archived/filter.store.ts index 12d541bea..1d169bb19 100644 --- a/web/store/issue/archived/filter.store.ts +++ b/web/store/issue/archived/filter.store.ts @@ -14,20 +14,22 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface IArchivedIssuesFilter { - // observables - filters: Record; // Record defines projectId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface IArchivedIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string) => Promise; updateFilters: ( @@ -92,6 +94,22 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string) => { try { const _filters = this.handleIssuesLocalFilters.get( @@ -150,7 +168,7 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc }); const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.archivedIssues.fetchIssues( + this.rootIssueStore.archivedIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, isEmpty(filteredFilters) ? "init-loader" : "mutation" @@ -193,7 +211,7 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.archivedIssues.fetchIssues(workspaceSlug, projectId, "mutation"); + this.rootIssueStore.archivedIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation"); this.handleIssuesLocalFilters.set(EIssuesStoreType.ARCHIVED, type, workspaceSlug, projectId, undefined, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/archived/issue.store.ts b/web/store/issue/archived/issue.store.ts index 14a7b4008..c3c753236 100644 --- a/web/store/issue/archived/issue.store.ts +++ b/web/store/issue/archived/issue.store.ts @@ -1,35 +1,36 @@ import pull from "lodash/pull"; -import set from "lodash/set"; -import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { action, makeObservable, runInAction } from "mobx"; // base class -import { IssueArchiveService } from "services/issue"; -import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; // services // types import { IIssueRootStore } from "../root.store"; +import { IArchivedIssuesFilter } from "./filter.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; -export interface IArchivedIssues { +export interface IArchivedIssues extends IBaseIssuesStore { // observable - loader: TLoader; - issues: { [project_id: string]: string[] }; viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions - fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + fetchIssues: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + option: IssuePaginationOptions + ) => Promise; + fetchIssuesWithExistingPagination: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader + ) => Promise; + fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise; + restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - quickAddIssue: undefined; } -export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues { - loader: TLoader = "init-loader"; - issues: { [project_id: string]: string[] } = {}; - // root store - rootIssueStore: IIssueRootStore; - // services - archivedIssueService; +export class ArchivedIssues extends BaseIssuesStore implements IArchivedIssues { + // filter store + issueFilterStore: IArchivedIssuesFilter; //viewData viewFlags = { @@ -38,99 +39,73 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues enableInlineEditing: true, }; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IArchivedIssuesFilter) { + super(_rootStore, issueFilterStore, true); makeObservable(this, { - // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, // action fetchIssues: action, - removeIssue: action, restoreIssue: action, }); - // root store - this.rootIssueStore = _rootStore; - // services - this.archivedIssueService = new IssueArchiveService(); + // filter store + this.issueFilterStore = issueFilterStore; } - get groupedIssueIds() { - const projectId = this.rootIssueStore.projectId; - if (!projectId) return undefined; - - const displayFilters = this.rootIssueStore?.archivedIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const archivedIssueIds = this.issues[projectId]; - if (!archivedIssueIds) return undefined; - - const _issues = this.rootIssueStore.issues.getIssuesByIds(archivedIssueIds, "archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } - - return issues; - } - - fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => { + fetchIssues = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "init-loader", + options: IssuePaginationOptions + ) => { try { - this.loader = loadType; - - const params = this.rootIssueStore?.archivedIssuesFilter?.appliedFilters; - const response = await this.archivedIssueService.getArchivedIssues(workspaceSlug, projectId, params); - runInAction(() => { - set( - this.issues, - [projectId], - response.map((issue: TIssue) => issue.id) - ); - this.loader = undefined; + this.loader = loadType; }); + this.clear(); + const params = this.issueFilterStore?.getFilterParams(options); + const response = await this.issueArchiveService.getArchivedIssues(workspaceSlug, projectId, params); - this.rootIssueStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { - console.error(error); this.loader = undefined; throw error; } }; - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string) => { + if (!this.paginationOptions) return; try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + this.loader = "pagination"; - runInAction(() => { - pull(this.issues[projectId], issueId); - }); + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); + + this.onfetchNexIssues(response); + return response; } catch (error) { + this.loader = undefined; throw error; } }; + fetchIssuesWithExistingPagination = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "mutation" + ) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions); + }; + restoreIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { try { - const response = await this.archivedIssueService.restoreIssue(workspaceSlug, projectId, issueId); + const response = await this.issueArchiveService.restoreIssue(workspaceSlug, projectId, issueId); runInAction(() => { - this.rootStore.issues.updateIssue(issueId, { + this.rootIssueStore.issues.updateIssue(issueId, { archived_at: null, }); - pull(this.issues[projectId], issueId); + this.issues && pull(this.issues, issueId); }); return response; @@ -138,6 +113,4 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues throw error; } }; - - quickAddIssue: undefined; } diff --git a/web/store/issue/cycle/filter.store.ts b/web/store/issue/cycle/filter.store.ts index 6d5050b59..27785616e 100644 --- a/web/store/issue/cycle/filter.store.ts +++ b/web/store/issue/cycle/filter.store.ts @@ -14,20 +14,22 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface ICycleIssuesFilter { - // observables - filters: Record; // Record defines cycleId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface ICycleIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; updateFilters: ( @@ -95,6 +97,22 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => { try { const _filters = await this.issueFilterService.fetchCycleIssueFilters(workspaceSlug, projectId, cycleId); @@ -161,7 +179,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.cycleIssues.fetchIssues( + this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, isEmpty(filteredFilters) ? "init-loader" : "mutation", @@ -205,7 +223,12 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination( + workspaceSlug, + projectId, + "mutation", + cycleId + ); await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/cycle/issue.store.ts b/web/store/issue/cycle/issue.store.ts index b270f0054..3a961c9e8 100644 --- a/web/store/issue/cycle/issue.store.ts +++ b/web/store/issue/cycle/issue.store.ts @@ -1,55 +1,43 @@ import concat from "lodash/concat"; import pull from "lodash/pull"; -import set from "lodash/set"; import uniq from "lodash/uniq"; import update from "lodash/update"; -import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { action, observable, makeObservable, runInAction } from "mobx"; // base class // services import { CycleService } from "services/cycle.service"; -import { IssueService } from "services/issue"; // types -import { TIssue, TSubGroupedIssues, TGroupedIssues, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; import { IIssueRootStore } from "../root.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; +import { ICycleIssuesFilter } from "./filter.store"; export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES"; -export interface ICycleIssues { - // observable - loader: TLoader; - issues: { [cycle_id: string]: string[] }; +export interface ICycleIssues extends IBaseIssuesStore { viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions fetchIssues: ( workspaceSlug: string, projectId: string, loadType: TLoader, + options: IssuePaginationOptions, cycleId: string - ) => Promise; - createIssue: ( + ) => Promise; + fetchIssuesWithExistingPagination: ( workspaceSlug: string, projectId: string, - data: Partial, + loadType: TLoader, cycleId: string - ) => Promise; - updateIssue: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - cycleId: string - ) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise; - archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise; - quickAddIssue: ( - workspaceSlug: string, - projectId: string, - data: TIssue, - cycleId?: string | undefined - ) => Promise; + ) => Promise; + fetchNextIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + + createIssue: (workspaceSlug: string, projectId: string, data: Partial, cycleId: string) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; + removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise; + addIssueToCycle: ( workspaceSlug: string, projectId: string, @@ -66,106 +54,63 @@ export interface ICycleIssues { new_cycle_id: string; } ) => Promise; - fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + fetchActiveCycleIssues: ( + workspaceSlug: string, + projectId: string, + cycleId: string + ) => Promise; } -export class CycleIssues extends IssueHelperStore implements ICycleIssues { - loader: TLoader = "init-loader"; - issues: { [cycle_id: string]: string[] } = {}; +export class CycleIssues extends BaseIssuesStore implements ICycleIssues { + cycleId: string | undefined = undefined; viewFlags = { enableQuickAdd: true, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; // service cycleService; - issueService; + // filter store + issueFilterStore; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: ICycleIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, + cycleId: observable.ref, // action fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, - quickAddIssue: action, + addIssueToCycle: action, removeIssueFromCycle: action, transferIssuesFromCycle: action, fetchActiveCycleIssues: action, }); - - this.rootIssueStore = _rootStore; - this.issueService = new IssueService(); + // service this.cycleService = new CycleService(); - } - - get groupedIssueIds() { - const cycleId = this.rootIssueStore?.cycleId; - if (!cycleId) return undefined; - - const displayFilters = this.rootIssueStore?.cycleIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const cycleIssueIds = this.issues[cycleId]; - if (!cycleIssueIds) return; - - const _issues = this.rootIssueStore.issues.getIssuesByIds(cycleIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues = []; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } else if (layout === "calendar") issues = this.groupedIssues("target_date", "target_date", _issues, true); - else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", _issues); - - return issues; + // filter store + this.issueFilterStore = issueFilterStore; } fetchIssues = async ( workspaceSlug: string, projectId: string, - loadType: TLoader = "init-loader", + loadType: TLoader, + options: IssuePaginationOptions, cycleId: string ) => { try { - this.loader = loadType; - - const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters; - const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); - this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - runInAction(() => { - set( - this.issues, - [cycleId], - response.map((issue) => issue.id) - ); - this.loader = undefined; + this.loader = loadType; }); + this.clear(); - this.rootIssueStore.issues.addIssue(response); + this.cycleId = cycleId; + const params = this.issueFilterStore?.getFilterParams(options); + const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params); + + this.onfetchIssues(response, options); return response; } catch (error) { this.loader = undefined; @@ -173,7 +118,33 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial, cycleId: string) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => { + if (!this.paginationOptions) return; + try { + this.loader = "pagination"; + + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params); + + this.onfetchNexIssues(response); + return response; + } catch (error) { + this.loader = undefined; + throw error; + } + }; + + fetchIssuesWithExistingPagination = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + cycleId: string + ) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, cycleId); + }; + + override createIssue = async (workspaceSlug: string, projectId: string, data: Partial, cycleId: string) => { try { const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id], false); @@ -185,81 +156,6 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { } }; - updateIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - cycleId: string - ) => { - try { - await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); - this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); - throw error; - } - }; - - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => { - try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); - this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - - const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId); - if (issueIndex >= 0) - runInAction(() => { - this.issues[cycleId].splice(issueIndex, 1); - }); - } catch (error) { - throw error; - } - }; - - archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => { - try { - await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); - this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - - runInAction(() => { - pull(this.issues[cycleId], issueId); - }); - } catch (error) { - throw error; - } - }; - - quickAddIssue = async ( - workspaceSlug: string, - projectId: string, - data: TIssue, - cycleId: string | undefined = undefined - ) => { - try { - if (!cycleId) throw new Error("Cycle Id is required"); - - runInAction(() => { - this.issues[cycleId].push(data.id); - this.rootIssueStore.issues.addIssue([data]); - }); - - const response = await this.createIssue(workspaceSlug, projectId, data, cycleId); - this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - - const quickAddIssueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === data.id); - if (quickAddIssueIndex >= 0) - runInAction(() => { - this.issues[cycleId].splice(quickAddIssueIndex, 1); - this.rootIssueStore.issues.removeIssue(data.id); - }); - - return response; - } catch (error) { - if (cycleId) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); - throw error; - } - }; - addIssueToCycle = async ( workspaceSlug: string, projectId: string, @@ -275,17 +171,14 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds); runInAction(() => { - update(this.issues, cycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds))); + this.cycleId === cycleId && + update(this, "issues", (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds))); }); + issueIds.forEach((issueId) => { - const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id; - if (issueCycleId && issueCycleId !== cycleId) { - runInAction(() => { - pull(this.issues[issueCycleId], issueId); - }); - } - this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId }); + this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: cycleId }); }); + this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); } catch (error) { throw error; @@ -294,13 +187,12 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => { try { + await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); runInAction(() => { - pull(this.issues[cycleId], issueId); + this.issues && this.cycleId === cycleId && pull(this.issues, issueId); }); - this.rootStore.issues.updateIssue(issueId, { cycle_id: null }); - - await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); + this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: null }); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); } catch (error) { throw error; @@ -322,7 +214,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { cycleId as string, payload ); - await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + this.paginationOptions && + (await this.fetchIssues(workspaceSlug, projectId, "mutation", this.paginationOptions, cycleId)); return response; } catch (error) { @@ -333,14 +226,14 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => { try { const params = { priority: `urgent,high` }; - const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); + const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params); - runInAction(() => { - set(this.issues, [ACTIVE_CYCLE_ISSUES], Object.keys(response)); - this.loader = undefined; - }); + // runInAction(() => { + // set(this.issues, , Object.keys(response)); + // this.loader = undefined; + // }); - this.rootIssueStore.issues.addIssue(Object.values(response)); + // this.rootIssueStore.issues.addIssue(Object.values(response)); return response; } catch (error) { diff --git a/web/store/issue/draft/filter.store.ts b/web/store/issue/draft/filter.store.ts index 51b8d9bc7..bde6b3f76 100644 --- a/web/store/issue/draft/filter.store.ts +++ b/web/store/issue/draft/filter.store.ts @@ -14,20 +14,22 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface IDraftIssuesFilter { - // observables - filters: Record; // Record defines projectId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface IDraftIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string) => Promise; updateFilters: ( @@ -92,6 +94,22 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string) => { try { const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.DRAFT, workspaceSlug, projectId, undefined); @@ -145,7 +163,7 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI }); const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.draftIssues.fetchIssues( + this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, isEmpty(filteredFilters) ? "init-loader" : "mutation" @@ -188,7 +206,7 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.draftIssues.fetchIssues(workspaceSlug, projectId, "mutation"); + this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation"); this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/draft/issue.store.ts b/web/store/issue/draft/issue.store.ts index 67dcf2729..e7d4b26c0 100644 --- a/web/store/issue/draft/issue.store.ts +++ b/web/store/issue/draft/issue.store.ts @@ -1,178 +1,103 @@ -import concat from "lodash/concat"; -import pull from "lodash/pull"; -import set from "lodash/set"; -import uniq from "lodash/uniq"; -import update from "lodash/update"; -import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { action, makeObservable, runInAction } from "mobx"; // base class // services -import { IssueDraftService } from "services/issue/issue_draft.service"; // types -import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; import { IIssueRootStore } from "../root.store"; +import { IDraftIssuesFilter } from "./filter.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; -export interface IDraftIssues { +export interface IDraftIssues extends IBaseIssuesStore { // observable - loader: TLoader; - issues: { [project_id: string]: string[] }; + viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions - fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; + fetchIssues: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + option: IssuePaginationOptions + ) => Promise; + fetchIssuesWithExistingPagination: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader + ) => Promise; + + fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise; createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - quickAddIssue: undefined; } -export class DraftIssues extends IssueHelperStore implements IDraftIssues { - loader: TLoader = "init-loader"; - issues: { [project_id: string]: string[] } = {}; +export class DraftIssues extends BaseIssuesStore implements IDraftIssues { viewFlags = { enableQuickAdd: false, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; - // service - issueDraftService; + // filter store + issueFilterStore: IDraftIssuesFilter; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IDraftIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { - // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, // action fetchIssues: action, createIssue: action, updateIssue: action, removeIssue: action, }); - // root store - this.rootIssueStore = _rootStore; - this.issueDraftService = new IssueDraftService(); + // filter store + this.issueFilterStore = issueFilterStore; } - get getIssues() { - const projectId = this.rootIssueStore.projectId; - if (!projectId || !this.issues || !this.issues[projectId]) return undefined; - - return this.issues[projectId]; - } - - get groupedIssueIds() { - const projectId = this.rootIssueStore.projectId; - if (!projectId) return undefined; - - const displayFilters = this.rootIssueStore?.draftIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const draftIssueIds = this.issues[projectId]; - if (!draftIssueIds) return undefined; - - const _issues = this.rootIssueStore.issues.getIssuesByIds(draftIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } - - return issues; - } - - fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => { + fetchIssues = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "init-loader", + options: IssuePaginationOptions + ) => { try { - this.loader = loadType; - - const params = this.rootIssueStore?.draftIssuesFilter?.appliedFilters; + runInAction(() => { + this.loader = loadType; + }); + this.clear(); + const params = this.issueFilterStore?.getFilterParams(options); const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params); - runInAction(() => { - set( - this.issues, - [projectId], - response.map((issue) => issue.id) - ); - this.loader = undefined; - }); - - this.rootIssueStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { - console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string) => { + if (!this.paginationOptions) return; try { - const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data); + this.loader = "pagination"; - runInAction(() => { - update(this.issues, [projectId], (issueIds = []) => uniq(concat(issueIds, response.id))); - }); - - this.rootStore.issues.addIssue([response]); + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); + this.onfetchNexIssues(response); return response; } catch (error) { + this.loader = undefined; throw error; } }; - updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { - try { - await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data); - - this.rootStore.issues.updateIssue(issueId, data); - - if (data.hasOwnProperty("is_draft") && data?.is_draft === false) { - runInAction(() => { - update(this.issues, [projectId], (issueIds = []) => { - if (issueIds.includes(issueId)) pull(issueIds, issueId); - return issueIds; - }); - }); - } - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation"); - throw error; - } + fetchIssuesWithExistingPagination = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "mutation" + ) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions); }; - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - update(this.issues, [projectId], (issueIds = []) => { - if (issueIds.includes(issueId)) pull(issueIds, issueId); - return issueIds; - }); - }); - } catch (error) { - throw error; - } - }; - - quickAddIssue: undefined; + createIssue = this.createDraftIssue; + updateIssue = this.updateDraftIssue; } diff --git a/web/store/issue/helpers/issue-helper.store.ts b/web/store/issue/helpers/base-issues.store.ts similarity index 50% rename from web/store/issue/helpers/issue-helper.store.ts rename to web/store/issue/helpers/base-issues.store.ts index 50e04e890..93c42bb5f 100644 --- a/web/store/issue/helpers/issue-helper.store.ts +++ b/web/store/issue/helpers/base-issues.store.ts @@ -1,72 +1,387 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import update from "lodash/update"; +import uniq from "lodash/uniq"; +import concat from "lodash/concat"; +import pull from "lodash/pull"; +import orderBy from "lodash/orderBy"; import get from "lodash/get"; import indexOf from "lodash/indexOf"; import isEmpty from "lodash/isEmpty"; -import orderBy from "lodash/orderBy"; import values from "lodash/values"; // types +import { + TIssue, + TIssueMap, + TIssueGroupByOptions, + TIssueOrderByOptions, + TGroupedIssues, + TSubGroupedIssues, + TUnGroupedIssues, + TLoader, + IssuePaginationOptions, + TIssuesResponse, +} from "@plane/types"; +import { IIssueRootStore } from "../root.store"; +import { IBaseIssueFilterStore } from "./issue-filter-helper.store"; // constants import { ISSUE_PRIORITIES } from "constants/issue"; import { STATE_GROUPS } from "constants/state"; // helpers import { renderFormattedPayloadDate } from "helpers/date-time.helper"; -import { TIssue, TIssueMap, TIssueGroupByOptions, TIssueOrderByOptions } from "@plane/types"; -import { IIssueRootStore } from "../root.store"; +// services +import { IssueArchiveService, IssueDraftService, IssueService } from "services/issue"; export type TIssueDisplayFilterOptions = Exclude | "target_date"; -export type TIssueHelperStore = { +export interface IBaseIssuesStore { + // observable + loader: TLoader; + + issues: string[] | undefined; + + nextCursor: string | undefined; + prevCursor: string | undefined; + issueCount: number | undefined; + pageCount: number | undefined; + + groupedIssueCount: Record | undefined; + + // computed + groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; + + //actions + removeIssue(workspaceSlug: string, projectId: string, issueId: string): Promise; // helper methods groupedIssues( groupBy: TIssueDisplayFilterOptions, orderBy: TIssueOrderByOptions, issues: TIssueMap, + groupedIssueCount: Record, isCalendarIssues?: boolean - ): { [group_id: string]: string[] }; + ): TGroupedIssues; subGroupedIssues( subGroupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions, orderBy: TIssueOrderByOptions, - issues: TIssueMap - ): { [sub_group_id: string]: { [group_id: string]: string[] } }; - unGroupedIssues(orderBy: TIssueOrderByOptions, issues: TIssueMap): string[]; + issues: TIssueMap, + groupedIssueCount: Record + ): TSubGroupedIssues; + unGroupedIssues(orderBy: TIssueOrderByOptions, issues: TIssueMap, count: number): TUnGroupedIssues; issueDisplayFiltersDefaultData(groupBy: string | null): string[]; issuesSortWithOrderBy(issueObject: TIssueMap, key: Partial): TIssue[]; getGroupArray(value: boolean | number | string | string[] | null, isDate?: boolean): string[]; -}; +} const ISSUE_FILTER_DEFAULT_DATA: Record = { project: "project_id", - cycle: "cycle_id", - module: "module_ids", state: "state_id", "state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display, priority: "priority", labels: "label_ids", created_by: "created_by", assignees: "assignee_ids", - mentions: "assignee_ids", target_date: "target_date", + cycle: "cycle_id", + module: "module_ids", }; -export class IssueHelperStore implements TIssueHelperStore { - // root store - rootStore; +export class BaseIssuesStore implements IBaseIssuesStore { + loader: TLoader = "init-loader"; + issues: string[] | undefined = undefined; + groupedIssueCount: Record | undefined = undefined; - constructor(_rootStore: IIssueRootStore) { - this.rootStore = _rootStore; + nextCursor: string | undefined = undefined; + prevCursor: string | undefined = undefined; + + issueCount: number | undefined = undefined; + pageCount: number | undefined = undefined; + + paginationOptions: IssuePaginationOptions | undefined = undefined; + + isArchived: boolean; + + // services + issueService; + issueArchiveService; + issueDraftService; + // root store + rootIssueStore; + issueFilterStore; + + constructor(_rootStore: IIssueRootStore, issueFilterStore: IBaseIssueFilterStore, isArchived = false) { + makeObservable(this, { + // observable + loader: observable.ref, + groupedIssueCount: observable, + issues: observable, + + nextCursor: observable.ref, + prevCursor: observable.ref, + issueCount: observable.ref, + pageCount: observable.ref, + + paginationOptions: observable, + // computed + groupedIssueIds: computed, + // action + storePreviousPaginationValues: action.bound, + + onfetchIssues: action.bound, + onfetchNexIssues: action.bound, + clear: action.bound, + + createIssue: action, + updateIssue: action, + removeIssue: action, + archiveIssue: action, + quickAddIssue: action, + removeBulkIssues: action, + }); + this.rootIssueStore = _rootStore; + this.issueFilterStore = issueFilterStore; + + this.isArchived = isArchived; + + this.issueService = new IssueService(); + this.issueArchiveService = new IssueArchiveService(); + this.issueDraftService = new IssueDraftService(); + } + + storePreviousPaginationValues = (issuesResponse: TIssuesResponse, options?: IssuePaginationOptions) => { + if (options) this.paginationOptions = options; + + this.nextCursor = issuesResponse.next_cursor; + this.prevCursor = issuesResponse.prev_cursor; + + this.issueCount = issuesResponse.count; + this.pageCount = issuesResponse.total_pages; + }; + + get groupedIssueIds() { + const displayFilters = this.issueFilterStore?.issueFilters?.displayFilters; + if (!displayFilters) return undefined; + + const subGroupBy = displayFilters?.sub_group_by; + const groupBy = displayFilters?.group_by; + const orderBy = displayFilters?.order_by; + const layout = displayFilters?.layout; + + if (!this.issues) return; + + const _issues = this.rootIssueStore.issues.getIssuesByIds( + this.issues, + this.isArchived ? "archived" : "un-archived" + ); + if (!_issues) return {}; + + let groupedIssues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues = {}; + + if (layout === "list" && orderBy) { + if (groupBy) groupedIssues = this.groupedIssues(groupBy, orderBy, _issues, this.groupedIssueCount); + else groupedIssues = this.unGroupedIssues(orderBy, _issues, this.issueCount); + } else if (layout === "kanban" && groupBy && orderBy) { + if (subGroupBy) + groupedIssues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues, this.groupedIssueCount); + else groupedIssues = this.groupedIssues(groupBy, orderBy, _issues, this.groupedIssueCount); + } else if (layout === "calendar") + groupedIssues = this.groupedIssues("target_date", "target_date", _issues, this.groupedIssueCount, true); + else if (layout === "spreadsheet") + groupedIssues = this.unGroupedIssues(orderBy ?? "-created_at", _issues, this.issueCount); + else if (layout === "gantt_chart") + groupedIssues = this.unGroupedIssues(orderBy ?? "sort_order", _issues, this.issueCount); + + return groupedIssues; + } + + onfetchIssues(issuesResponse: TIssuesResponse, options: IssuePaginationOptions) { + const { issueList, groupedIssueCount } = this.processIssueResponse(issuesResponse); + + runInAction(() => { + this.issues = issueList.map((issue) => issue.id); + this.groupedIssueCount = groupedIssueCount; + this.loader = undefined; + }); + + this.rootIssueStore.issues.addIssue(issueList); + + this.storePreviousPaginationValues(issuesResponse, options); + } + + onfetchNexIssues(issuesResponse: TIssuesResponse) { + const { issueList, groupedIssueCount } = this.processIssueResponse(issuesResponse); + const newIssueIds = issueList.map((issue) => issue.id); + + runInAction(() => { + update(this, "issues", (issueIds: string[] = []) => { + return uniq(concat(issueIds, newIssueIds)); + }); + + this.groupedIssueCount = groupedIssueCount; + this.loader = undefined; + }); + + this.rootIssueStore.issues.addIssue(issueList); + + this.storePreviousPaginationValues(issuesResponse); + } + + async createIssue( + workspaceSlug: string, + projectId: string, + data: Partial, + id?: string, + shouldAddStore = true + ) { + try { + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); + + if (shouldAddStore) this.addIssue(response); + + return response; + } catch (error) { + throw error; + } + } + + async updateIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial) { + const issueBeforeUpdate = { ...this.rootIssueStore.issues.getIssueById(issueId) }; + try { + this.rootIssueStore.issues.updateIssue(issueId, data); + + await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); + } catch (error) { + this.rootIssueStore.issues.updateIssue(issueId, issueBeforeUpdate); + throw error; + } + } + + async createDraftIssue(workspaceSlug: string, projectId: string, data: Partial) { + try { + const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data); + + this.addIssue(response); + + return response; + } catch (error) { + throw error; + } + } + + async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial) { + const issueBeforeUpdate = { ...this.rootIssueStore.issues.getIssueById(issueId) }; + try { + this.rootIssueStore.issues.updateIssue(issueId, data); + + await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data); + } catch (error) { + this.rootIssueStore.issues.updateIssue(issueId, issueBeforeUpdate); + throw error; + } + } + + async removeIssue(workspaceSlug: string, projectId: string, issueId: string) { + try { + await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); + + runInAction(() => { + if (this.issues) pull(this.issues, issueId); + }); + + this.rootIssueStore.issues.removeIssue(issueId); + } catch (error) { + throw error; + } + } + + async archiveIssue(workspaceSlug: string, projectId: string, issueId: string) { + try { + const response = await this.issueArchiveService.archiveIssue(workspaceSlug, projectId, issueId); + + runInAction(() => { + this.rootIssueStore.issues.updateIssue(issueId, { + archived_at: response.archived_at, + }); + if (this.issues) pull(this.issues, issueId); + }); + } catch (error) { + throw error; + } + } + + async quickAddIssue(workspaceSlug: string, projectId: string, data: TIssue) { + if (!this.issues) this.issues = []; + try { + this.addIssue(data); + + const response = await this.createIssue(workspaceSlug, projectId, data); + return response; + } catch (error) { + throw error; + } finally { + if (!this.issues) return; + const quickAddIssueIndex = this.issues.findIndex((_issueId) => _issueId === data.id); + if (quickAddIssueIndex >= 0) + runInAction(() => { + this.issues!.splice(quickAddIssueIndex, 1); + this.rootIssueStore.issues.removeIssue(data.id); + }); + } + } + + async removeBulkIssues(workspaceSlug: string, projectId: string, issueIds: string[]) { + try { + if (!this.issues) return; + + const response = await this.issueService.bulkDeleteIssues(workspaceSlug, projectId, { issue_ids: issueIds }); + + runInAction(() => { + issueIds.forEach((issueId) => { + pull(this.issues!, issueId); + this.rootIssueStore.issues.removeIssue(issueId); + }); + }); + return response; + } catch (error) { + throw error; + } + } + + addIssue(issue: TIssue) { + runInAction(() => { + if (!this.issues) this.issues = []; + this.issues.push(issue.id); + this.rootIssueStore.issues.addIssue([issue]); + }); + } + + clear() { + runInAction(() => { + this.issues = undefined; + this.groupedIssueCount = undefined; + this.groupedIssueCount = undefined; + + this.nextCursor = undefined; + this.prevCursor = undefined; + + this.issueCount = undefined; + this.pageCount = undefined; + + this.paginationOptions = undefined; + }); } groupedIssues = ( groupBy: TIssueDisplayFilterOptions, orderBy: TIssueOrderByOptions, issues: TIssueMap, + groupedIssueCount: Record | undefined, isCalendarIssues: boolean = false ) => { - const _issues: { [group_id: string]: string[] } = {}; - if (!groupBy) return _issues; + const _issues: TGroupedIssues = {}; + if (!groupBy || !groupedIssueCount) return _issues; this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { - _issues[group] = []; + _issues[group] = { issueIds: [], issueCount: groupedIssueCount[group] }; }); const projectIssues = this.issuesSortWithOrderBy(issues, orderBy); @@ -76,8 +391,8 @@ export class IssueHelperStore implements TIssueHelperStore { let groupArray = []; if (groupBy === "state_detail.group") { - // if groupBy state_detail.group is coming from the project level the we are using stateDetails from root store else we are looping through the stateMap - const state_group = (this.rootStore?.stateMap || {})?.[_issue?.state_id]?.group || "None"; + const state_group = + this.rootIssueStore?.stateDetails?.find((_state) => _state.id === _issue?.state_id)?.group || "None"; groupArray = [state_group]; } else { const groupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]); @@ -85,8 +400,8 @@ export class IssueHelperStore implements TIssueHelperStore { } for (const group of groupArray) { - if (group && _issues[group]) _issues[group].push(_issue.id); - else if (group) _issues[group] = [_issue.id]; + if (group && _issues[group]) _issues[group].issueIds.push(_issue.id); + else if (group) _issues[group].issueIds = [_issue.id]; } } @@ -97,15 +412,16 @@ export class IssueHelperStore implements TIssueHelperStore { subGroupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions, orderBy: TIssueOrderByOptions, - issues: TIssueMap + issues: TIssueMap, + groupedIssueCount: Record | undefined ) => { - const _issues: { [sub_group_id: string]: { [group_id: string]: string[] } } = {}; - if (!subGroupBy || !groupBy) return _issues; + const _issues: TSubGroupedIssues = {}; + if (!subGroupBy || !groupBy || !groupedIssueCount) return _issues; this.issueDisplayFiltersDefaultData(subGroupBy).forEach((sub_group: any) => { - const groupByIssues: { [group_id: string]: string[] } = {}; + const groupByIssues: TGroupedIssues = {}; this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { - groupByIssues[group] = []; + groupByIssues[group] = { issueIds: [], issueCount: groupedIssueCount[group] }; }); _issues[sub_group] = groupByIssues; }); @@ -117,8 +433,8 @@ export class IssueHelperStore implements TIssueHelperStore { let subGroupArray = []; let groupArray = []; if (subGroupBy === "state_detail.group" || groupBy === "state_detail.group") { - const state_group = (this.rootStore?.stateMap || {})?.[_issue?.state_id]?.group || "None"; - + const state_group = + this.rootIssueStore?.stateDetails?.find((_state) => _state.id === _issue?.state_id)?.group || "None"; subGroupArray = [state_group]; groupArray = [state_group]; } else { @@ -130,9 +446,10 @@ export class IssueHelperStore implements TIssueHelperStore { for (const subGroup of subGroupArray) { for (const group of groupArray) { - if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].push(_issue.id); - else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group] = [_issue.id]; - else if (subGroup && group) _issues[subGroup] = { [group]: [_issue.id] }; + if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].issueIds.push(_issue.id); + else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group].issueIds = [_issue.id]; + else if (subGroup && group) + _issues[subGroup] = { [group]: { issueIds: [_issue.id], issueCount: groupedIssueCount[group] } }; } } } @@ -140,29 +457,28 @@ export class IssueHelperStore implements TIssueHelperStore { return _issues; }; - unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: TIssueMap) => - this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id); + unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: TIssueMap, count: number | undefined) => { + const issueIds = this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id); + + return { "All Issues": { issueIds, issueCount: count || issueIds.length } }; + }; issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => { switch (groupBy) { case "state": - return Object.keys(this.rootStore?.stateMap || {}); + return Object.keys(this.rootIssueStore?.stateMap || {}); case "state_detail.group": return Object.keys(STATE_GROUPS); case "priority": return ISSUE_PRIORITIES.map((i) => i.key); case "labels": - return Object.keys(this.rootStore?.labelMap || {}); + return Object.keys(this.rootIssueStore?.labelMap || {}); case "created_by": - return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {}); + return Object.keys(this.rootIssueStore?.workSpaceMemberRolesMap || {}); case "assignees": - return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {}); + return Object.keys(this.rootIssueStore?.workSpaceMemberRolesMap || {}); case "project": - return Object.keys(this.rootStore?.projectMap || {}); - case "cycle": - return Object.keys(this.rootStore?.cycleMap || {}); - case "module": - return Object.keys(this.rootStore?.moduleMap || {}); + return Object.keys(this.rootIssueStore?.projectMap || {}); default: return []; } @@ -188,7 +504,7 @@ export class IssueHelperStore implements TIssueHelperStore { switch (dataType) { case "state_id": - const stateMap = this.rootStore?.stateMap; + const stateMap = this.rootIssueStore?.stateMap; if (!stateMap) break; for (const dataId of dataIdsArray) { const state = stateMap[dataId]; @@ -196,7 +512,7 @@ export class IssueHelperStore implements TIssueHelperStore { } break; case "label_ids": - const labelMap = this.rootStore?.labelMap; + const labelMap = this.rootIssueStore?.labelMap; if (!labelMap) break; for (const dataId of dataIdsArray) { const label = labelMap[dataId]; @@ -204,7 +520,7 @@ export class IssueHelperStore implements TIssueHelperStore { } break; case "assignee_ids": - const memberMap = this.rootStore?.memberMap; + const memberMap = this.rootIssueStore?.memberMap; if (!memberMap) break; for (const dataId of dataIdsArray) { const member = memberMap[dataId]; @@ -212,7 +528,7 @@ export class IssueHelperStore implements TIssueHelperStore { } break; case "module_ids": - const moduleMap = this.rootStore?.moduleMap; + const moduleMap = this.rootIssueStore?.moduleMap; if (!moduleMap) break; for (const dataId of dataIdsArray) { const _module = moduleMap[dataId]; @@ -220,7 +536,7 @@ export class IssueHelperStore implements TIssueHelperStore { } break; case "cycle_id": - const cycleMap = this.rootStore?.cycleMap; + const cycleMap = this.rootIssueStore?.cycleMap; if (!cycleMap) break; for (const dataId of dataIdsArray) { const cycle = cycleMap[dataId]; @@ -233,10 +549,10 @@ export class IssueHelperStore implements TIssueHelperStore { } /** - * This Method is mainly used to filter out empty values in the beginning + * This Method is mainly used to filter out empty values in the begining * @param key key of the value that is to be checked if empty * @param object any object in which the key's value is to be checked - * @returns 1 if empty, 0 if not empty + * @returns 1 if emoty, 0 if not empty */ getSortOrderToFilterEmptyValues(key: string, object: any) { const value = object?.[key]; @@ -248,7 +564,7 @@ export class IssueHelperStore implements TIssueHelperStore { issuesSortWithOrderBy = (issueObject: TIssueMap, key: Partial): TIssue[] => { let array = values(issueObject); - array = orderBy(array, "created_at"); + array = orderBy(array, "created_at", ["asc"]); switch (key) { case "sort_order": @@ -395,4 +711,38 @@ export class IssueHelperStore implements TIssueHelperStore { else if (isDate) return [renderFormattedPayloadDate(value) || "None"]; else return [value || "None"]; } + + processIssueResponse(issueResponse: TIssuesResponse): { + issueList: TIssue[]; + groupedIssueCount: Record; + } { + const issueResult = issueResponse?.results; + + if (!issueResult) + return { + issueList: [], + groupedIssueCount: {}, + }; + + if (Array.isArray(issueResult)) { + return { + issueList: issueResult, + groupedIssueCount: { "All Issues": issueResponse.count }, + }; + } + + const issueList: TIssue[] = []; + const groupedIssueCount: Record = {}; + + for (const groupId in issueResult) { + const groupIssueResult = issueResult[groupId]; + + if (!groupIssueResult) continue; + + issueList.push(...groupIssueResult.results); + groupedIssueCount[groupId] = groupIssueResult.total_results; + } + + return { issueList, groupedIssueCount }; + } } diff --git a/web/store/issue/helpers/issue-filter-helper.store.ts b/web/store/issue/helpers/issue-filter-helper.store.ts index 2921e9ca8..41222eeed 100644 --- a/web/store/issue/helpers/issue-filter-helper.store.ts +++ b/web/store/issue/helpers/issue-filter-helper.store.ts @@ -1,9 +1,5 @@ import isEmpty from "lodash/isEmpty"; // types -// constants -import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; -// lib -import { storage } from "lib/local-storage"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, @@ -14,6 +10,10 @@ import { TIssueParams, TStaticViewTypes, } from "@plane/types"; +// constants +import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue"; +// lib +import { storage } from "lib/local-storage"; interface ILocalStoreIssueFilters { key: EIssuesStoreType; @@ -23,6 +23,14 @@ interface ILocalStoreIssueFilters { filters: IIssueFilters; } +export interface IBaseIssueFilterStore { + // observables + filters: Record; + //computed + appliedFilters: Partial> | undefined; + issueFilters: IIssueFilters | undefined; +} + export interface IIssueFilterHelperStore { computedIssueFilters(filters: IIssueFilters): IIssueFilters; computedFilteredParams( @@ -78,9 +86,11 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore { module: filters?.module || undefined, start_date: filters?.start_date || undefined, target_date: filters?.target_date || undefined, - project: filters.project || undefined, - subscriber: filters.subscriber || undefined, + project: filters?.project || undefined, + subscriber: filters?.subscriber || undefined, // display filters + group_by: displayFilters?.group_by ? IssueGroupByOptions[displayFilters.group_by] : undefined, + order_by: displayFilters?.order_by || undefined, type: displayFilters?.type || undefined, sub_issue: displayFilters?.sub_issue ?? true, }; diff --git a/web/store/issue/module/filter.store.ts b/web/store/issue/module/filter.store.ts index 3f3dcb2ba..a3359d16e 100644 --- a/web/store/issue/module/filter.store.ts +++ b/web/store/issue/module/filter.store.ts @@ -14,20 +14,22 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface IModuleIssuesFilter { - // observables - filters: Record; // Record defines moduleId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface IModuleIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise; updateFilters: ( @@ -95,6 +97,22 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => { try { const _filters = await this.issueFilterService.fetchModuleIssueFilters(workspaceSlug, projectId, moduleId); @@ -160,7 +178,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul }); const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.moduleIssues.fetchIssues( + this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, isEmpty(filteredFilters) ? "init-loader" : "mutation", @@ -204,7 +222,12 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); + this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination( + workspaceSlug, + projectId, + "mutation", + moduleId + ); await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/module/issue.store.ts b/web/store/issue/module/issue.store.ts index caa5331dc..156fe486b 100644 --- a/web/store/issue/module/issue.store.ts +++ b/web/store/issue/module/issue.store.ts @@ -1,53 +1,41 @@ import concat from "lodash/concat"; import pull from "lodash/pull"; -import set from "lodash/set"; import uniq from "lodash/uniq"; import update from "lodash/update"; import { action, observable, makeObservable, computed, runInAction } from "mobx"; // base class +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; // services -import { IssueService } from "services/issue"; import { ModuleService } from "services/module.service"; // types -import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; import { IIssueRootStore } from "../root.store"; +import { IModuleIssuesFilter } from "./filter.store"; -export interface IModuleIssues { - // observable - loader: TLoader; - issues: { [module_id: string]: string[] }; +export interface IModuleIssues extends IBaseIssuesStore { viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions fetchIssues: ( workspaceSlug: string, projectId: string, loadType: TLoader, + options: IssuePaginationOptions, moduleId: string - ) => Promise; - createIssue: ( + ) => Promise; + fetchIssuesWithExistingPagination: ( workspaceSlug: string, projectId: string, - data: Partial, + loadType: TLoader, moduleId: string - ) => Promise; - updateIssue: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - moduleId: string - ) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise; - archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise; - quickAddIssue: ( - workspaceSlug: string, - projectId: string, - data: TIssue, - moduleId?: string | undefined - ) => Promise; + ) => Promise; + fetchNextIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise; + + createIssue: (workspaceSlug: string, projectId: string, data: Partial, moduleId: string) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; + removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise; + addIssuesToModule: ( workspaceSlug: string, projectId: string, @@ -71,193 +59,99 @@ export interface IModuleIssues { removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise; } -export class ModuleIssues extends IssueHelperStore implements IModuleIssues { - loader: TLoader = "init-loader"; - issues: { [module_id: string]: string[] } = {}; +export class ModuleIssues extends BaseIssuesStore implements IModuleIssues { + moduleId: string | undefined = undefined; viewFlags = { enableQuickAdd: true, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; // service moduleService; - issueService; + // filter store + issueFilterStore: IModuleIssuesFilter; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IModuleIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, + moduleId: observable.ref, // action fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, - quickAddIssue: action, + addIssuesToModule: action, removeIssuesFromModule: action, addModulesToIssue: action, removeModulesFromIssue: action, removeIssueFromModule: action, }); - - this.rootIssueStore = _rootStore; - this.issueService = new IssueService(); + // filter store + this.issueFilterStore = issueFilterStore; + // service this.moduleService = new ModuleService(); } - get groupedIssueIds() { - const moduleId = this.rootIssueStore?.moduleId; - if (!moduleId) return undefined; - - const displayFilters = this.rootIssueStore?.moduleIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const moduleIssueIds = this.issues[moduleId]; - if (!moduleIssueIds) return; - - const _issues = this.rootIssueStore.issues.getIssuesByIds(moduleIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues = []; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } else if (layout === "calendar") issues = this.groupedIssues("target_date", "target_date", _issues, true); - else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", _issues); - - return issues; - } - fetchIssues = async ( workspaceSlug: string, projectId: string, - loadType: TLoader = "init-loader", + loadType: TLoader, + options: IssuePaginationOptions, moduleId: string ) => { try { - this.loader = loadType; - - const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters; - const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params); - this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - runInAction(() => { - set( - this.issues, - [moduleId], - response.map((issue) => issue.id) - ); - this.loader = undefined; + this.loader = loadType; }); + this.clear(); - this.rootIssueStore.issues.addIssue(response); + this.moduleId = moduleId; + const params = this.issueFilterStore?.getFilterParams(options); + const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params); + + this.onfetchIssues(response, options); return response; } catch (error) { - console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial, moduleId: string) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string, moduleId: string) => { + if (!this.paginationOptions) return; try { - const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); - await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id], false); - this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); + this.loader = "pagination"; + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params); + + this.onfetchNexIssues(response); return response; } catch (error) { + this.loader = undefined; throw error; } }; - updateIssue = async ( + fetchIssuesWithExistingPagination = async ( workspaceSlug: string, projectId: string, - issueId: string, - data: Partial, + loadType: TLoader, moduleId: string ) => { - try { - await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); - this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); - throw error; - } + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, moduleId); }; - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => { + override createIssue = async (workspaceSlug: string, projectId: string, data: Partial, moduleId: string) => { try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + const response = await super.createIssue(workspaceSlug, projectId, data, moduleId, false); + await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id], false); + this.moduleId === moduleId && this.addIssue(response); + this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId); - if (issueIndex >= 0) - runInAction(() => { - this.issues[moduleId].splice(issueIndex, 1); - }); - } catch (error) { - throw error; - } - }; - - archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => { - try { - await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); - this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - - runInAction(() => { - pull(this.issues[moduleId], issueId); - }); - } catch (error) { - throw error; - } - }; - - quickAddIssue = async ( - workspaceSlug: string, - projectId: string, - data: TIssue, - moduleId: string | undefined = undefined - ) => { - try { - if (!moduleId) throw new Error("Module Id is required"); - - runInAction(() => { - this.issues[moduleId].push(data.id); - this.rootIssueStore.issues.addIssue([data]); - }); - - const response = await this.createIssue(workspaceSlug, projectId, data, moduleId); - this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - - const quickAddIssueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === data.id); - if (quickAddIssueIndex >= 0) - runInAction(() => { - this.issues[moduleId].splice(quickAddIssueIndex, 1); - this.rootIssueStore.issues.removeIssue(data.id); - }); - return response; } catch (error) { throw error; @@ -279,18 +173,20 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds); runInAction(() => { - update(this.issues, moduleId, (moduleIssueIds = []) => { - if (!moduleIssueIds) return [...issueIds]; - else return uniq(concat(moduleIssueIds, issueIds)); - }); + this.moduleId === moduleId && + update(this, "issues", (moduleIssueIds = []) => { + if (!moduleIssueIds) return [...issueIds]; + else return uniq(concat(moduleIssueIds, issueIds)); + }); }); issueIds.forEach((issueId) => { - update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { + update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { if (issueModuleIds.includes(moduleId)) return issueModuleIds; else return uniq(concat(issueModuleIds, [moduleId])); }); }); + this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); } catch (error) { throw error; @@ -299,27 +195,29 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { removeIssuesFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { try { - runInAction(() => { - issueIds.forEach((issueId) => { - pull(this.issues[moduleId], issueId); - }); - }); - - runInAction(() => { - issueIds.forEach((issueId) => { - update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { - if (issueModuleIds.includes(moduleId)) return pull(issueModuleIds, moduleId); - else return uniq(concat(issueModuleIds, [moduleId])); - }); - }); - }); - const response = await this.moduleService.removeIssuesFromModuleBulk( workspaceSlug, projectId, moduleId, issueIds ); + + runInAction(() => { + this.moduleId === moduleId && + issueIds.forEach((issueId) => { + this.issues && pull(this.issues, issueId); + }); + }); + + runInAction(() => { + issueIds.forEach((issueId) => { + update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => { + if (issueModuleIds.includes(moduleId)) return pull(issueModuleIds, moduleId); + else return uniq(concat(issueModuleIds, [moduleId])); + }); + }); + }); + this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); return response; @@ -336,12 +234,13 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { runInAction(() => { moduleIds.forEach((moduleId) => { - update(this.issues, moduleId, (moduleIssueIds = []) => { - if (moduleIssueIds.includes(issueId)) return moduleIssueIds; - else return uniq(concat(moduleIssueIds, [issueId])); - }); + this.moduleId === moduleId && + update(this, "issues", (moduleIssueIds = []) => { + if (moduleIssueIds.includes(issueId)) return moduleIssueIds; + else return uniq(concat(moduleIssueIds, [issueId])); + }); }); - update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => uniq(concat(issueModuleIds, moduleIds)) ); }); @@ -356,11 +255,12 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { try { runInAction(() => { moduleIds.forEach((moduleId) => { - update(this.issues, moduleId, (moduleIssueIds = []) => { - if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId); - else return uniq(concat(moduleIssueIds, [issueId])); - }); - update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + this.moduleId === moduleId && + update(this, "issues", (moduleIssueIds = []) => { + if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId); + else return uniq(concat(moduleIssueIds, [issueId])); + }); + update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => pull(issueModuleIds, moduleId) ); }); @@ -382,8 +282,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => { try { runInAction(() => { - pull(this.issues[moduleId], issueId); - update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => + this.issues && this.moduleId === this.moduleId && pull(this.issues, issueId); + update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => pull(issueModuleIds, moduleId) ); }); diff --git a/web/store/issue/profile/filter.store.ts b/web/store/issue/profile/filter.store.ts index f25f3d9b6..11b1dbd9d 100644 --- a/web/store/issue/profile/filter.store.ts +++ b/web/store/issue/profile/filter.store.ts @@ -14,21 +14,24 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface IProfileIssuesFilter { +export interface IProfileIssuesFilter extends IBaseIssueFilterStore { // observables userId: string; - filters: Record; // Record defines userId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, userId: string) => Promise; updateFilters: ( @@ -96,6 +99,22 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, userId: string) => { try { this.userId = userId; @@ -150,12 +169,10 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.profileIssues.fetchIssues( + this.rootIssueStore.profileIssues.fetchIssuesWithExistingPagination( workspaceSlug, - undefined, - isEmpty(filteredFilters) ? "init-loader" : "mutation", userId, - this.rootIssueStore.profileIssues.currentView + isEmpty(filteredFilters) ? "init-loader" : "mutation" ); this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, { @@ -196,13 +213,7 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.profileIssues.fetchIssues( - workspaceSlug, - undefined, - "mutation", - userId, - this.rootIssueStore.profileIssues.currentView - ); + this.rootIssueStore.profileIssues.fetchIssuesWithExistingPagination(workspaceSlug, userId, "mutation"); this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/profile/issue.store.ts b/web/store/issue/profile/issue.store.ts index 03e4e76f8..7d4ba0f21 100644 --- a/web/store/issue/profile/issue.store.ts +++ b/web/store/issue/profile/issue.store.ts @@ -1,123 +1,63 @@ -import pull from "lodash/pull"; -import set from "lodash/set"; import { action, observable, makeObservable, computed, runInAction } from "mobx"; // base class import { UserService } from "services/user.service"; -import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; // services // types import { IIssueRootStore } from "../root.store"; +import { IProfileIssuesFilter } from "./filter.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; -interface IProfileIssueTabTypes { - [key: string]: string[]; -} - -export interface IProfileIssues { +export interface IProfileIssues extends IBaseIssuesStore { // observable - loader: TLoader; currentView: "assigned" | "created" | "subscribed"; - issues: { [userId: string]: IProfileIssueTabTypes }; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; viewFlags: ViewFlags; // actions setViewId: (viewId: "assigned" | "created" | "subscribed") => void; + // action fetchIssues: ( workspaceSlug: string, - projectId: string | undefined, - loadType: TLoader, userId: string, - view?: "assigned" | "created" | "subscribed" - ) => Promise; - createIssue: ( + loadType: TLoader, + option: IssuePaginationOptions, + view: "assigned" | "created" | "subscribed" + ) => Promise; + fetchIssuesWithExistingPagination: ( workspaceSlug: string, - projectId: string, - data: Partial, - userId: string - ) => Promise; - updateIssue: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - userId: string - ) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise; - archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise; - quickAddIssue: undefined; + userId: string, + loadType: TLoader + ) => Promise; + fetchNextIssues: (workspaceSlug: string, userId: string) => Promise; + + createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; } -export class ProfileIssues extends IssueHelperStore implements IProfileIssues { - loader: TLoader = "init-loader"; +export class ProfileIssues extends BaseIssuesStore implements IProfileIssues { currentView: "assigned" | "created" | "subscribed" = "assigned"; - issues: { [userId: string]: IProfileIssueTabTypes } = {}; - quickAddIssue = undefined; - // root store - rootIssueStore: IIssueRootStore; + // filter store + issueFilterStore: IProfileIssuesFilter; // services userService; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IProfileIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { // observable - loader: observable.ref, currentView: observable.ref, - issues: observable, // computed - groupedIssueIds: computed, viewFlags: computed, // action setViewId: action.bound, fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, }); - // root store - this.rootIssueStore = _rootStore; + // filter store + this.issueFilterStore = issueFilterStore; // services this.userService = new UserService(); } - get groupedIssueIds() { - const userId = this.rootIssueStore.userId; - const workspaceSlug = this.rootIssueStore.workspaceSlug; - const currentView = this.currentView; - if (!userId || !currentView || !workspaceSlug) return undefined; - - const uniqueViewId = `${workspaceSlug}_${currentView}`; - - const displayFilters = this.rootIssueStore?.profileIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const userIssueIds = this.issues[userId]?.[uniqueViewId]; - - if (!userIssueIds) return; - - const _issues = this.rootStore.issues.getIssuesByIds(userIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } - - return issues; - } - get viewFlags() { if (this.currentView === "subscribed") return { @@ -138,20 +78,20 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { fetchIssues = async ( workspaceSlug: string, - projectId: string | undefined, - loadType: TLoader = "init-loader", userId: string, - view?: "assigned" | "created" | "subscribed" + loadType: TLoader, + options: IssuePaginationOptions, + view: "assigned" | "created" | "subscribed" ) => { try { - this.loader = loadType; - if (view) this.currentView = view; + runInAction(() => { + this.loader = loadType; + }); + this.clear(); - if (!this.currentView) throw new Error("current tab view is required"); + this.setViewId(view); - const uniqueViewId = `${workspaceSlug}_${view}`; - - let params: any = this.rootIssueStore?.profileIssuesFilter?.appliedFilters; + let params = this.issueFilterStore?.getFilterParams(options); params = { ...params, assignees: undefined, @@ -164,17 +104,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); - runInAction(() => { - set( - this.issues, - [userId, uniqueViewId], - response.map((issue) => issue.id) - ); - this.loader = undefined; - }); - - this.rootIssueStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { this.loader = undefined; @@ -182,73 +112,34 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial, userId: string) => { + fetchNextIssues = async (workspaceSlug: string, userId: string) => { + if (!this.paginationOptions || !this.currentView) return; try { - const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); + this.loader = "pagination"; - const uniqueViewId = `${workspaceSlug}_${this.currentView}`; + let params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor); + params = { + ...params, + assignees: undefined, + created_by: undefined, + subscriber: undefined, + }; + if (this.currentView === "assigned") params = { ...params, assignees: userId }; + else if (this.currentView === "created") params = { ...params, created_by: userId }; + else if (this.currentView === "subscribed") params = { ...params, subscriber: userId }; - runInAction(() => { - this.issues[userId][uniqueViewId].push(response.id); - }); - - this.rootStore.issues.addIssue([response]); + const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); + this.onfetchNexIssues(response); return response; } catch (error) { + this.loader = undefined; throw error; } }; - updateIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - userId: string - ) => { - try { - this.rootStore.issues.updateIssue(issueId, data); - await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, data.id as keyof TIssue, data); - } catch (error) { - if (this.currentView) this.fetchIssues(workspaceSlug, undefined, "mutation", userId, this.currentView); - throw error; - } - }; - - removeIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - userId: string | undefined = undefined - ) => { - if (!userId) return; - try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); - - const uniqueViewId = `${workspaceSlug}_${this.currentView}`; - - const issueIndex = this.issues[userId][uniqueViewId].findIndex((_issueId) => _issueId === issueId); - if (issueIndex >= 0) - runInAction(() => { - this.issues[userId][uniqueViewId].splice(issueIndex, 1); - }); - } catch (error) { - throw error; - } - }; - - archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, userId: string) => { - try { - await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); - - const uniqueViewId = `${workspaceSlug}_${this.currentView}`; - - runInAction(() => { - pull(this.issues[userId][uniqueViewId], issueId); - }); - } catch (error) { - throw error; - } + fetchIssuesWithExistingPagination = async (workspaceSlug: string, userId: string, loadType: TLoader) => { + if (!this.paginationOptions || !this.currentView) return; + return await this.fetchIssues(workspaceSlug, userId, loadType, this.paginationOptions, this.currentView); }; } diff --git a/web/store/issue/project-views/filter.store.ts b/web/store/issue/project-views/filter.store.ts index 050d3dce4..9cfffde1e 100644 --- a/web/store/issue/project-views/filter.store.ts +++ b/web/store/issue/project-views/filter.store.ts @@ -14,20 +14,22 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services -export interface IProjectViewIssuesFilter { - // observables - filters: Record; // Record defines viewId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface IProjectViewIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise; updateFilters: ( @@ -93,6 +95,22 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string, viewId: string) => { try { const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, projectId, viewId); @@ -159,11 +177,10 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.projectViewIssues.fetchIssues( + this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, - isEmpty(filteredFilters) ? "init-loader" : "mutation", - viewId + isEmpty(filteredFilters) ? "init-loader" : "mutation" ); break; case EIssueFilterType.DISPLAY_FILTERS: @@ -200,7 +217,11 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, "mutation", viewId); + this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination( + workspaceSlug, + projectId, + "mutation" + ); await this.issueFilterService.patchView(workspaceSlug, projectId, viewId, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/project-views/issue.store.ts b/web/store/issue/project-views/issue.store.ts index 97cf537c7..701d0c3d4 100644 --- a/web/store/issue/project-views/issue.store.ts +++ b/web/store/issue/project-views/issue.store.ts @@ -1,230 +1,94 @@ -import pull from "lodash/pull"; -import set from "lodash/set"; -import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { action, makeObservable, runInAction } from "mobx"; // base class -import { IssueService } from "services/issue/issue.service"; -import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types"; // services // types import { IIssueRootStore } from "../root.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; +import { IProjectViewIssuesFilter } from "./filter.store"; -export interface IProjectViewIssues { - // observable - loader: TLoader; - issues: { [view_id: string]: string[] }; +export interface IProjectViewIssues extends IBaseIssuesStore { viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions fetchIssues: ( workspaceSlug: string, projectId: string, loadType: TLoader, - viewId: string - ) => Promise; - createIssue: ( + options: IssuePaginationOptions + ) => Promise; + fetchIssuesWithExistingPagination: ( workspaceSlug: string, projectId: string, - data: Partial, - viewId: string - ) => Promise; - updateIssue: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - viewId: string - ) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise; - archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise; - quickAddIssue: ( - workspaceSlug: string, - projectId: string, - data: TIssue, - viewId?: string | undefined - ) => Promise; + loadType: TLoader + ) => Promise; + fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise; + + createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; + removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise; } -export class ProjectViewIssues extends IssueHelperStore implements IProjectViewIssues { - loader: TLoader = "init-loader"; - issues: { [view_id: string]: string[] } = {}; +export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIssues { viewFlags = { enableQuickAdd: true, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; - // services - issueService; + //filter store + issueFilterStore: IProjectViewIssuesFilter; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IProjectViewIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { - // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, // action fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, - quickAddIssue: action, }); - // root store - this.rootIssueStore = _rootStore; - // services - this.issueService = new IssueService(); + //filter store + this.issueFilterStore = issueFilterStore; } - get groupedIssueIds() { - const viewId = this.rootStore?.viewId; - if (!viewId) return undefined; - - const displayFilters = this.rootIssueStore?.projectViewIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const viewIssueIds = this.issues[viewId]; - if (!viewIssueIds) return; - - const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues = []; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } else if (layout === "calendar") issues = this.groupedIssues("target_date", "target_date", _issues, true); - else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", _issues); - - return issues; - } - - fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader", viewId: string) => { + fetchIssues = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + options: IssuePaginationOptions + ) => { try { - this.loader = loadType; - - const params = this.rootIssueStore?.projectViewIssuesFilter?.appliedFilters; + runInAction(() => { + this.loader = loadType; + }); + this.clear(); + const params = this.issueFilterStore?.getFilterParams(options); const response = await this.issueService.getIssues(workspaceSlug, projectId, params); - runInAction(() => { - set( - this.issues, - [viewId], - response.map((issue) => issue.id) - ); - this.loader = undefined; - }); - - this.rootIssueStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { - console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial, viewId: string) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string) => { + if (!this.paginationOptions) return; try { - const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); + this.loader = "pagination"; - runInAction(() => { - this.issues[viewId].push(response.id); - }); + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); + this.onfetchNexIssues(response); return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); + this.loader = undefined; throw error; } }; - updateIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - viewId: string - ) => { - try { - await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); - throw error; - } - }; - - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => { - try { - await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); - - const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId); - if (issueIndex >= 0) - runInAction(() => { - this.issues[viewId].splice(issueIndex, 1); - }); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); - throw error; - } - }; - - archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => { - try { - await this.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - pull(this.issues[viewId], issueId); - }); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); - throw error; - } - }; - - quickAddIssue = async ( - workspaceSlug: string, - projectId: string, - data: TIssue, - viewId: string | undefined = undefined - ) => { - try { - if (!viewId) throw new Error("View Id is required"); - - runInAction(() => { - this.issues[viewId].push(data.id); - this.rootIssueStore.issues.addIssue([data]); - }); - - const response = await this.createIssue(workspaceSlug, projectId, data, viewId); - - const quickAddIssueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === data.id); - if (quickAddIssueIndex >= 0) - runInAction(() => { - this.issues[viewId].splice(quickAddIssueIndex, 1); - this.rootIssueStore.issues.removeIssue(data.id); - }); - - return response; - } catch (error) { - if (viewId) this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); - throw error; - } + fetchIssuesWithExistingPagination = async (workspaceSlug: string, projectId: string, loadType: TLoader) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions); }; } diff --git a/web/store/issue/project/filter.store.ts b/web/store/issue/project/filter.store.ts index d5c353487..78cec2c8c 100644 --- a/web/store/issue/project/filter.store.ts +++ b/web/store/issue/project/filter.store.ts @@ -4,7 +4,6 @@ import pickBy from "lodash/pickBy"; import set from "lodash/set"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; // base class -import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { IssueFiltersService } from "services/issue_filter.service"; import { @@ -14,20 +13,23 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; // constants +import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue"; +import { computedFn } from "mobx-utils"; // services -export interface IProjectIssuesFilter { - // observables - filters: Record; // Record defines projectId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; +export interface IProjectIssuesFilter extends IBaseIssueFilterStore { + //helper actions + getFilterParams: ( + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; // action fetchFilters: (workspaceSlug: string, projectId: string) => Promise; updateFilters: ( @@ -92,6 +94,22 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj return filteredRouteParams; } + getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.appliedFilters; + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + fetchFilters = async (workspaceSlug: string, projectId: string) => { try { const _filters = await this.issueFilterService.fetchProjectIssueFilters(workspaceSlug, projectId); @@ -157,7 +175,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.projectIssues.fetchIssues( + this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination( workspaceSlug, projectId, isEmpty(filteredFilters) ? "init-loader" : "mutation" @@ -200,7 +218,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.projectIssues.fetchIssues(workspaceSlug, projectId, "mutation"); + this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation"); await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, { display_filters: _filters.displayFilters, diff --git a/web/store/issue/project/issue.store.ts b/web/store/issue/project/issue.store.ts index 080b8cee6..42f9f20e4 100644 --- a/web/store/issue/project/issue.store.ts +++ b/web/store/issue/project/issue.store.ts @@ -1,123 +1,72 @@ -import concat from "lodash/concat"; -import pull from "lodash/pull"; -import set from "lodash/set"; -import update from "lodash/update"; -import { action, makeObservable, observable, runInAction, computed } from "mobx"; -// base class -import { IssueService, IssueArchiveService } from "services/issue"; -import { TIssue, TGroupedIssues, TSubGroupedIssues, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; // services // types +import { action, makeObservable, runInAction } from "mobx"; +// base class +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; +// types import { IIssueRootStore } from "../root.store"; +import { TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse, TIssue } from "@plane/types"; +import { IProjectIssuesFilter } from "./filter.store"; -export interface IProjectIssues { - // observable - loader: TLoader; - issues: Record; // Record of project_id as key and issue_ids as value +export interface IProjectIssues extends IBaseIssuesStore { viewFlags: ViewFlags; - // computed - groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // action - fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; + fetchIssues: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + option: IssuePaginationOptions + ) => Promise; + fetchIssuesWithExistingPagination: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader + ) => Promise; + fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise; + createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; + quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise; } -export class ProjectIssues extends IssueHelperStore implements IProjectIssues { - // observable - loader: TLoader = "init-loader"; - issues: Record = {}; +export class ProjectIssues extends BaseIssuesStore implements IProjectIssues { viewFlags = { enableQuickAdd: true, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; - // services - issueService; - issueArchiveService; - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + // filter store + issueFilterStore: IProjectIssuesFilter; + + constructor(_rootStore: IIssueRootStore, issueFilterStore: IProjectIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { - // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, - // action fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, - removeBulkIssues: action, - quickAddIssue: action, + fetchNextIssues: action, + fetchIssuesWithExistingPagination: action, }); - // root store - this.rootIssueStore = _rootStore; - // services - this.issueService = new IssueService(); - this.issueArchiveService = new IssueArchiveService(); + // filter store + this.issueFilterStore = issueFilterStore; } - get groupedIssueIds() { - const projectId = this.rootStore?.projectId; - if (!projectId) return undefined; - - const displayFilters = this.rootStore?.projectIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; - - const subGroupBy = displayFilters?.sub_group_by; - const groupBy = displayFilters?.group_by; - const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - - const projectIssueIds = this.issues[projectId]; - if (!projectIssueIds) return; - - const _issues = this.rootStore.issues.getIssuesByIds(projectIssueIds, "un-archived"); - if (!_issues) return []; - - let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues = []; - - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); - else issues = this.unGroupedIssues(orderBy, _issues); - } else if (layout === "kanban" && groupBy && orderBy) { - if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); - else issues = this.groupedIssues(groupBy, orderBy, _issues); - } else if (layout === "calendar") issues = this.groupedIssues("target_date", "target_date", _issues, true); - else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", _issues); - - return issues; - } - - fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => { + fetchIssues = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "init-loader", + options: IssuePaginationOptions + ) => { try { - this.loader = loadType; - - const params = this.rootStore?.projectIssuesFilter?.appliedFilters; + runInAction(() => { + this.loader = loadType; + }); + this.clear(); + const params = this.issueFilterStore?.getFilterParams(options); const response = await this.issueService.getIssues(workspaceSlug, projectId, params); - runInAction(() => { - set( - this.issues, - [projectId], - response.map((issue) => issue.id) - ); - this.loader = undefined; - }); - - this.rootStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { this.loader = undefined; @@ -125,102 +74,28 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues { } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial) => { + fetchNextIssues = async (workspaceSlug: string, projectId: string) => { + if (!this.paginationOptions) return; try { - const response = await this.issueService.createIssue(workspaceSlug, projectId, data); + this.loader = "pagination"; - runInAction(() => { - update(this.issues, [projectId], (issueIds) => { - if (!issueIds) return [response.id]; - return concat(issueIds, response.id); - }); - }); - - this.rootStore.issues.addIssue([response]); + const params = this.issueFilterStore?.getFilterParams(this.paginationOptions); + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); + this.onfetchNexIssues(response); return response; } catch (error) { + this.loader = undefined; throw error; } }; - updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { - try { - this.rootStore.issues.updateIssue(issueId, data); - - await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation"); - throw error; - } - }; - - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - pull(this.issues[projectId], issueId); - }); - - this.rootStore.issues.removeIssue(issueId); - } catch (error) { - throw error; - } - }; - - archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - const response = await this.issueArchiveService.archiveIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.rootStore.issues.updateIssue(issueId, { - archived_at: response.archived_at, - }); - pull(this.issues[projectId], issueId); - }); - } catch (error) { - throw error; - } - }; - - quickAddIssue = async (workspaceSlug: string, projectId: string, data: TIssue) => { - try { - runInAction(() => { - this.issues[projectId].push(data.id); - this.rootStore.issues.addIssue([data]); - }); - - const response = await this.createIssue(workspaceSlug, projectId, data); - - const quickAddIssueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === data.id); - if (quickAddIssueIndex >= 0) - runInAction(() => { - this.issues[projectId].splice(quickAddIssueIndex, 1); - this.rootStore.issues.removeIssue(data.id); - }); - return response; - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation"); - throw error; - } - }; - - removeBulkIssues = async (workspaceSlug: string, projectId: string, issueIds: string[]) => { - try { - runInAction(() => { - issueIds.forEach((issueId) => { - pull(this.issues[projectId], issueId); - this.rootStore.issues.removeIssue(issueId); - }); - }); - - const response = await this.issueService.bulkDeleteIssues(workspaceSlug, projectId, { issue_ids: issueIds }); - - return response; - } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation"); - throw error; - } + fetchIssuesWithExistingPagination = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "mutation" + ) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions); }; } diff --git a/web/store/issue/root.store.ts b/web/store/issue/root.store.ts index 68206a704..aff70efce 100644 --- a/web/store/issue/root.store.ts +++ b/web/store/issue/root.store.ts @@ -184,28 +184,28 @@ export class IssueRootStore implements IIssueRootStore { this.issueDetail = new IssueDetail(this); this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this); - this.workspaceIssues = new WorkspaceIssues(this); + this.workspaceIssues = new WorkspaceIssues(this, this.workspaceIssuesFilter); this.profileIssuesFilter = new ProfileIssuesFilter(this); - this.profileIssues = new ProfileIssues(this); + this.profileIssues = new ProfileIssues(this, this.profileIssuesFilter); this.projectIssuesFilter = new ProjectIssuesFilter(this); - this.projectIssues = new ProjectIssues(this); + this.projectIssues = new ProjectIssues(this, this.projectIssuesFilter); this.cycleIssuesFilter = new CycleIssuesFilter(this); - this.cycleIssues = new CycleIssues(this); + this.cycleIssues = new CycleIssues(this, this.cycleIssuesFilter); this.moduleIssuesFilter = new ModuleIssuesFilter(this); - this.moduleIssues = new ModuleIssues(this); + this.moduleIssues = new ModuleIssues(this, this.moduleIssuesFilter); this.projectViewIssuesFilter = new ProjectViewIssuesFilter(this); - this.projectViewIssues = new ProjectViewIssues(this); + this.projectViewIssues = new ProjectViewIssues(this, this.projectViewIssuesFilter); this.archivedIssuesFilter = new ArchivedIssuesFilter(this); - this.archivedIssues = new ArchivedIssues(this); + this.archivedIssues = new ArchivedIssues(this, this.archivedIssuesFilter); this.draftIssuesFilter = new DraftIssuesFilter(this); - this.draftIssues = new DraftIssues(this); + this.draftIssues = new DraftIssues(this, this.draftIssuesFilter); this.issueKanBanView = new IssueKanBanViewStore(this); this.issueCalendarView = new CalendarStore(); diff --git a/web/store/issue/workspace/filter.store.ts b/web/store/issue/workspace/filter.store.ts index 8278bdfc7..b1870dcd6 100644 --- a/web/store/issue/workspace/filter.store.ts +++ b/web/store/issue/workspace/filter.store.ts @@ -15,21 +15,19 @@ import { IIssueFilters, TIssueParams, TStaticViewTypes, + IssuePaginationOptions, } from "@plane/types"; -import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; // helpers // types import { IIssueRootStore } from "../root.store"; +import { computedFn } from "mobx-utils"; // constants // services type TWorkspaceFilters = "all-issues" | "assigned" | "created" | "subscribed" | string; -export interface IWorkspaceIssuesFilter { - // observables - filters: Record; // Record defines viewId as key and IIssueFilters as value - // computed - issueFilters: IIssueFilters | undefined; - appliedFilters: Partial> | undefined; + +export interface IWorkspaceIssuesFilter extends IBaseIssueFilterStore { // fetch action fetchFilters: (workspaceSlug: string, viewId: string) => Promise; updateFilters: ( @@ -42,6 +40,11 @@ export interface IWorkspaceIssuesFilter { //helper action getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined; getAppliedFilters: (viewId: string) => Partial> | undefined; + getFilterParams: ( + viewId: string, + options: IssuePaginationOptions, + cursor?: string + ) => Partial>; } export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter { @@ -63,9 +66,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo // fetch actions fetchFilters: action, updateFilters: action, - // helper actions - getIssueFilters: action, - getAppliedFilters: action, }); // root store this.rootIssueStore = _rootStore; @@ -102,6 +102,22 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo return filteredRouteParams; }; + getFilterParams = computedFn((viewId: string, options: IssuePaginationOptions, cursor: string | undefined) => { + const filterParams = this.getAppliedFilters(viewId); + + const paginationOptions: Partial> = { + ...filterParams, + cursor: cursor ? cursor : `${options.perPageCount}:0:0`, + per_page: options.perPageCount.toString(), + }; + + if (options.groupedBy) { + paginationOptions.group_by = options.groupedBy; + } + + return paginationOptions; + }); + get issueFilters() { const viewId = this.rootIssueStore.globalViewId; return this.getIssueFilters(viewId); @@ -182,7 +198,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo }); const appliedFilters = _filters.filters || {}; const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); - this.rootIssueStore.workspaceIssues.fetchIssues( + this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination( workspaceSlug, viewId, isEmpty(filteredFilters) ? "init-loader" : "mutation" @@ -222,7 +238,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo }); if (this.requiresServerUpdate(updatedDisplayFilters)) - this.rootIssueStore.workspaceIssues.fetchIssues(workspaceSlug, viewId, "mutation"); + this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug, viewId, "mutation"); if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, { diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts index 7d7e52e1e..d1c8917de 100644 --- a/web/store/issue/workspace/issue.store.ts +++ b/web/store/issue/workspace/issue.store.ts @@ -1,215 +1,93 @@ -import pull from "lodash/pull"; -import set from "lodash/set"; -import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { action, makeObservable, runInAction } from "mobx"; // base class -import { IssueService, IssueArchiveService } from "services/issue"; import { WorkspaceService } from "services/workspace.service"; -import { TIssue, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; -import { IssueHelperStore } from "../helpers/issue-helper.store"; +import { IssuePaginationOptions, TIssue, TIssuesResponse, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; // services // types import { IIssueRootStore } from "../root.store"; +import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"; +import { IWorkspaceIssuesFilter } from "./filter.store"; -export interface IWorkspaceIssues { +export interface IWorkspaceIssues extends IBaseIssuesStore { // observable - loader: TLoader; - issues: { [viewId: string]: string[] }; viewFlags: ViewFlags; - // computed - groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined }; // actions - fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise; - createIssue: ( + fetchIssues: ( workspaceSlug: string, - projectId: string, - data: Partial, - viewId: string - ) => Promise; - updateIssue: ( + viewId: string, + loadType: TLoader, + options: IssuePaginationOptions + ) => Promise; + fetchIssuesWithExistingPagination: ( workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - viewId: string - ) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise; - archiveIssue: ( - workspaceSlug: string, - projectId: string, - issueId: string, - viewId?: string | undefined - ) => Promise; - quickAddIssue: undefined; + viewId: string, + loadType: TLoader + ) => Promise; + fetchNextIssues: (workspaceSlug: string, viewId: string) => Promise; + createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; } -export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues { - loader: TLoader = "init-loader"; - issues: { [viewId: string]: string[] } = {}; +export class WorkspaceIssues extends BaseIssuesStore implements IWorkspaceIssues { viewFlags = { enableQuickAdd: true, enableIssueCreation: true, enableInlineEditing: true, }; - // root store - rootIssueStore: IIssueRootStore; // service workspaceService; - issueService; - issueArchiveService; + // filterStore + issueFilterStore; - quickAddIssue = undefined; - - constructor(_rootStore: IIssueRootStore) { - super(_rootStore); + constructor(_rootStore: IIssueRootStore, issueFilterStore: IWorkspaceIssuesFilter) { + super(_rootStore, issueFilterStore); makeObservable(this, { - // observable - loader: observable.ref, - issues: observable, - // computed - groupedIssueIds: computed, // action fetchIssues: action, - createIssue: action, - updateIssue: action, - removeIssue: action, - archiveIssue: action, }); - // root store - this.rootIssueStore = _rootStore; // services this.workspaceService = new WorkspaceService(); - this.issueService = new IssueService(); - this.issueArchiveService = new IssueArchiveService(); + // filter store + this.issueFilterStore = issueFilterStore; } - get groupedIssueIds() { - const viewId = this.rootIssueStore.globalViewId; - const workspaceSlug = this.rootIssueStore.workspaceSlug; - if (!workspaceSlug || !viewId) return { dataViewId: "", issueIds: undefined }; - - const uniqueViewId = `${workspaceSlug}_${viewId}`; - - const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters; - if (!displayFilters) return { dataViewId: viewId, issueIds: undefined }; - - const orderBy = displayFilters?.order_by; - - const viewIssueIds = this.issues[uniqueViewId]; - - if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined }; - - const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds, "un-archived"); - if (!_issues) return { dataViewId: viewId, issueIds: [] }; - - let issueIds: TIssue | TUnGroupedIssues | undefined = undefined; - - issueIds = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - - return { dataViewId: viewId, issueIds }; - } - - fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => { + fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader, options: IssuePaginationOptions) => { try { - this.loader = loadType; - - const uniqueViewId = `${workspaceSlug}_${viewId}`; - - const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId); + runInAction(() => { + this.loader = loadType; + }); + this.clear(); + const params = this.issueFilterStore?.getFilterParams(viewId, options, undefined); const response = await this.workspaceService.getViewIssues(workspaceSlug, params); - runInAction(() => { - set( - this.issues, - [uniqueViewId], - response.map((issue) => issue.id) - ); - this.loader = undefined; - }); - - this.rootIssueStore.issues.addIssue(response); - + this.onfetchIssues(response, options); return response; } catch (error) { - console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, projectId: string, data: Partial, viewId: string) => { + fetchNextIssues = async (workspaceSlug: string, viewId: string) => { + if (!this.paginationOptions) return; try { - const uniqueViewId = `${workspaceSlug}_${viewId}`; + this.loader = "pagination"; - const response = await this.issueService.createIssue(workspaceSlug, projectId, data); - - runInAction(() => { - this.issues[uniqueViewId].push(response.id); - }); - - this.rootStore.issues.addIssue([response]); + const params = this.issueFilterStore?.getFilterParams(viewId, this.paginationOptions, this.nextCursor); + const response = await this.workspaceService.getViewIssues(workspaceSlug, params); + this.onfetchNexIssues(response); return response; } catch (error) { + this.loader = undefined; throw error; } }; - updateIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - viewId: string - ) => { - try { - this.rootStore.issues.updateIssue(issueId, data); - await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); - } catch (error) { - if (viewId) this.fetchIssues(workspaceSlug, viewId, "mutation"); - throw error; - } - }; - - removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => { - try { - const uniqueViewId = `${workspaceSlug}_${viewId}`; - - await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); - - const issueIndex = this.issues[uniqueViewId].findIndex((_issueId) => _issueId === issueId); - if (issueIndex >= 0) - runInAction(() => { - this.issues[uniqueViewId].splice(issueIndex, 1); - }); - - this.rootStore.issues.removeIssue(issueId); - } catch (error) { - throw error; - } - }; - - archiveIssue = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - viewId: string | undefined = undefined - ) => { - try { - if (!viewId) throw new Error("View id is required"); - - const uniqueViewId = `${workspaceSlug}_${viewId}`; - - const response = await this.issueArchiveService.archiveIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.rootStore.issues.updateIssue(issueId, { - archived_at: response.archived_at, - }); - pull(this.issues[uniqueViewId], issueId); - }); - } catch (error) { - throw error; - } + fetchIssuesWithExistingPagination = async (workspaceSlug: string, viewId: string, loadType: TLoader) => { + if (!this.paginationOptions) return; + return await this.fetchIssues(workspaceSlug, viewId, loadType, this.paginationOptions); }; }