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 // issues
export * from "./issue"; export * from "./issue";
export * from "./issue_reaction"; export * from "./issue_reaction";
@ -7,16 +10,17 @@ export * from "./issue_relation";
export * from "./issue_sub_issues"; export * from "./issue_sub_issues";
export * from "./activity/base"; export * from "./activity/base";
export type TLoader = "init-loader" | "mutation" | undefined; export type TLoader = "init-loader" | "mutation" | "pagination" | undefined;
export type TGroupedIssues = { export type TGroupedIssues = {
[group_id: string]: string[]; [group_id: string]: { issueIds: string[]; issueCount: number };
}; };
export type TSubGroupedIssues = { export type TSubGroupedIssues = {
[sub_grouped_id: string]: { [sub_grouped_id: string]: TGroupedIssues;
[group_id: string]: string[];
}; };
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"; import { TIssueReaction } from "./issue_reaction";
// new issue structure types // new issue structure types
export type TIssue = {
export type TBaseIssue = {
id: string; id: string;
sequence_id: number; sequence_id: number;
name: string; name: string;
description_html: string;
sort_order: number; sort_order: number;
state_id: string; state_id: string | null;
priority: TIssuePriorities; priority: TIssuePriorities | null;
label_ids: string[]; label_ids: string[];
assignee_ids: string[]; assignee_ids: string[];
estimate_point: number | null; estimate_point: number | null;
@ -21,10 +21,10 @@ export type TIssue = {
attachment_count: number; attachment_count: number;
link_count: number; link_count: number;
project_id: string; project_id: string | null;
parent_id: string | null; parent_id: string | null;
cycle_id: string | null; cycle_id: string | null;
module_ids: string[] | null; module_ids: string[];
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@ -37,9 +37,14 @@ export type TIssue = {
updated_by: string; updated_by: string;
is_draft: boolean; is_draft: boolean;
};
export type TIssue = TBaseIssue & {
description_html?: string;
is_subscribed?: boolean; is_subscribed?: boolean;
parent?: partial<TIssue>; parent?: partial<TIssue>;
issue_reactions?: TIssueReaction[]; issue_reactions?: TIssueReaction[];
issue_attachment?: TIssueAttachment[]; issue_attachment?: TIssueAttachment[];
issue_link?: TIssueLink[]; issue_link?: TIssueLink[];
@ -51,3 +56,24 @@ export type TIssue = {
export type TIssueMap = { export type TIssueMap = {
[issue_id: string]: TIssue; [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" | "state_detail.group"
| "project" | "project"
| "assignees" | "assignees"
| "mentions"
| "cycle" | "cycle"
| "module" | "module"
| null; | null;
@ -72,7 +71,9 @@ export type TIssueParams =
| "order_by" | "order_by"
| "type" | "type"
| "sub_issue" | "sub_issue"
| "show_empty_groups"; | "show_empty_groups"
| "cursor"
| "per_page";
export type TCalendarLayouts = "month" | "week"; export type TCalendarLayouts = "month" | "week";
@ -82,9 +83,9 @@ export interface IIssueFilterOptions {
created_by?: string[] | null; created_by?: string[] | null;
labels?: string[] | null; labels?: string[] | null;
priority?: string[] | null; priority?: string[] | null;
project?: string[] | null;
cycle?: string[] | null; cycle?: string[] | null;
module?: string[] | null; module?: string[] | null;
project?: string[] | null;
start_date?: string[] | null; start_date?: string[] | null;
state?: string[] | null; state?: string[] | null;
state_group?: string[] | null; state_group?: string[] | null;
@ -191,3 +192,11 @@ export interface IWorkspaceGlobalViewProps {
display_filters: IWorkspaceIssueDisplayFilterOptions | undefined; display_filters: IWorkspaceIssueDisplayFilterOptions | undefined;
display_properties: IIssueDisplayProperties; 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; 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, IIssueDisplayFilterOptions,
IIssueDisplayProperties, IIssueDisplayProperties,
IIssueFilterOptions, IIssueFilterOptions,
IssuePaginationOptions,
TIssue, TIssue,
TIssueKanbanFilters, TIssueKanbanFilters,
TIssuesResponse,
TLoader, TLoader,
} from "@plane/types"; } from "@plane/types";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
interface IssueActions { 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>; removeIssue: (projectId: string, issueId: string) => Promise<void>;
createIssue?: (projectId: string, data: Partial<TIssue>) => Promise<TIssue | undefined>; createIssue?: (projectId: string, data: Partial<TIssue>) => Promise<TIssue | undefined>;
updateIssue?: (projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>; updateIssue?: (projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
@ -29,25 +36,25 @@ export const useIssuesActions = (storeType: EIssuesStoreType): IssueActions => {
const projectIssueActions = useProjectIssueActions(); const projectIssueActions = useProjectIssueActions();
const cycleIssueActions = useCycleIssueActions(); const cycleIssueActions = useCycleIssueActions();
const moduleIssueActions = useModuleIssueActions(); const moduleIssueActions = useModuleIssueActions();
const profileIssueActions = useProfileIssueActions();
const projectViewIssueActions = useProjectViewIssueActions(); const projectViewIssueActions = useProjectViewIssueActions();
const globalIssueActions = useGlobalIssueActions();
const profileIssueActions = useProfileIssueActions();
const draftIssueActions = useDraftIssueActions(); const draftIssueActions = useDraftIssueActions();
const archivedIssueActions = useArchivedIssueActions(); const archivedIssueActions = useArchivedIssueActions();
const globalIssueActions = useGlobalIssueActions();
switch (storeType) { switch (storeType) {
case EIssuesStoreType.PROJECT_VIEW: case EIssuesStoreType.PROJECT_VIEW:
return projectViewIssueActions; return projectViewIssueActions;
case EIssuesStoreType.PROFILE: case EIssuesStoreType.PROFILE:
return profileIssueActions; return profileIssueActions;
case EIssuesStoreType.CYCLE:
return cycleIssueActions;
case EIssuesStoreType.MODULE:
return moduleIssueActions;
case EIssuesStoreType.ARCHIVED: case EIssuesStoreType.ARCHIVED:
return archivedIssueActions; return archivedIssueActions;
case EIssuesStoreType.DRAFT: case EIssuesStoreType.DRAFT:
return draftIssueActions; return draftIssueActions;
case EIssuesStoreType.CYCLE:
return cycleIssueActions;
case EIssuesStoreType.MODULE:
return moduleIssueActions;
case EIssuesStoreType.GLOBAL: case EIssuesStoreType.GLOBAL:
return globalIssueActions; return globalIssueActions;
case EIssuesStoreType.PROJECT: case EIssuesStoreType.PROJECT:
@ -60,16 +67,21 @@ const useProjectIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT); const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
const { const {
router: { workspaceSlug }, router: { workspaceSlug, projectId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!workspaceSlug) return; if (!workspaceSlug || !projectId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -114,13 +126,14 @@ const useProjectIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
archiveIssue, archiveIssue,
updateFilters, 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 { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { const {
router: { workspaceSlug, cycleId }, router: { workspaceSlug, projectId, cycleId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!cycleId || !workspaceSlug) return; if (!workspaceSlug || !projectId || !cycleId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, cycleId); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!cycleId || !workspaceSlug) return; if (!cycleId || !workspaceSlug) return;
@ -147,17 +165,17 @@ const useCycleIssueActions = () => {
); );
const updateIssue = useCallback( const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => { async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!cycleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, cycleId); return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
}, },
[issues.updateIssue, cycleId, workspaceSlug] [issues.updateIssue, workspaceSlug]
); );
const removeIssue = useCallback( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!cycleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, cycleId); return await issues.removeIssue(workspaceSlug, projectId, issueId);
}, },
[issues.removeIssue, cycleId, workspaceSlug] [issues.removeIssue, workspaceSlug]
); );
const removeIssueFromView = useCallback( const removeIssueFromView = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
@ -168,10 +186,10 @@ const useCycleIssueActions = () => {
); );
const archiveIssue = useCallback( const archiveIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!cycleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, cycleId); return await issues.archiveIssue(workspaceSlug, projectId, issueId);
}, },
[issues.archiveIssue, cycleId, workspaceSlug] [issues.archiveIssue, workspaceSlug]
); );
const updateFilters = useCallback( const updateFilters = useCallback(
@ -189,6 +207,7 @@ const useCycleIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
@ -196,7 +215,16 @@ const useCycleIssueActions = () => {
archiveIssue, archiveIssue,
updateFilters, 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 { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const { const {
router: { workspaceSlug, moduleId }, router: { workspaceSlug, projectId, moduleId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!moduleId || !workspaceSlug) return; if (!workspaceSlug || !projectId || !moduleId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, moduleId); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!moduleId || !workspaceSlug) return; if (!moduleId || !workspaceSlug) return;
@ -223,17 +256,17 @@ const useModuleIssueActions = () => {
); );
const updateIssue = useCallback( const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => { async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!moduleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, moduleId); return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
}, },
[issues.updateIssue, moduleId, workspaceSlug] [issues.updateIssue, workspaceSlug]
); );
const removeIssue = useCallback( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, moduleId); return await issues.removeIssue(workspaceSlug, projectId, issueId);
}, },
[issues.removeIssue, moduleId, workspaceSlug] [issues.removeIssue, workspaceSlug]
); );
const removeIssueFromView = useCallback( const removeIssueFromView = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
@ -244,8 +277,8 @@ const useModuleIssueActions = () => {
); );
const archiveIssue = useCallback( const archiveIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, moduleId); return await issues.archiveIssue(workspaceSlug, projectId, issueId);
}, },
[issues.archiveIssue, moduleId, workspaceSlug] [issues.archiveIssue, moduleId, workspaceSlug]
); );
@ -265,6 +298,7 @@ const useModuleIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
@ -284,39 +318,44 @@ const useProfileIssueActions = () => {
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions, viewId?: "assigned" | "created" | "subscribed") => {
if (!userId || !workspaceSlug) return; if (!workspaceSlug || !userId || !viewId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, userId); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!userId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, userId); return await issues.createIssue(workspaceSlug, projectId, data);
}, },
[issues.createIssue, userId, workspaceSlug] [issues.createIssue, workspaceSlug]
); );
const updateIssue = useCallback( const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => { async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!userId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, userId); return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
}, },
[issues.updateIssue, userId, workspaceSlug] [issues.updateIssue, workspaceSlug]
); );
const removeIssue = useCallback( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!userId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, userId); return await issues.removeIssue(workspaceSlug, projectId, issueId);
}, },
[issues.removeIssue, userId, workspaceSlug] [issues.removeIssue, workspaceSlug]
); );
const archiveIssue = useCallback( const archiveIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!userId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, userId); return await issues.archiveIssue(workspaceSlug, projectId, issueId);
}, },
[issues.archiveIssue, userId, workspaceSlug] [issues.archiveIssue, workspaceSlug]
); );
const updateFilters = useCallback( const updateFilters = useCallback(
@ -334,6 +373,7 @@ const useProfileIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
@ -348,43 +388,48 @@ const useProjectViewIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW); const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { const {
router: { workspaceSlug, viewId }, router: { workspaceSlug, projectId, viewId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!viewId || !workspaceSlug) return; if (!workspaceSlug || !projectId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType, viewId); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!viewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, viewId); return await issues.createIssue(workspaceSlug, projectId, data);
}, },
[issues.createIssue, viewId, workspaceSlug] [issues.createIssue, workspaceSlug]
); );
const updateIssue = useCallback( const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => { async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!viewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, viewId); return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
}, },
[issues.updateIssue, viewId, workspaceSlug] [issues.updateIssue, workspaceSlug]
); );
const removeIssue = useCallback( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!viewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, viewId); return await issues.removeIssue(workspaceSlug, projectId, issueId);
}, },
[issues.removeIssue, viewId, workspaceSlug] [issues.removeIssue, workspaceSlug]
); );
const archiveIssue = useCallback( const archiveIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!viewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.archiveIssue(workspaceSlug, projectId, issueId, viewId); return await issues.archiveIssue(workspaceSlug, projectId, issueId);
}, },
[issues.archiveIssue, viewId, workspaceSlug] [issues.archiveIssue, workspaceSlug]
); );
const updateFilters = useCallback( const updateFilters = useCallback(
@ -402,13 +447,14 @@ const useProjectViewIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
archiveIssue, archiveIssue,
updateFilters, 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 { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
const { const {
router: { workspaceSlug }, router: { workspaceSlug, projectId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!workspaceSlug) return; if (!workspaceSlug || !projectId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType); 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -463,6 +514,7 @@ const useDraftIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,
@ -476,16 +528,21 @@ const useArchivedIssueActions = () => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED); const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
const { const {
router: { workspaceSlug }, router: { workspaceSlug, projectId },
} = useApplication(); } = useApplication();
const fetchIssues = useCallback( const fetchIssues = useCallback(
async (projectId: string, loadType: TLoader) => { async (loadType: TLoader, options: IssuePaginationOptions) => {
if (!workspaceSlug) return; if (!workspaceSlug || !projectId) return;
return await issues.fetchIssues(workspaceSlug, projectId, loadType); 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( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -516,11 +573,12 @@ const useArchivedIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues, fetchIssues,
fetchNextIssues,
removeIssue, removeIssue,
restoreIssue, restoreIssue,
updateFilters, updateFilters,
}), }),
[fetchIssues, removeIssue, restoreIssue, updateFilters] [fetchIssues, fetchNextIssues, removeIssue, restoreIssue, updateFilters]
); );
}; };
@ -530,26 +588,38 @@ const useGlobalIssueActions = () => {
const { const {
router: { workspaceSlug, globalViewId }, router: { workspaceSlug, globalViewId },
} = useApplication(); } = 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( const createIssue = useCallback(
async (projectId: string, data: Partial<TIssue>) => { async (projectId: string, data: Partial<TIssue>) => {
if (!globalViewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.createIssue(workspaceSlug, projectId, data, globalViewId); return await issues.createIssue(workspaceSlug, projectId, data);
}, },
[issues.createIssue, globalViewId, workspaceSlug] [issues.createIssue, workspaceSlug]
); );
const updateIssue = useCallback( const updateIssue = useCallback(
async (projectId: string, issueId: string, data: Partial<TIssue>) => { async (projectId: string, issueId: string, data: Partial<TIssue>) => {
if (!globalViewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.updateIssue(workspaceSlug, projectId, issueId, data, globalViewId); return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
}, },
[issues.updateIssue, globalViewId, workspaceSlug] [issues.updateIssue, workspaceSlug]
); );
const removeIssue = useCallback( const removeIssue = useCallback(
async (projectId: string, issueId: string) => { async (projectId: string, issueId: string) => {
if (!globalViewId || !workspaceSlug) return; if (!workspaceSlug) return;
return await issues.removeIssue(workspaceSlug, projectId, issueId, globalViewId); return await issues.removeIssue(workspaceSlug, projectId, issueId);
}, },
[issues.removeIssue, globalViewId, workspaceSlug] [issues.removeIssue, workspaceSlug]
); );
const updateFilters = useCallback( const updateFilters = useCallback(
@ -566,6 +636,8 @@ const useGlobalIssueActions = () => {
return useMemo( return useMemo(
() => ({ () => ({
fetchIssues,
fetchNextIssues,
createIssue, createIssue,
updateIssue, updateIssue,
removeIssue, removeIssue,

View File

@ -2,7 +2,7 @@
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
import { APIService } from "services/api.service"; import { APIService } from "services/api.service";
// types // types
import type { CycleDateCheckData, ICycle, TIssue } from "@plane/types"; import type { CycleDateCheckData, ICycle, TIssue, TIssuesResponse } from "@plane/types";
// helpers // helpers
export class CycleService extends APIService { export class CycleService extends APIService {
@ -46,20 +46,12 @@ export class CycleService extends APIService {
}); });
} }
async getCycleIssues(workspaceSlug: string, projectId: string, cycleId: string): Promise<TIssue[]> { async getCycleIssues(
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getCycleIssuesWithParams(
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
cycleId: string, cycleId: string,
queries?: any queries?: any
): Promise<TIssue[]> { ): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, {
params: queries, params: queries,
}) })

View File

@ -2,7 +2,14 @@
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
import { APIService } from "services/api.service"; import { APIService } from "services/api.service";
// type // type
import type { TIssue, IIssueDisplayProperties, TIssueLink, TIssueSubIssues, TIssueActivity } from "@plane/types"; import type {
TIssue,
IIssueDisplayProperties,
TIssueLink,
TIssueSubIssues,
TIssueActivity,
TIssuesResponse,
} from "@plane/types";
// helper // helper
export class IssueService extends APIService { export class IssueService extends APIService {
@ -10,7 +17,7 @@ export class IssueService extends APIService {
super(API_BASE_URL); 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) return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .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/`, { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
params: queries, params: queries,
}) })

View File

@ -1,14 +1,14 @@
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
import { APIService } from "services/api.service"; import { APIService } from "services/api.service";
// helpers // helpers
import { TIssue } from "@plane/types"; import { TIssue, TIssuesResponse } from "@plane/types";
export class IssueDraftService extends APIService { export class IssueDraftService extends APIService {
constructor() { constructor() {
super(API_BASE_URL); 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/`, { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, {
params: { ...query }, 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) return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .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) return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, data)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .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}/`) return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .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}/`, { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, {
params: queries, params: queries,
}) })

View File

@ -2,7 +2,7 @@
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
import { APIService } from "services/api.service"; import { APIService } from "services/api.service";
// types // types
import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types"; import type { IModule, ILinkDetails, ModuleLink, TIssuesResponse } from "@plane/types";
export class ModuleService extends APIService { export class ModuleService extends APIService {
constructor() { 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/`, { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, {
params: queries, params: queries,
}) })
@ -111,7 +116,7 @@ export class ModuleService extends APIService {
projectId: string, projectId: string,
moduleId: string, moduleId: string,
issueId: string issueId: string
): Promise<any> { ): Promise<void> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
@ -124,14 +129,14 @@ export class ModuleService extends APIService {
projectId: string, projectId: string,
moduleId: string, moduleId: string,
issueIds: string[] issueIds: string[]
): Promise<any> { ): Promise<void> {
const promiseDataUrls: any = []; const promiseDataUrls: any = [];
issueIds.forEach((issueId) => { issueIds.forEach((issueId) => {
promiseDataUrls.push( promiseDataUrls.push(
this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
); );
}); });
return await Promise.all(promiseDataUrls) await Promise.all(promiseDataUrls)
.then((response) => response) .then((response) => response)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response?.data;
@ -143,14 +148,14 @@ export class ModuleService extends APIService {
projectId: string, projectId: string,
issueId: string, issueId: string,
moduleIds: string[] moduleIds: string[]
): Promise<any> { ): Promise<void> {
const promiseDataUrls: any = []; const promiseDataUrls: any = [];
moduleIds.forEach((moduleId) => { moduleIds.forEach((moduleId) => {
promiseDataUrls.push( promiseDataUrls.push(
this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
); );
}); });
return await Promise.all(promiseDataUrls) await Promise.all(promiseDataUrls)
.then((response) => response) .then((response) => response)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response?.data;

View File

@ -11,6 +11,7 @@ import type {
IUserProfileProjectSegregation, IUserProfileProjectSegregation,
IUserSettings, IUserSettings,
IUserEmailNotificationSettings, IUserEmailNotificationSettings,
TIssuesResponse,
} from "@plane/types"; } from "@plane/types";
// helpers // 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}/`, { return this.get(`/api/workspaces/${workspaceSlug}/user-issues/${userId}/`, {
params, params,
}) })

View File

@ -14,8 +14,8 @@ import {
IWorkspaceBulkInviteFormData, IWorkspaceBulkInviteFormData,
IWorkspaceViewProps, IWorkspaceViewProps,
IUserProjectsRole, IUserProjectsRole,
TIssue,
IWorkspaceView, IWorkspaceView,
TIssuesResponse,
} from "@plane/types"; } from "@plane/types";
export class WorkspaceService extends APIService { 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/`, { return this.get(`/api/workspaces/${workspaceSlug}/issues/`, {
params, params,
}) })

View File

@ -14,20 +14,22 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface IArchivedIssuesFilter { export interface IArchivedIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -92,6 +94,22 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string) => {
try { try {
const _filters = this.handleIssuesLocalFilters.get( const _filters = this.handleIssuesLocalFilters.get(
@ -150,7 +168,7 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
}); });
const appliedFilters = _filters.filters || {}; const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.archivedIssues.fetchIssues( this.rootIssueStore.archivedIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation" isEmpty(filteredFilters) ? "init-loader" : "mutation"
@ -193,7 +211,7 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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, { this.handleIssuesLocalFilters.set(EIssuesStoreType.ARCHIVED, type, workspaceSlug, projectId, undefined, {
display_filters: _filters.displayFilters, display_filters: _filters.displayFilters,

View File

@ -1,35 +1,36 @@
import pull from "lodash/pull"; import pull from "lodash/pull";
import set from "lodash/set"; import { action, makeObservable, runInAction } from "mobx";
import { action, observable, makeObservable, computed, runInAction } from "mobx";
// base class // base class
import { IssueArchiveService } from "services/issue"; import { TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
// services // services
// types // types
import { IIssueRootStore } from "../root.store"; 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 // observable
loader: TLoader;
issues: { [project_id: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>; fetchIssues: (
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>; 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>; restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
quickAddIssue: undefined;
} }
export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues { export class ArchivedIssues extends BaseIssuesStore implements IArchivedIssues {
loader: TLoader = "init-loader"; // filter store
issues: { [project_id: string]: string[] } = {}; issueFilterStore: IArchivedIssuesFilter;
// root store
rootIssueStore: IIssueRootStore;
// services
archivedIssueService;
//viewData //viewData
viewFlags = { viewFlags = {
@ -38,99 +39,73 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
enableInlineEditing: true, enableInlineEditing: true,
}; };
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: IArchivedIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore, true);
makeObservable(this, { makeObservable(this, {
// observable
loader: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
removeIssue: action,
restoreIssue: action, restoreIssue: action,
}); });
// root store // filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
// services
this.archivedIssueService = new IssueArchiveService();
} }
get groupedIssueIds() { fetchIssues = async (
const projectId = this.rootIssueStore.projectId; workspaceSlug: string,
if (!projectId) return undefined; projectId: string,
loadType: TLoader = "init-loader",
const displayFilters = this.rootIssueStore?.archivedIssuesFilter?.issueFilters?.displayFilters; options: IssuePaginationOptions
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") => {
try { try {
this.loader = loadType;
const params = this.rootIssueStore?.archivedIssuesFilter?.appliedFilters;
const response = await this.archivedIssueService.getArchivedIssues(workspaceSlug, projectId, params);
runInAction(() => { runInAction(() => {
set( this.loader = loadType;
this.issues,
[projectId],
response.map((issue: TIssue) => issue.id)
);
this.loader = undefined;
}); });
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; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; throw error;
} }
}; };
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
if (!this.paginationOptions) return;
try { try {
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); this.loader = "pagination";
runInAction(() => { const params = this.issueFilterStore?.getFilterParams(this.paginationOptions);
pull(this.issues[projectId], issueId); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
});
this.onfetchNexIssues(response);
return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; 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) => { restoreIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
try { try {
const response = await this.archivedIssueService.restoreIssue(workspaceSlug, projectId, issueId); const response = await this.issueArchiveService.restoreIssue(workspaceSlug, projectId, issueId);
runInAction(() => { runInAction(() => {
this.rootStore.issues.updateIssue(issueId, { this.rootIssueStore.issues.updateIssue(issueId, {
archived_at: null, archived_at: null,
}); });
pull(this.issues[projectId], issueId); this.issues && pull(this.issues, issueId);
}); });
return response; return response;
@ -138,6 +113,4 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
throw error; throw error;
} }
}; };
quickAddIssue: undefined;
} }

View File

@ -14,20 +14,22 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface ICycleIssuesFilter { export interface ICycleIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines cycleId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -95,6 +97,22 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try { try {
const _filters = await this.issueFilterService.fetchCycleIssueFilters(workspaceSlug, projectId, cycleId); 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 appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.cycleIssues.fetchIssues( this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation", isEmpty(filteredFilters) ? "init-loader" : "mutation",
@ -205,7 +223,12 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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, { await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {
display_filters: _filters.displayFilters, display_filters: _filters.displayFilters,

View File

@ -1,55 +1,43 @@
import concat from "lodash/concat"; import concat from "lodash/concat";
import pull from "lodash/pull"; import pull from "lodash/pull";
import set from "lodash/set";
import uniq from "lodash/uniq"; import uniq from "lodash/uniq";
import update from "lodash/update"; import update from "lodash/update";
import { action, observable, makeObservable, computed, runInAction } from "mobx"; import { action, observable, makeObservable, runInAction } from "mobx";
// base class // base class
// services // services
import { CycleService } from "services/cycle.service"; import { CycleService } from "services/cycle.service";
import { IssueService } from "services/issue";
// types // types
import { TIssue, TSubGroupedIssues, TGroupedIssues, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
import { IIssueRootStore } from "../root.store"; 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 const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
export interface ICycleIssues { export interface ICycleIssues extends IBaseIssuesStore {
// observable
loader: TLoader;
issues: { [cycle_id: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: ( fetchIssues: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
options: IssuePaginationOptions,
cycleId: string cycleId: string
) => Promise<TIssue[] | undefined>; ) => Promise<TIssuesResponse | undefined>;
createIssue: ( fetchIssuesWithExistingPagination: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, loadType: TLoader,
cycleId: string cycleId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssuesResponse | undefined>;
updateIssue: ( fetchNextIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<TIssuesResponse | undefined>;
workspaceSlug: string,
projectId: string, createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, cycleId: string) => Promise<TIssue>;
issueId: string, updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
data: Partial<TIssue>, archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
cycleId: string quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
) => Promise<void>; removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: 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>;
addIssueToCycle: ( addIssueToCycle: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -66,106 +54,63 @@ export interface ICycleIssues {
new_cycle_id: string; new_cycle_id: string;
} }
) => Promise<TIssue>; ) => 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 { export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
loader: TLoader = "init-loader"; cycleId: string | undefined = undefined;
issues: { [cycle_id: string]: string[] } = {};
viewFlags = { viewFlags = {
enableQuickAdd: true, enableQuickAdd: true,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store
rootIssueStore: IIssueRootStore;
// service // service
cycleService; cycleService;
issueService; // filter store
issueFilterStore;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: ICycleIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable // observable
loader: observable.ref, cycleId: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action,
updateIssue: action,
removeIssue: action,
archiveIssue: action,
quickAddIssue: action,
addIssueToCycle: action, addIssueToCycle: action,
removeIssueFromCycle: action, removeIssueFromCycle: action,
transferIssuesFromCycle: action, transferIssuesFromCycle: action,
fetchActiveCycleIssues: action, fetchActiveCycleIssues: action,
}); });
// service
this.rootIssueStore = _rootStore;
this.issueService = new IssueService();
this.cycleService = new CycleService(); this.cycleService = new CycleService();
} // filter store
this.issueFilterStore = issueFilterStore;
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;
} }
fetchIssues = async ( fetchIssues = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader = "init-loader", loadType: TLoader,
options: IssuePaginationOptions,
cycleId: string cycleId: string
) => { ) => {
try { 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(() => { runInAction(() => {
set( this.loader = loadType;
this.issues,
[cycleId],
response.map((issue) => issue.id)
);
this.loader = undefined;
}); });
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; return response;
} catch (error) { } catch (error) {
this.loader = undefined; 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 { try {
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id], false); 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 ( addIssueToCycle = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -275,17 +171,14 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds); if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
runInAction(() => { runInAction(() => {
update(this.issues, cycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds))); this.cycleId === cycleId &&
update(this, "issues", (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds)));
}); });
issueIds.forEach((issueId) => { issueIds.forEach((issueId) => {
const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id; this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: cycleId });
if (issueCycleId && issueCycleId !== cycleId) {
runInAction(() => {
pull(this.issues[issueCycleId], issueId);
});
}
this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId });
}); });
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
} catch (error) { } catch (error) {
throw error; throw error;
@ -294,13 +187,12 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => { removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try { try {
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
runInAction(() => { runInAction(() => {
pull(this.issues[cycleId], issueId); this.issues && this.cycleId === cycleId && pull(this.issues, issueId);
}); });
this.rootStore.issues.updateIssue(issueId, { cycle_id: null }); this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: null });
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
} catch (error) { } catch (error) {
throw error; throw error;
@ -322,7 +214,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
cycleId as string, cycleId as string,
payload payload
); );
await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); this.paginationOptions &&
(await this.fetchIssues(workspaceSlug, projectId, "mutation", this.paginationOptions, cycleId));
return response; return response;
} catch (error) { } catch (error) {
@ -333,14 +226,14 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => { fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try { try {
const params = { priority: `urgent,high` }; 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(() => { // runInAction(() => {
set(this.issues, [ACTIVE_CYCLE_ISSUES], Object.keys(response)); // set(this.issues, , Object.keys(response));
this.loader = undefined; // this.loader = undefined;
}); // });
this.rootIssueStore.issues.addIssue(Object.values(response)); // this.rootIssueStore.issues.addIssue(Object.values(response));
return response; return response;
} catch (error) { } catch (error) {

View File

@ -14,20 +14,22 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface IDraftIssuesFilter { export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -92,6 +94,22 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string) => {
try { try {
const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.DRAFT, workspaceSlug, projectId, undefined); 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 appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.draftIssues.fetchIssues( this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation" isEmpty(filteredFilters) ? "init-loader" : "mutation"
@ -188,7 +206,7 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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, { this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
display_filters: _filters.displayFilters, display_filters: _filters.displayFilters,

View File

@ -1,178 +1,103 @@
import concat from "lodash/concat"; import { action, makeObservable, runInAction } from "mobx";
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 // base class
// services // services
import { IssueDraftService } from "services/issue/issue_draft.service";
// types // types
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
import { IIssueRootStore } from "../root.store"; 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 // observable
loader: TLoader;
issues: { [project_id: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // 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>; createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>; 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 { export class DraftIssues extends BaseIssuesStore implements IDraftIssues {
loader: TLoader = "init-loader";
issues: { [project_id: string]: string[] } = {};
viewFlags = { viewFlags = {
enableQuickAdd: false, enableQuickAdd: false,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store // filter store
rootIssueStore: IIssueRootStore; issueFilterStore: IDraftIssuesFilter;
// service
issueDraftService;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: IDraftIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable
loader: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action, createIssue: action,
updateIssue: action, updateIssue: action,
removeIssue: action, removeIssue: action,
}); });
// root store // filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
this.issueDraftService = new IssueDraftService();
} }
get getIssues() { fetchIssues = async (
const projectId = this.rootIssueStore.projectId; workspaceSlug: string,
if (!projectId || !this.issues || !this.issues[projectId]) return undefined; projectId: string,
loadType: TLoader = "init-loader",
return this.issues[projectId]; options: IssuePaginationOptions
} ) => {
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") => {
try { try {
runInAction(() => {
this.loader = loadType; 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); const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
runInAction(() => { this.onfetchIssues(response, options);
set(
this.issues,
[projectId],
response.map((issue) => issue.id)
);
this.loader = undefined;
});
this.rootIssueStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; throw error;
} }
}; };
createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => { fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
if (!this.paginationOptions) return;
try { try {
const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data); this.loader = "pagination";
runInAction(() => { const params = this.issueFilterStore?.getFilterParams(this.paginationOptions);
update(this.issues, [projectId], (issueIds = []) => uniq(concat(issueIds, response.id))); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
});
this.rootStore.issues.addIssue([response]);
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => { fetchIssuesWithExistingPagination = async (
try { workspaceSlug: string,
await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data); projectId: string,
loadType: TLoader = "mutation"
this.rootStore.issues.updateIssue(issueId, data); ) => {
if (!this.paginationOptions) return;
if (data.hasOwnProperty("is_draft") && data?.is_draft === false) { return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions);
runInAction(() => {
update(this.issues, [projectId], (issueIds = []) => {
if (issueIds.includes(issueId)) pull(issueIds, issueId);
return issueIds;
});
});
}
} catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation");
throw error;
}
}; };
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { createIssue = this.createDraftIssue;
try { updateIssue = this.updateDraftIssue;
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;
} }

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 get from "lodash/get";
import indexOf from "lodash/indexOf"; import indexOf from "lodash/indexOf";
import isEmpty from "lodash/isEmpty"; import isEmpty from "lodash/isEmpty";
import orderBy from "lodash/orderBy";
import values from "lodash/values"; import values from "lodash/values";
// types // 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 // constants
import { ISSUE_PRIORITIES } from "constants/issue"; import { ISSUE_PRIORITIES } from "constants/issue";
import { STATE_GROUPS } from "constants/state"; import { STATE_GROUPS } from "constants/state";
// helpers // helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { TIssue, TIssueMap, TIssueGroupByOptions, TIssueOrderByOptions } from "@plane/types"; // services
import { IIssueRootStore } from "../root.store"; import { IssueArchiveService, IssueDraftService, IssueService } from "services/issue";
export type TIssueDisplayFilterOptions = Exclude<TIssueGroupByOptions, null> | "target_date"; 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 // helper methods
groupedIssues( groupedIssues(
groupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions,
orderBy: TIssueOrderByOptions, orderBy: TIssueOrderByOptions,
issues: TIssueMap, issues: TIssueMap,
groupedIssueCount: Record<string, number>,
isCalendarIssues?: boolean isCalendarIssues?: boolean
): { [group_id: string]: string[] }; ): TGroupedIssues;
subGroupedIssues( subGroupedIssues(
subGroupBy: TIssueDisplayFilterOptions, subGroupBy: TIssueDisplayFilterOptions,
groupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions,
orderBy: TIssueOrderByOptions, orderBy: TIssueOrderByOptions,
issues: TIssueMap issues: TIssueMap,
): { [sub_group_id: string]: { [group_id: string]: string[] } }; groupedIssueCount: Record<string, number>
unGroupedIssues(orderBy: TIssueOrderByOptions, issues: TIssueMap): string[]; ): TSubGroupedIssues;
unGroupedIssues(orderBy: TIssueOrderByOptions, issues: TIssueMap, count: number): TUnGroupedIssues;
issueDisplayFiltersDefaultData(groupBy: string | null): string[]; issueDisplayFiltersDefaultData(groupBy: string | null): string[];
issuesSortWithOrderBy(issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[]; issuesSortWithOrderBy(issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[];
getGroupArray(value: boolean | number | string | string[] | null, isDate?: boolean): string[]; getGroupArray(value: boolean | number | string | string[] | null, isDate?: boolean): string[];
}; }
const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = { const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
project: "project_id", project: "project_id",
cycle: "cycle_id",
module: "module_ids",
state: "state_id", state: "state_id",
"state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display, "state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
priority: "priority", priority: "priority",
labels: "label_ids", labels: "label_ids",
created_by: "created_by", created_by: "created_by",
assignees: "assignee_ids", assignees: "assignee_ids",
mentions: "assignee_ids",
target_date: "target_date", target_date: "target_date",
cycle: "cycle_id",
module: "module_ids",
}; };
export class IssueHelperStore implements TIssueHelperStore { export class BaseIssuesStore implements IBaseIssuesStore {
// root store loader: TLoader = "init-loader";
rootStore; issues: string[] | undefined = undefined;
groupedIssueCount: Record<string, number> | undefined = undefined;
constructor(_rootStore: IIssueRootStore) { nextCursor: string | undefined = undefined;
this.rootStore = _rootStore; 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 = ( groupedIssues = (
groupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions,
orderBy: TIssueOrderByOptions, orderBy: TIssueOrderByOptions,
issues: TIssueMap, issues: TIssueMap,
groupedIssueCount: Record<string, number> | undefined,
isCalendarIssues: boolean = false isCalendarIssues: boolean = false
) => { ) => {
const _issues: { [group_id: string]: string[] } = {}; const _issues: TGroupedIssues = {};
if (!groupBy) return _issues; if (!groupBy || !groupedIssueCount) return _issues;
this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => {
_issues[group] = []; _issues[group] = { issueIds: [], issueCount: groupedIssueCount[group] };
}); });
const projectIssues = this.issuesSortWithOrderBy(issues, orderBy); const projectIssues = this.issuesSortWithOrderBy(issues, orderBy);
@ -76,8 +391,8 @@ export class IssueHelperStore implements TIssueHelperStore {
let groupArray = []; let groupArray = [];
if (groupBy === "state_detail.group") { 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 =
const state_group = (this.rootStore?.stateMap || {})?.[_issue?.state_id]?.group || "None"; this.rootIssueStore?.stateDetails?.find((_state) => _state.id === _issue?.state_id)?.group || "None";
groupArray = [state_group]; groupArray = [state_group];
} else { } else {
const groupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]); const groupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]);
@ -85,8 +400,8 @@ export class IssueHelperStore implements TIssueHelperStore {
} }
for (const group of groupArray) { for (const group of groupArray) {
if (group && _issues[group]) _issues[group].push(_issue.id); if (group && _issues[group]) _issues[group].issueIds.push(_issue.id);
else if (group) _issues[group] = [_issue.id]; else if (group) _issues[group].issueIds = [_issue.id];
} }
} }
@ -97,15 +412,16 @@ export class IssueHelperStore implements TIssueHelperStore {
subGroupBy: TIssueDisplayFilterOptions, subGroupBy: TIssueDisplayFilterOptions,
groupBy: TIssueDisplayFilterOptions, groupBy: TIssueDisplayFilterOptions,
orderBy: TIssueOrderByOptions, orderBy: TIssueOrderByOptions,
issues: TIssueMap issues: TIssueMap,
groupedIssueCount: Record<string, number> | undefined
) => { ) => {
const _issues: { [sub_group_id: string]: { [group_id: string]: string[] } } = {}; const _issues: TSubGroupedIssues = {};
if (!subGroupBy || !groupBy) return _issues; if (!subGroupBy || !groupBy || !groupedIssueCount) return _issues;
this.issueDisplayFiltersDefaultData(subGroupBy).forEach((sub_group: any) => { this.issueDisplayFiltersDefaultData(subGroupBy).forEach((sub_group: any) => {
const groupByIssues: { [group_id: string]: string[] } = {}; const groupByIssues: TGroupedIssues = {};
this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => {
groupByIssues[group] = []; groupByIssues[group] = { issueIds: [], issueCount: groupedIssueCount[group] };
}); });
_issues[sub_group] = groupByIssues; _issues[sub_group] = groupByIssues;
}); });
@ -117,8 +433,8 @@ export class IssueHelperStore implements TIssueHelperStore {
let subGroupArray = []; let subGroupArray = [];
let groupArray = []; let groupArray = [];
if (subGroupBy === "state_detail.group" || groupBy === "state_detail.group") { 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]; subGroupArray = [state_group];
groupArray = [state_group]; groupArray = [state_group];
} else { } else {
@ -130,9 +446,10 @@ export class IssueHelperStore implements TIssueHelperStore {
for (const subGroup of subGroupArray) { for (const subGroup of subGroupArray) {
for (const group of groupArray) { for (const group of groupArray) {
if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].push(_issue.id); if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].issueIds.push(_issue.id);
else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group] = [_issue.id]; else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group].issueIds = [_issue.id];
else if (subGroup && group) _issues[subGroup] = { [group]: [_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; return _issues;
}; };
unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: TIssueMap) => unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: TIssueMap, count: number | undefined) => {
this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id); const issueIds = this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id);
return { "All Issues": { issueIds, issueCount: count || issueIds.length } };
};
issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => { issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => {
switch (groupBy) { switch (groupBy) {
case "state": case "state":
return Object.keys(this.rootStore?.stateMap || {}); return Object.keys(this.rootIssueStore?.stateMap || {});
case "state_detail.group": case "state_detail.group":
return Object.keys(STATE_GROUPS); return Object.keys(STATE_GROUPS);
case "priority": case "priority":
return ISSUE_PRIORITIES.map((i) => i.key); return ISSUE_PRIORITIES.map((i) => i.key);
case "labels": case "labels":
return Object.keys(this.rootStore?.labelMap || {}); return Object.keys(this.rootIssueStore?.labelMap || {});
case "created_by": case "created_by":
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {}); return Object.keys(this.rootIssueStore?.workSpaceMemberRolesMap || {});
case "assignees": case "assignees":
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {}); return Object.keys(this.rootIssueStore?.workSpaceMemberRolesMap || {});
case "project": case "project":
return Object.keys(this.rootStore?.projectMap || {}); return Object.keys(this.rootIssueStore?.projectMap || {});
case "cycle":
return Object.keys(this.rootStore?.cycleMap || {});
case "module":
return Object.keys(this.rootStore?.moduleMap || {});
default: default:
return []; return [];
} }
@ -188,7 +504,7 @@ export class IssueHelperStore implements TIssueHelperStore {
switch (dataType) { switch (dataType) {
case "state_id": case "state_id":
const stateMap = this.rootStore?.stateMap; const stateMap = this.rootIssueStore?.stateMap;
if (!stateMap) break; if (!stateMap) break;
for (const dataId of dataIdsArray) { for (const dataId of dataIdsArray) {
const state = stateMap[dataId]; const state = stateMap[dataId];
@ -196,7 +512,7 @@ export class IssueHelperStore implements TIssueHelperStore {
} }
break; break;
case "label_ids": case "label_ids":
const labelMap = this.rootStore?.labelMap; const labelMap = this.rootIssueStore?.labelMap;
if (!labelMap) break; if (!labelMap) break;
for (const dataId of dataIdsArray) { for (const dataId of dataIdsArray) {
const label = labelMap[dataId]; const label = labelMap[dataId];
@ -204,7 +520,7 @@ export class IssueHelperStore implements TIssueHelperStore {
} }
break; break;
case "assignee_ids": case "assignee_ids":
const memberMap = this.rootStore?.memberMap; const memberMap = this.rootIssueStore?.memberMap;
if (!memberMap) break; if (!memberMap) break;
for (const dataId of dataIdsArray) { for (const dataId of dataIdsArray) {
const member = memberMap[dataId]; const member = memberMap[dataId];
@ -212,7 +528,7 @@ export class IssueHelperStore implements TIssueHelperStore {
} }
break; break;
case "module_ids": case "module_ids":
const moduleMap = this.rootStore?.moduleMap; const moduleMap = this.rootIssueStore?.moduleMap;
if (!moduleMap) break; if (!moduleMap) break;
for (const dataId of dataIdsArray) { for (const dataId of dataIdsArray) {
const _module = moduleMap[dataId]; const _module = moduleMap[dataId];
@ -220,7 +536,7 @@ export class IssueHelperStore implements TIssueHelperStore {
} }
break; break;
case "cycle_id": case "cycle_id":
const cycleMap = this.rootStore?.cycleMap; const cycleMap = this.rootIssueStore?.cycleMap;
if (!cycleMap) break; if (!cycleMap) break;
for (const dataId of dataIdsArray) { for (const dataId of dataIdsArray) {
const cycle = cycleMap[dataId]; 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 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 * @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) { getSortOrderToFilterEmptyValues(key: string, object: any) {
const value = object?.[key]; const value = object?.[key];
@ -248,7 +564,7 @@ export class IssueHelperStore implements TIssueHelperStore {
issuesSortWithOrderBy = (issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[] => { issuesSortWithOrderBy = (issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[] => {
let array = values(issueObject); let array = values(issueObject);
array = orderBy(array, "created_at"); array = orderBy(array, "created_at", ["asc"]);
switch (key) { switch (key) {
case "sort_order": case "sort_order":
@ -395,4 +711,38 @@ export class IssueHelperStore implements TIssueHelperStore {
else if (isDate) return [renderFormattedPayloadDate(value) || "None"]; else if (isDate) return [renderFormattedPayloadDate(value) || "None"];
else return [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"; import isEmpty from "lodash/isEmpty";
// types // types
// constants
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
// lib
import { storage } from "lib/local-storage";
import { import {
IIssueDisplayFilterOptions, IIssueDisplayFilterOptions,
IIssueDisplayProperties, IIssueDisplayProperties,
@ -14,6 +10,10 @@ import {
TIssueParams, TIssueParams,
TStaticViewTypes, TStaticViewTypes,
} from "@plane/types"; } from "@plane/types";
// constants
import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue";
// lib
import { storage } from "lib/local-storage";
interface ILocalStoreIssueFilters { interface ILocalStoreIssueFilters {
key: EIssuesStoreType; key: EIssuesStoreType;
@ -23,6 +23,14 @@ interface ILocalStoreIssueFilters {
filters: IIssueFilters; filters: IIssueFilters;
} }
export interface IBaseIssueFilterStore {
// observables
filters: Record<string, IIssueFilters>;
//computed
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
issueFilters: IIssueFilters | undefined;
}
export interface IIssueFilterHelperStore { export interface IIssueFilterHelperStore {
computedIssueFilters(filters: IIssueFilters): IIssueFilters; computedIssueFilters(filters: IIssueFilters): IIssueFilters;
computedFilteredParams( computedFilteredParams(
@ -78,9 +86,11 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
module: filters?.module || undefined, module: filters?.module || undefined,
start_date: filters?.start_date || undefined, start_date: filters?.start_date || undefined,
target_date: filters?.target_date || undefined, target_date: filters?.target_date || undefined,
project: filters.project || undefined, project: filters?.project || undefined,
subscriber: filters.subscriber || undefined, subscriber: filters?.subscriber || undefined,
// display filters // display filters
group_by: displayFilters?.group_by ? IssueGroupByOptions[displayFilters.group_by] : undefined,
order_by: displayFilters?.order_by || undefined,
type: displayFilters?.type || undefined, type: displayFilters?.type || undefined,
sub_issue: displayFilters?.sub_issue ?? true, sub_issue: displayFilters?.sub_issue ?? true,
}; };

View File

@ -14,20 +14,22 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface IModuleIssuesFilter { export interface IModuleIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines moduleId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -95,6 +97,22 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => {
try { try {
const _filters = await this.issueFilterService.fetchModuleIssueFilters(workspaceSlug, projectId, moduleId); 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 appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.moduleIssues.fetchIssues( this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation", isEmpty(filteredFilters) ? "init-loader" : "mutation",
@ -204,7 +222,12 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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, { await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {
display_filters: _filters.displayFilters, display_filters: _filters.displayFilters,

View File

@ -1,53 +1,41 @@
import concat from "lodash/concat"; import concat from "lodash/concat";
import pull from "lodash/pull"; import pull from "lodash/pull";
import set from "lodash/set";
import uniq from "lodash/uniq"; import uniq from "lodash/uniq";
import update from "lodash/update"; import update from "lodash/update";
import { action, observable, makeObservable, computed, runInAction } from "mobx"; import { action, observable, makeObservable, computed, runInAction } from "mobx";
// base class // base class
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
// services // services
import { IssueService } from "services/issue";
import { ModuleService } from "services/module.service"; import { ModuleService } from "services/module.service";
// types // types
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { IModuleIssuesFilter } from "./filter.store";
export interface IModuleIssues { export interface IModuleIssues extends IBaseIssuesStore {
// observable
loader: TLoader;
issues: { [module_id: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: ( fetchIssues: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
options: IssuePaginationOptions,
moduleId: string moduleId: string
) => Promise<TIssue[] | undefined>; ) => Promise<TIssuesResponse | undefined>;
createIssue: ( fetchIssuesWithExistingPagination: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, loadType: TLoader,
moduleId: string moduleId: string
) => Promise<TIssue | undefined>; ) => Promise<TIssuesResponse | undefined>;
updateIssue: ( fetchNextIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<TIssuesResponse | undefined>;
workspaceSlug: string,
projectId: string, createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
issueId: string, updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
data: Partial<TIssue>, archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
moduleId: string quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
) => Promise<void>; removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: 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>;
addIssuesToModule: ( addIssuesToModule: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
@ -71,193 +59,99 @@ export interface IModuleIssues {
removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>; removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>;
} }
export class ModuleIssues extends IssueHelperStore implements IModuleIssues { export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
loader: TLoader = "init-loader"; moduleId: string | undefined = undefined;
issues: { [module_id: string]: string[] } = {};
viewFlags = { viewFlags = {
enableQuickAdd: true, enableQuickAdd: true,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store
rootIssueStore: IIssueRootStore;
// service // service
moduleService; moduleService;
issueService; // filter store
issueFilterStore: IModuleIssuesFilter;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: IModuleIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable // observable
loader: observable.ref, moduleId: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action,
updateIssue: action,
removeIssue: action,
archiveIssue: action,
quickAddIssue: action,
addIssuesToModule: action, addIssuesToModule: action,
removeIssuesFromModule: action, removeIssuesFromModule: action,
addModulesToIssue: action, addModulesToIssue: action,
removeModulesFromIssue: action, removeModulesFromIssue: action,
removeIssueFromModule: action, removeIssueFromModule: action,
}); });
// filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
this.issueService = new IssueService(); // service
this.moduleService = new ModuleService(); 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 ( fetchIssues = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader = "init-loader", loadType: TLoader,
options: IssuePaginationOptions,
moduleId: string moduleId: string
) => { ) => {
try { try {
runInAction(() => {
this.loader = loadType; 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; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; 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 { try {
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); this.loader = "pagination";
await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id], false);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions);
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async ( fetchIssuesWithExistingPagination = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
issueId: string, loadType: TLoader,
data: Partial<TIssue>,
moduleId: string moduleId: string
) => { ) => {
try { if (!this.paginationOptions) return;
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, moduleId);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
} catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
throw error;
}
}; };
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleId: string) => { override createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => {
try { 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); 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; return response;
} catch (error) { } catch (error) {
throw error; throw error;
@ -279,18 +173,20 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds); if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
runInAction(() => { runInAction(() => {
update(this.issues, moduleId, (moduleIssueIds = []) => { this.moduleId === moduleId &&
update(this, "issues", (moduleIssueIds = []) => {
if (!moduleIssueIds) return [...issueIds]; if (!moduleIssueIds) return [...issueIds];
else return uniq(concat(moduleIssueIds, issueIds)); else return uniq(concat(moduleIssueIds, issueIds));
}); });
}); });
issueIds.forEach((issueId) => { 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; if (issueModuleIds.includes(moduleId)) return issueModuleIds;
else return uniq(concat(issueModuleIds, [moduleId])); else return uniq(concat(issueModuleIds, [moduleId]));
}); });
}); });
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
} catch (error) { } catch (error) {
throw error; throw error;
@ -299,27 +195,29 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
removeIssuesFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { removeIssuesFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => {
try { 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( const response = await this.moduleService.removeIssuesFromModuleBulk(
workspaceSlug, workspaceSlug,
projectId, projectId,
moduleId, moduleId,
issueIds 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); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
return response; return response;
@ -336,12 +234,13 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
runInAction(() => { runInAction(() => {
moduleIds.forEach((moduleId) => { moduleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => { this.moduleId === moduleId &&
update(this, "issues", (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return moduleIssueIds; if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
else return uniq(concat(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 = []) =>
uniq(concat(issueModuleIds, moduleIds)) uniq(concat(issueModuleIds, moduleIds))
); );
}); });
@ -356,11 +255,12 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
try { try {
runInAction(() => { runInAction(() => {
moduleIds.forEach((moduleId) => { moduleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => { this.moduleId === moduleId &&
update(this, "issues", (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId); if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
else return uniq(concat(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) pull(issueModuleIds, moduleId)
); );
}); });
@ -382,8 +282,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => { removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
try { try {
runInAction(() => { runInAction(() => {
pull(this.issues[moduleId], issueId); this.issues && this.moduleId === this.moduleId && pull(this.issues, issueId);
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
pull(issueModuleIds, moduleId) pull(issueModuleIds, moduleId)
); );
}); });

View File

@ -14,21 +14,24 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface IProfileIssuesFilter { export interface IProfileIssuesFilter extends IBaseIssueFilterStore {
// observables // observables
userId: string; userId: string;
filters: Record<string, IIssueFilters>; // Record defines userId as key and IIssueFilters as value //helper actions
// computed getFilterParams: (
issueFilters: IIssueFilters | undefined; options: IssuePaginationOptions,
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; cursor?: string
) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, userId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, userId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -96,6 +99,22 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, userId: string) => {
try { try {
this.userId = userId; this.userId = userId;
@ -150,12 +169,10 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
const appliedFilters = _filters.filters || {}; const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.profileIssues.fetchIssues( this.rootIssueStore.profileIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
undefined,
isEmpty(filteredFilters) ? "init-loader" : "mutation",
userId, userId,
this.rootIssueStore.profileIssues.currentView isEmpty(filteredFilters) ? "init-loader" : "mutation"
); );
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, { this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, {
@ -196,13 +213,7 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.profileIssues.fetchIssues( this.rootIssueStore.profileIssues.fetchIssuesWithExistingPagination(workspaceSlug, userId, "mutation");
workspaceSlug,
undefined,
"mutation",
userId,
this.rootIssueStore.profileIssues.currentView
);
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, { this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, {
display_filters: _filters.displayFilters, 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"; import { action, observable, makeObservable, computed, runInAction } from "mobx";
// base class // base class
import { UserService } from "services/user.service"; import { UserService } from "services/user.service";
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
// services // services
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { IProfileIssuesFilter } from "./filter.store";
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
interface IProfileIssueTabTypes { export interface IProfileIssues extends IBaseIssuesStore {
[key: string]: string[];
}
export interface IProfileIssues {
// observable // observable
loader: TLoader;
currentView: "assigned" | "created" | "subscribed"; currentView: "assigned" | "created" | "subscribed";
issues: { [userId: string]: IProfileIssueTabTypes };
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
viewFlags: ViewFlags; viewFlags: ViewFlags;
// actions // actions
setViewId: (viewId: "assigned" | "created" | "subscribed") => void; setViewId: (viewId: "assigned" | "created" | "subscribed") => void;
// action
fetchIssues: ( fetchIssues: (
workspaceSlug: string, workspaceSlug: string,
projectId: string | undefined,
loadType: TLoader,
userId: string, userId: string,
view?: "assigned" | "created" | "subscribed" loadType: TLoader,
) => Promise<TIssue[]>; option: IssuePaginationOptions,
createIssue: ( view: "assigned" | "created" | "subscribed"
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, userId: string,
data: Partial<TIssue>, loadType: TLoader
userId: string ) => Promise<TIssuesResponse | undefined>;
) => Promise<TIssue | undefined>; fetchNextIssues: (workspaceSlug: string, userId: string) => Promise<TIssuesResponse | undefined>;
updateIssue: (
workspaceSlug: string, createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
projectId: string, updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
issueId: string, archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
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;
} }
export class ProfileIssues extends IssueHelperStore implements IProfileIssues { export class ProfileIssues extends BaseIssuesStore implements IProfileIssues {
loader: TLoader = "init-loader";
currentView: "assigned" | "created" | "subscribed" = "assigned"; currentView: "assigned" | "created" | "subscribed" = "assigned";
issues: { [userId: string]: IProfileIssueTabTypes } = {}; // filter store
quickAddIssue = undefined; issueFilterStore: IProfileIssuesFilter;
// root store
rootIssueStore: IIssueRootStore;
// services // services
userService; userService;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: IProfileIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable // observable
loader: observable.ref,
currentView: observable.ref, currentView: observable.ref,
issues: observable,
// computed // computed
groupedIssueIds: computed,
viewFlags: computed, viewFlags: computed,
// action // action
setViewId: action.bound, setViewId: action.bound,
fetchIssues: action, fetchIssues: action,
createIssue: action,
updateIssue: action,
removeIssue: action,
archiveIssue: action,
}); });
// root store // filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
// services // services
this.userService = new UserService(); 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() { get viewFlags() {
if (this.currentView === "subscribed") if (this.currentView === "subscribed")
return { return {
@ -138,20 +78,20 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
fetchIssues = async ( fetchIssues = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string | undefined,
loadType: TLoader = "init-loader",
userId: string, userId: string,
view?: "assigned" | "created" | "subscribed" loadType: TLoader,
options: IssuePaginationOptions,
view: "assigned" | "created" | "subscribed"
) => { ) => {
try { try {
runInAction(() => {
this.loader = loadType; 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 = this.issueFilterStore?.getFilterParams(options);
let params: any = this.rootIssueStore?.profileIssuesFilter?.appliedFilters;
params = { params = {
...params, ...params,
assignees: undefined, assignees: undefined,
@ -164,17 +104,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params);
runInAction(() => { this.onfetchIssues(response, options);
set(
this.issues,
[userId, uniqueViewId],
response.map((issue) => issue.id)
);
this.loader = undefined;
});
this.rootIssueStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined; 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 { 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(() => { const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params);
this.issues[userId][uniqueViewId].push(response.id);
});
this.rootStore.issues.addIssue([response]);
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async ( fetchIssuesWithExistingPagination = async (workspaceSlug: string, userId: string, loadType: TLoader) => {
workspaceSlug: string, if (!this.paginationOptions || !this.currentView) return;
projectId: string, return await this.fetchIssues(workspaceSlug, userId, loadType, this.paginationOptions, this.currentView);
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;
}
}; };
} }

View File

@ -14,20 +14,22 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
export interface IProjectViewIssuesFilter { export interface IProjectViewIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines viewId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -93,6 +95,22 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string, viewId: string) => {
try { try {
const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, projectId, viewId); 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 appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.projectViewIssues.fetchIssues( this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation", isEmpty(filteredFilters) ? "init-loader" : "mutation"
viewId
); );
break; break;
case EIssueFilterType.DISPLAY_FILTERS: case EIssueFilterType.DISPLAY_FILTERS:
@ -200,7 +217,11 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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, { await this.issueFilterService.patchView(workspaceSlug, projectId, viewId, {
display_filters: _filters.displayFilters, display_filters: _filters.displayFilters,

View File

@ -1,230 +1,94 @@
import pull from "lodash/pull"; import { action, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
import { action, observable, makeObservable, computed, runInAction } from "mobx";
// base class // base class
import { IssueService } from "services/issue/issue.service"; import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
// services // services
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
import { IProjectViewIssuesFilter } from "./filter.store";
export interface IProjectViewIssues { export interface IProjectViewIssues extends IBaseIssuesStore {
// observable
loader: TLoader;
issues: { [view_id: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: ( fetchIssues: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
loadType: TLoader, loadType: TLoader,
viewId: string options: IssuePaginationOptions
) => Promise<TIssue[] | undefined>; ) => Promise<TIssuesResponse | undefined>;
createIssue: ( fetchIssuesWithExistingPagination: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: Partial<TIssue>, loadType: TLoader
viewId: string ) => Promise<TIssuesResponse | undefined>;
) => Promise<TIssue | undefined>; fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
updateIssue: (
workspaceSlug: string, createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
projectId: string, updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
issueId: string, archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
data: Partial<TIssue>, quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue | undefined>;
viewId: string removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
) => 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>;
} }
export class ProjectViewIssues extends IssueHelperStore implements IProjectViewIssues { export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIssues {
loader: TLoader = "init-loader";
issues: { [view_id: string]: string[] } = {};
viewFlags = { viewFlags = {
enableQuickAdd: true, enableQuickAdd: true,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store //filter store
rootIssueStore: IIssueRootStore; issueFilterStore: IProjectViewIssuesFilter;
// services
issueService;
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore, issueFilterStore: IProjectViewIssuesFilter) {
super(_rootStore); super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable
loader: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action,
updateIssue: action,
removeIssue: action,
archiveIssue: action,
quickAddIssue: action,
}); });
// root store //filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
// services
this.issueService = new IssueService();
} }
get groupedIssueIds() { fetchIssues = async (
const viewId = this.rootStore?.viewId; workspaceSlug: string,
if (!viewId) return undefined; projectId: string,
loadType: TLoader,
const displayFilters = this.rootIssueStore?.projectViewIssuesFilter?.issueFilters?.displayFilters; options: IssuePaginationOptions
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) => {
try { try {
runInAction(() => {
this.loader = loadType; 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); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
runInAction(() => { this.onfetchIssues(response, options);
set(
this.issues,
[viewId],
response.map((issue) => issue.id)
);
this.loader = undefined;
});
this.rootIssueStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; throw error;
} }
}; };
createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, viewId: string) => { fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
if (!this.paginationOptions) return;
try { try {
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); this.loader = "pagination";
runInAction(() => { const params = this.issueFilterStore?.getFilterParams(this.paginationOptions);
this.issues[viewId].push(response.id); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
});
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation", viewId); this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async ( fetchIssuesWithExistingPagination = async (workspaceSlug: string, projectId: string, loadType: TLoader) => {
workspaceSlug: string, if (!this.paginationOptions) return;
projectId: string, return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions);
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;
}
}; };
} }

View File

@ -4,7 +4,6 @@ import pickBy from "lodash/pickBy";
import set from "lodash/set"; import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { action, computed, makeObservable, observable, runInAction } from "mobx";
// base class // base class
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
import { IssueFiltersService } from "services/issue_filter.service"; import { IssueFiltersService } from "services/issue_filter.service";
import { import {
@ -14,20 +13,23 @@ import {
TIssueKanbanFilters, TIssueKanbanFilters,
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
// constants // constants
import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue";
import { computedFn } from "mobx-utils";
// services // services
export interface IProjectIssuesFilter { export interface IProjectIssuesFilter extends IBaseIssueFilterStore {
// observables //helper actions
filters: Record<string, IIssueFilters>; // Record defines projectId as key and IIssueFilters as value getFilterParams: (
// computed options: IssuePaginationOptions,
issueFilters: IIssueFilters | undefined; cursor?: string
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined; ) => Partial<Record<TIssueParams, string | boolean>>;
// action // action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -92,6 +94,22 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
return filteredRouteParams; 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) => { fetchFilters = async (workspaceSlug: string, projectId: string) => {
try { try {
const _filters = await this.issueFilterService.fetchProjectIssueFilters(workspaceSlug, projectId); const _filters = await this.issueFilterService.fetchProjectIssueFilters(workspaceSlug, projectId);
@ -157,7 +175,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
const appliedFilters = _filters.filters || {}; const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.projectIssues.fetchIssues( this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
projectId, projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation" isEmpty(filteredFilters) ? "init-loader" : "mutation"
@ -200,7 +218,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.projectIssues.fetchIssues(workspaceSlug, projectId, "mutation"); this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, { await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {
display_filters: _filters.displayFilters, 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 // services
// types // types
import { action, makeObservable, runInAction } from "mobx";
// base class
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
// types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse, TIssue } from "@plane/types";
import { IProjectIssuesFilter } from "./filter.store";
export interface IProjectIssues { export interface IProjectIssues extends IBaseIssuesStore {
// observable
loader: TLoader;
issues: Record<string, string[]>; // Record of project_id as key and issue_ids as value
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
// action // 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>; createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>; 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>; 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>; removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
} }
export class ProjectIssues extends IssueHelperStore implements IProjectIssues { export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
// observable
loader: TLoader = "init-loader";
issues: Record<string, string[]> = {};
viewFlags = { viewFlags = {
enableQuickAdd: true, enableQuickAdd: true,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store
rootIssueStore: IIssueRootStore;
// services
issueService;
issueArchiveService;
constructor(_rootStore: IIssueRootStore) { // filter store
super(_rootStore); issueFilterStore: IProjectIssuesFilter;
constructor(_rootStore: IIssueRootStore, issueFilterStore: IProjectIssuesFilter) {
super(_rootStore, issueFilterStore);
makeObservable(this, { makeObservable(this, {
// observable
loader: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action
fetchIssues: action, fetchIssues: action,
createIssue: action, fetchNextIssues: action,
updateIssue: action, fetchIssuesWithExistingPagination: action,
removeIssue: action,
archiveIssue: action,
removeBulkIssues: action,
quickAddIssue: action,
}); });
// root store // filter store
this.rootIssueStore = _rootStore; this.issueFilterStore = issueFilterStore;
// services
this.issueService = new IssueService();
this.issueArchiveService = new IssueArchiveService();
} }
get groupedIssueIds() { fetchIssues = async (
const projectId = this.rootStore?.projectId; workspaceSlug: string,
if (!projectId) return undefined; projectId: string,
loadType: TLoader = "init-loader",
const displayFilters = this.rootStore?.projectIssuesFilter?.issueFilters?.displayFilters; options: IssuePaginationOptions
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") => {
try { try {
runInAction(() => {
this.loader = loadType; 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); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
runInAction(() => { this.onfetchIssues(response, options);
set(
this.issues,
[projectId],
response.map((issue) => issue.id)
);
this.loader = undefined;
});
this.rootStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined; 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 { try {
const response = await this.issueService.createIssue(workspaceSlug, projectId, data); this.loader = "pagination";
runInAction(() => { const params = this.issueFilterStore?.getFilterParams(this.paginationOptions);
update(this.issues, [projectId], (issueIds) => { const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
if (!issueIds) return [response.id];
return concat(issueIds, response.id);
});
});
this.rootStore.issues.addIssue([response]);
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => { fetchIssuesWithExistingPagination = async (
try { workspaceSlug: string,
this.rootStore.issues.updateIssue(issueId, data); projectId: string,
loadType: TLoader = "mutation"
await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); ) => {
} catch (error) { if (!this.paginationOptions) return;
this.fetchIssues(workspaceSlug, projectId, "mutation"); return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions);
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;
}
}; };
} }

View File

@ -184,28 +184,28 @@ export class IssueRootStore implements IIssueRootStore {
this.issueDetail = new IssueDetail(this); this.issueDetail = new IssueDetail(this);
this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this); this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this);
this.workspaceIssues = new WorkspaceIssues(this); this.workspaceIssues = new WorkspaceIssues(this, this.workspaceIssuesFilter);
this.profileIssuesFilter = new ProfileIssuesFilter(this); this.profileIssuesFilter = new ProfileIssuesFilter(this);
this.profileIssues = new ProfileIssues(this); this.profileIssues = new ProfileIssues(this, this.profileIssuesFilter);
this.projectIssuesFilter = new ProjectIssuesFilter(this); this.projectIssuesFilter = new ProjectIssuesFilter(this);
this.projectIssues = new ProjectIssues(this); this.projectIssues = new ProjectIssues(this, this.projectIssuesFilter);
this.cycleIssuesFilter = new CycleIssuesFilter(this); this.cycleIssuesFilter = new CycleIssuesFilter(this);
this.cycleIssues = new CycleIssues(this); this.cycleIssues = new CycleIssues(this, this.cycleIssuesFilter);
this.moduleIssuesFilter = new ModuleIssuesFilter(this); this.moduleIssuesFilter = new ModuleIssuesFilter(this);
this.moduleIssues = new ModuleIssues(this); this.moduleIssues = new ModuleIssues(this, this.moduleIssuesFilter);
this.projectViewIssuesFilter = new ProjectViewIssuesFilter(this); this.projectViewIssuesFilter = new ProjectViewIssuesFilter(this);
this.projectViewIssues = new ProjectViewIssues(this); this.projectViewIssues = new ProjectViewIssues(this, this.projectViewIssuesFilter);
this.archivedIssuesFilter = new ArchivedIssuesFilter(this); this.archivedIssuesFilter = new ArchivedIssuesFilter(this);
this.archivedIssues = new ArchivedIssues(this); this.archivedIssues = new ArchivedIssues(this, this.archivedIssuesFilter);
this.draftIssuesFilter = new DraftIssuesFilter(this); this.draftIssuesFilter = new DraftIssuesFilter(this);
this.draftIssues = new DraftIssues(this); this.draftIssues = new DraftIssues(this, this.draftIssuesFilter);
this.issueKanBanView = new IssueKanBanViewStore(this); this.issueKanBanView = new IssueKanBanViewStore(this);
this.issueCalendarView = new CalendarStore(); this.issueCalendarView = new CalendarStore();

View File

@ -15,21 +15,19 @@ import {
IIssueFilters, IIssueFilters,
TIssueParams, TIssueParams,
TStaticViewTypes, TStaticViewTypes,
IssuePaginationOptions,
} from "@plane/types"; } from "@plane/types";
import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers // helpers
// types // types
import { IIssueRootStore } from "../root.store"; import { IIssueRootStore } from "../root.store";
import { computedFn } from "mobx-utils";
// constants // constants
// services // services
type TWorkspaceFilters = "all-issues" | "assigned" | "created" | "subscribed" | string; type TWorkspaceFilters = "all-issues" | "assigned" | "created" | "subscribed" | string;
export interface IWorkspaceIssuesFilter {
// observables export interface IWorkspaceIssuesFilter extends IBaseIssueFilterStore {
filters: Record<TWorkspaceFilters, IIssueFilters>; // Record defines viewId as key and IIssueFilters as value
// computed
issueFilters: IIssueFilters | undefined;
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
// fetch action // fetch action
fetchFilters: (workspaceSlug: string, viewId: string) => Promise<void>; fetchFilters: (workspaceSlug: string, viewId: string) => Promise<void>;
updateFilters: ( updateFilters: (
@ -42,6 +40,11 @@ export interface IWorkspaceIssuesFilter {
//helper action //helper action
getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined; getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined;
getAppliedFilters: (viewId: string) => Partial<Record<TIssueParams, string | boolean>> | 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 { export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter {
@ -63,9 +66,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
// fetch actions // fetch actions
fetchFilters: action, fetchFilters: action,
updateFilters: action, updateFilters: action,
// helper actions
getIssueFilters: action,
getAppliedFilters: action,
}); });
// root store // root store
this.rootIssueStore = _rootStore; this.rootIssueStore = _rootStore;
@ -102,6 +102,22 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
return filteredRouteParams; 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() { get issueFilters() {
const viewId = this.rootIssueStore.globalViewId; const viewId = this.rootIssueStore.globalViewId;
return this.getIssueFilters(viewId); return this.getIssueFilters(viewId);
@ -182,7 +198,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
}); });
const appliedFilters = _filters.filters || {}; const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0); const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.workspaceIssues.fetchIssues( this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(
workspaceSlug, workspaceSlug,
viewId, viewId,
isEmpty(filteredFilters) ? "init-loader" : "mutation" isEmpty(filteredFilters) ? "init-loader" : "mutation"
@ -222,7 +238,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
}); });
if (this.requiresServerUpdate(updatedDisplayFilters)) 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)) if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, { this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {

View File

@ -1,215 +1,93 @@
import pull from "lodash/pull"; import { action, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
import { action, observable, makeObservable, computed, runInAction } from "mobx";
// base class // base class
import { IssueService, IssueArchiveService } from "services/issue";
import { WorkspaceService } from "services/workspace.service"; import { WorkspaceService } from "services/workspace.service";
import { TIssue, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; import { IssuePaginationOptions, TIssue, TIssuesResponse, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
// services // services
// types // types
import { IIssueRootStore } from "../root.store"; 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 // observable
loader: TLoader;
issues: { [viewId: string]: string[] };
viewFlags: ViewFlags; viewFlags: ViewFlags;
// computed
groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined };
// actions // actions
fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>; fetchIssues: (
createIssue: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, viewId: string,
data: Partial<TIssue>, loadType: TLoader,
viewId: string options: IssuePaginationOptions
) => Promise<TIssue | undefined>; ) => Promise<TIssuesResponse | undefined>;
updateIssue: ( fetchIssuesWithExistingPagination: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, viewId: string,
issueId: string, loadType: TLoader
data: Partial<TIssue>, ) => Promise<TIssuesResponse | undefined>;
viewId: string fetchNextIssues: (workspaceSlug: string, viewId: string) => Promise<TIssuesResponse | undefined>;
) => Promise<void>; createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string, viewId: string) => Promise<void>; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: ( archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
workspaceSlug: string,
projectId: string,
issueId: string,
viewId?: string | undefined
) => Promise<void>;
quickAddIssue: undefined;
} }
export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues { export class WorkspaceIssues extends BaseIssuesStore implements IWorkspaceIssues {
loader: TLoader = "init-loader";
issues: { [viewId: string]: string[] } = {};
viewFlags = { viewFlags = {
enableQuickAdd: true, enableQuickAdd: true,
enableIssueCreation: true, enableIssueCreation: true,
enableInlineEditing: true, enableInlineEditing: true,
}; };
// root store
rootIssueStore: IIssueRootStore;
// service // service
workspaceService; workspaceService;
issueService; // filterStore
issueArchiveService; issueFilterStore;
quickAddIssue = undefined; constructor(_rootStore: IIssueRootStore, issueFilterStore: IWorkspaceIssuesFilter) {
super(_rootStore, issueFilterStore);
constructor(_rootStore: IIssueRootStore) {
super(_rootStore);
makeObservable(this, { makeObservable(this, {
// observable
loader: observable.ref,
issues: observable,
// computed
groupedIssueIds: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action,
updateIssue: action,
removeIssue: action,
archiveIssue: action,
}); });
// root store
this.rootIssueStore = _rootStore;
// services // services
this.workspaceService = new WorkspaceService(); this.workspaceService = new WorkspaceService();
this.issueService = new IssueService(); // filter store
this.issueArchiveService = new IssueArchiveService(); this.issueFilterStore = issueFilterStore;
} }
get groupedIssueIds() { fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader, options: IssuePaginationOptions) => {
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") => {
try { try {
runInAction(() => {
this.loader = loadType; this.loader = loadType;
});
const uniqueViewId = `${workspaceSlug}_${viewId}`; this.clear();
const params = this.issueFilterStore?.getFilterParams(viewId, options, undefined);
const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId);
const response = await this.workspaceService.getViewIssues(workspaceSlug, params); const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
runInAction(() => { this.onfetchIssues(response, options);
set(
this.issues,
[uniqueViewId],
response.map((issue) => issue.id)
);
this.loader = undefined;
});
this.rootIssueStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; throw error;
} }
}; };
createIssue = async (workspaceSlug: string, projectId: string, data: Partial<TIssue>, viewId: string) => { fetchNextIssues = async (workspaceSlug: string, viewId: string) => {
if (!this.paginationOptions) return;
try { try {
const uniqueViewId = `${workspaceSlug}_${viewId}`; this.loader = "pagination";
const response = await this.issueService.createIssue(workspaceSlug, projectId, data); const params = this.issueFilterStore?.getFilterParams(viewId, this.paginationOptions, this.nextCursor);
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
runInAction(() => {
this.issues[uniqueViewId].push(response.id);
});
this.rootStore.issues.addIssue([response]);
this.onfetchNexIssues(response);
return response; return response;
} catch (error) { } catch (error) {
this.loader = undefined;
throw error; throw error;
} }
}; };
updateIssue = async ( fetchIssuesWithExistingPagination = async (workspaceSlug: string, viewId: string, loadType: TLoader) => {
workspaceSlug: string, if (!this.paginationOptions) return;
projectId: string, return await this.fetchIssues(workspaceSlug, viewId, loadType, this.paginationOptions);
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;
}
}; };
} }