make store changes for pagination

This commit is contained in:
rahulramesha 2024-03-08 17:03:35 +05:30
parent 425b36e391
commit 026bc9318f
30 changed files with 1544 additions and 1707 deletions

View File

@ -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;

View File

@ -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<TIssue>;
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;
};

View File

@ -13,7 +13,6 @@ export type TIssueGroupByOptions =
| "state_detail.group"
| "project"
| "assignees"
| "mentions"
| "cycle"
| "module"
| null;
@ -72,7 +71,9 @@ export type TIssueParams =
| "order_by"
| "type"
| "sub_issue"
| "show_empty_groups";
| "show_empty_groups"
| "cursor"
| "per_page";
export type TCalendarLayouts = "month" | "week";
@ -82,9 +83,9 @@ export interface IIssueFilterOptions {
created_by?: string[] | null;
labels?: string[] | null;
priority?: string[] | null;
project?: string[] | null;
cycle?: string[] | null;
module?: string[] | null;
project?: string[] | null;
start_date?: string[] | null;
state?: string[] | null;
state_group?: string[] | null;
@ -191,3 +192,11 @@ export interface IWorkspaceGlobalViewProps {
display_filters: IWorkspaceIssueDisplayFilterOptions | undefined;
display_properties: IIssueDisplayProperties;
}
export interface IssuePaginationOptions {
canGroup: boolean;
perPageCount: number;
greaterThanDate?: Date;
lessThanDate?: Date;
groupedBy?: TIssueGroupByOptions;
}

View File

@ -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",
}

View File

@ -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<TIssue[] | undefined>;
fetchIssues: (
loadType: TLoader,
options: IssuePaginationOptions,
userViewId?: "assigned" | "created" | "subscribed"
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: () => Promise<TIssuesResponse | undefined>;
removeIssue: (projectId: string, issueId: string) => Promise<void>;
createIssue?: (projectId: string, data: Partial<TIssue>) => Promise<TIssue | undefined>;
updateIssue?: (projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
@ -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<TIssue>) => {
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<TIssue>) => {
if (!cycleId || !workspaceSlug) return;
@ -147,17 +165,17 @@ const useCycleIssueActions = () => {
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
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<TIssue>) => {
if (!moduleId || !workspaceSlug) return;
@ -223,17 +256,17 @@ const useModuleIssueActions = () => {
);
const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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<TIssue>) => {
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,

View File

@ -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<TIssue[]> {
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<TIssue[]> {
): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, {
params: queries,
})

View File

@ -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<any> {
async createIssue(workspaceSlug: string, projectId: string, data: Partial<TIssue>): Promise<TIssue> {
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<TIssue[]> {
async getIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
params: queries,
})

View File

@ -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<TIssue[]> {
async getDraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise<TIssuesResponse> {
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<any> {
async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise<TIssue> {
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<any> {
async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<void> {
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<any> {
async deleteDraftIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<void> {
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<any> {
async getDraftIssueById(workspaceSlug: string, projectId: string, issueId: string, queries?: any): Promise<TIssue> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, {
params: queries,
})

View File

@ -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<TIssue[]> {
async getModuleIssues(
workspaceSlug: string,
projectId: string,
moduleId: string,
queries?: any
): Promise<TIssuesResponse> {
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<any> {
): Promise<void> {
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<any> {
): Promise<void> {
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<any> {
): Promise<void> {
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;

View File

@ -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<TIssue[]> {
async getUserProfileIssues(workspaceSlug: string, userId: string, params: any): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/user-issues/${userId}/`, {
params,
})

View File

@ -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<TIssue[]> {
async getViewIssues(workspaceSlug: string, params: any): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/issues/`, {
params,
})

View File

@ -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<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IArchivedIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[]>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
option: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
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;
}

View File

@ -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<string, IIssueFilters>; // Record defines cycleId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface ICycleIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[] | undefined>;
createIssue: (
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
loadType: TLoader,
cycleId: string
) => Promise<TIssue | undefined>;
updateIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
cycleId: string
) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, cycleId: string) => Promise<void>;
quickAddIssue: (
workspaceSlug: string,
projectId: string,
data: TIssue,
cycleId?: string | undefined
) => Promise<TIssue>;
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, cycleId: string) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
addIssueToCycle: (
workspaceSlug: string,
projectId: string,
@ -66,106 +54,63 @@ export interface ICycleIssues {
new_cycle_id: string;
}
) => Promise<TIssue>;
fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<TIssue[] | undefined>;
fetchActiveCycleIssues: (
workspaceSlug: string,
projectId: string,
cycleId: string
) => Promise<TIssuesResponse | undefined>;
}
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<TIssue>, 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<TIssue>, 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<TIssue>,
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) {

View File

@ -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<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[]>;
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
option: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
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 {
runInAction(() => {
this.loader = loadType;
const params = this.rootIssueStore?.draftIssuesFilter?.appliedFilters;
});
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<TIssue>) => {
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<TIssue>) => {
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;
}

View File

@ -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<TIssueGroupByOptions, null> | "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<string, number> | undefined;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
//actions
removeIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<void>;
// helper methods
groupedIssues(
groupBy: TIssueDisplayFilterOptions,
orderBy: TIssueOrderByOptions,
issues: TIssueMap,
groupedIssueCount: Record<string, number>,
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<string, number>
): TSubGroupedIssues;
unGroupedIssues(orderBy: TIssueOrderByOptions, issues: TIssueMap, count: number): TUnGroupedIssues;
issueDisplayFiltersDefaultData(groupBy: string | null): string[];
issuesSortWithOrderBy(issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[];
getGroupArray(value: boolean | number | string | string[] | null, isDate?: boolean): string[];
};
}
const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
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<string, number> | 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<TIssue>,
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<TIssue>) {
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<TIssue>) {
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<TIssue>) {
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<string, number> | 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<string, number> | 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<TIssueOrderByOptions>): 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<string, number>;
} {
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<string, number> = {};
for (const groupId in issueResult) {
const groupIssueResult = issueResult[groupId];
if (!groupIssueResult) continue;
issueList.push(...groupIssueResult.results);
groupedIssueCount[groupId] = groupIssueResult.total_results;
}
return { issueList, groupedIssueCount };
}
}

View File

@ -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<string, IIssueFilters>;
//computed
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | 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,
};

View File

@ -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<string, IIssueFilters>; // Record defines moduleId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IModuleIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[] | undefined>;
createIssue: (
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
loadType: TLoader,
moduleId: string
) => Promise<TIssue | undefined>;
updateIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
moduleId: string
) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => Promise<void>;
quickAddIssue: (
workspaceSlug: string,
projectId: string,
data: TIssue,
moduleId?: string | undefined
) => Promise<TIssue | undefined>;
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
addIssuesToModule: (
workspaceSlug: string,
projectId: string,
@ -71,193 +59,99 @@ export interface IModuleIssues {
removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>;
}
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 {
runInAction(() => {
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.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<TIssue>, 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<TIssue>,
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<TIssue>, 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 = []) => {
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 = []) => {
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 = []) => {
this.moduleId === moduleId &&
update(this, "issues", (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
else return uniq(concat(moduleIssueIds, [issueId]));
});
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
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)
);
});

View File

@ -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<string, IIssueFilters>; // Record defines userId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, userId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[]>;
createIssue: (
loadType: TLoader,
option: IssuePaginationOptions,
view: "assigned" | "created" | "subscribed"
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
userId: string
) => Promise<TIssue | undefined>;
updateIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
userId: string
) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, userId: string) => Promise<void>;
quickAddIssue: undefined;
userId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, userId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
}
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 {
runInAction(() => {
this.loader = loadType;
if (view) this.currentView = view;
});
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<TIssue>, 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<TIssue>,
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);
};
}

View File

@ -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<string, IIssueFilters>; // Record defines viewId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IProjectViewIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<TIssue[] | undefined>;
createIssue: (
options: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
viewId: string
) => Promise<TIssue | undefined>;
updateIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
viewId: string
) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
quickAddIssue: (
workspaceSlug: string,
projectId: string,
data: TIssue,
viewId?: string | undefined
) => Promise<TIssue | undefined>;
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
}
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 {
runInAction(() => {
this.loader = loadType;
const params = this.rootIssueStore?.projectViewIssuesFilter?.appliedFilters;
});
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<TIssue>, 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<TIssue>,
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);
};
}

View File

@ -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<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IProjectIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
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<Record<TIssueParams, string | boolean>> = {
...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,

View File

@ -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<string, string[]>; // 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<TIssue[]>;
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
option: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
}
export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
// observable
loader: TLoader = "init-loader";
issues: Record<string, string[]> = {};
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 {
runInAction(() => {
this.loader = loadType;
const params = this.rootStore?.projectIssuesFilter?.appliedFilters;
});
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<TIssue>) => {
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<TIssue>) => {
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);
};
}

View File

@ -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();

View File

@ -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<TWorkspaceFilters, IIssueFilters>; // Record defines viewId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
export interface IWorkspaceIssuesFilter extends IBaseIssueFilterStore {
// fetch action
fetchFilters: (workspaceSlug: string, viewId: string) => Promise<void>;
updateFilters: (
@ -42,6 +40,11 @@ export interface IWorkspaceIssuesFilter {
//helper action
getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined;
getAppliedFilters: (viewId: string) => Partial<Record<TIssueParams, string | boolean>> | undefined;
getFilterParams: (
viewId: string,
options: IssuePaginationOptions,
cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
}
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<Record<TIssueParams, string | boolean>> = {
...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, {

View File

@ -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<TIssue[]>;
createIssue: (
fetchIssues: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue>,
viewId: string
) => Promise<TIssue | undefined>;
updateIssue: (
viewId: string,
loadType: TLoader,
options: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssue>,
viewId: string
) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>;
archiveIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
viewId?: string | undefined
) => Promise<void>;
quickAddIssue: undefined;
viewId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (workspaceSlug: string, viewId: string) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
}
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 {
runInAction(() => {
this.loader = loadType;
const uniqueViewId = `${workspaceSlug}_${viewId}`;
const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId);
});
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<TIssue>, 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<TIssue>,
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);
};
}