[WEB-1142] chore: optimistically add issue to cycle/modules (#4334)

* chore: optimistically add issue to cycle and module

* chore: update toast alerts

* refactor: module issue store

* chore: added addCycleToIssueFunction
This commit is contained in:
Aaryan Khandelwal 2024-05-07 14:05:56 +05:30 committed by GitHub
parent a85517de99
commit 780b239ecb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 235 additions and 141 deletions

View File

@ -34,7 +34,7 @@ export const IssueCycleSelect: React.FC<TIssueCycleSelect> = observer((props) =>
const handleIssueCycleChange = async (cycleId: string | null) => {
if (!issue || issue.cycle_id === cycleId) return;
setIsUpdating(true);
if (cycleId) await issueOperations.addIssueToCycle?.(workspaceSlug, projectId, cycleId, [issueId]);
if (cycleId) await issueOperations.addCycleToIssue?.(workspaceSlug, projectId, cycleId, issueId);
else await issueOperations.removeIssueFromCycle?.(workspaceSlug, projectId, issue.cycle_id ?? "", issueId);
setIsUpdating(false);
};

View File

@ -26,6 +26,7 @@ export type TIssueOperations = {
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archive?: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
restore?: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
addCycleToIssue?: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addIssueToCycle?: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>;
removeIssueFromCycle?: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addModulesToIssue?: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<void>;
@ -62,6 +63,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
updateIssue,
removeIssue,
archiveIssue,
addCycleToIssue,
addIssueToCycle,
removeIssueFromCycle,
addModulesToIssue,
@ -158,21 +160,38 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
});
}
},
addCycleToIssue: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
await addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the cycle. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
}
},
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
try {
const addToCyclePromise = addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setPromiseToast(addToCyclePromise, {
loading: "Adding cycle to issue...",
success: {
title: "Success!",
message: () => "Cycle added to issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle add to issue failed",
},
});
await addToCyclePromise;
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issueIds, state: "SUCCESS", element: "Issue detail page" },
@ -183,6 +202,11 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the cycle. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue detail page" },
@ -198,14 +222,14 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
try {
const removeFromCyclePromise = removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setPromiseToast(removeFromCyclePromise, {
loading: "Removing cycle from issue...",
loading: "Removing issue from the cycle...",
success: {
title: "Success!",
message: () => "Cycle removed from issue successfully",
message: () => "Issue removed from the cycle successfully.",
},
error: {
title: "Error!",
message: () => "Cycle remove from issue failed",
message: () => "Issue could not be removed from the cycle. Please try again.",
},
});
await removeFromCyclePromise;
@ -232,19 +256,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
},
addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
const addToModulePromise = addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(addToModulePromise, {
loading: "Adding module to issue...",
success: {
title: "Success!",
message: () => "Module added to issue successfully",
},
error: {
title: "Error!",
message: () => "Module add to issue failed",
},
});
const response = await addToModulePromise;
const response = await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
@ -255,6 +267,11 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the module. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
@ -270,14 +287,14 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
try {
const removeFromModulePromise = removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setPromiseToast(removeFromModulePromise, {
loading: "Removing module from issue...",
loading: "Removing issue from the module...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
message: () => "Issue removed from the module successfully.",
},
error: {
title: "Error!",
message: () => "Module remove from issue failed",
message: () => "Issue could not be removed from the module. Please try again.",
},
});
await removeFromModulePromise;
@ -335,6 +352,8 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
addModulesToIssue,
removeIssueFromModule,
removeModulesFromIssue,
captureIssueEvent,
router.asPath,
]
);

View File

@ -1,19 +1,18 @@
import { FC, useEffect, useState, useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// types
import { TIssue } from "@plane/types";
// hooks
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
import { IssueView } from "@/components/issues";
// ui
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// components
import { IssueView } from "@/components/issues";
// constants
import { ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED, ISSUE_RESTORED } from "@/constants/event-tracker";
import { EIssuesStoreType } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useEventTracker, useIssueDetail, useIssues, useUser } from "@/hooks/store";
// components
// types
// constants
interface IIssuePeekOverview {
is_archived?: boolean;
@ -60,8 +59,14 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
archiveIssue,
issue: { getIssueById, fetchIssue },
} = useIssueDetail();
const { addIssueToCycle, removeIssueFromCycle, addModulesToIssue, removeIssueFromModule, removeModulesFromIssue } =
useIssueDetail();
const {
addCycleToIssue,
addIssueToCycle,
removeIssueFromCycle,
addModulesToIssue,
removeIssueFromModule,
removeModulesFromIssue,
} = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
// state
const [loader, setLoader] = useState(false);
@ -174,21 +179,39 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
});
}
},
addCycleToIssue: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
console.log("Peek adding...");
await addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the cycle. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue peek-overview" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
}
},
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
try {
const addToCyclePromise = addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setPromiseToast(addToCyclePromise, {
loading: "Adding cycle to issue...",
success: {
title: "Success!",
message: () => "Cycle added to issue successfully",
},
error: {
title: "Error!",
message: () => "Cycle add to issue failed",
},
});
await addToCyclePromise;
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...issueIds, state: "SUCCESS", element: "Issue peek-overview" },
@ -199,6 +222,11 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the cycle. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { state: "FAILED", element: "Issue peek-overview" },
@ -214,14 +242,14 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
try {
const removeFromCyclePromise = removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setPromiseToast(removeFromCyclePromise, {
loading: "Removing cycle from issue...",
loading: "Removing issue from the cycle...",
success: {
title: "Success!",
message: () => "Cycle removed from issue successfully",
message: () => "Issue removed from the cycle successfully.",
},
error: {
title: "Error!",
message: () => "Cycle remove from issue failed",
message: () => "Issue could not be removed from the cycle. Please try again.",
},
});
await removeFromCyclePromise;
@ -248,19 +276,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
},
addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
const addToModulePromise = addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setPromiseToast(addToModulePromise, {
loading: "Adding module to issue...",
success: {
title: "Success!",
message: () => "Module added to issue successfully",
},
error: {
title: "Error!",
message: () => "Module add to issue failed",
},
});
const response = await addToModulePromise;
const response = await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
@ -271,6 +287,11 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
path: router.asPath,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be added to the module. Please try again.",
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
@ -286,14 +307,14 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
try {
const removeFromModulePromise = removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
setPromiseToast(removeFromModulePromise, {
loading: "Removing module from issue...",
loading: "Removing issue from the module...",
success: {
title: "Success!",
message: () => "Module removed from issue successfully",
message: () => "Issue removed from the module successfully.",
},
error: {
title: "Error!",
message: () => "Module remove from issue failed",
message: () => "Issue could not be removed from the module. Please try again.",
},
});
await removeFromModulePromise;

View File

@ -1,5 +1,5 @@
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
import { useApplication, useIssues } from "./store";
import { useCallback, useMemo } from "react";
// types
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
@ -8,7 +8,10 @@ import {
TIssueKanbanFilters,
TLoader,
} from "@plane/types";
import { useCallback, useMemo } from "react";
// constants
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
// hooks
import { useApplication, useIssues } from "./store";
interface IssueActions {
fetchIssues?: (projectId: string, loadType: TLoader) => Promise<TIssue[] | undefined>;
@ -238,9 +241,9 @@ const useModuleIssueActions = () => {
const removeIssueFromView = useCallback(
async (projectId: string, issueId: string) => {
if (!moduleId || !workspaceSlug) return;
return await issues.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
return await issues.removeIssuesFromModule(workspaceSlug, projectId, moduleId, [issueId]);
},
[issues.removeIssueFromModule, moduleId, workspaceSlug]
[issues.removeIssuesFromModule, moduleId, workspaceSlug]
);
const archiveIssue = useCallback(
async (projectId: string, issueId: string) => {

View File

@ -1,9 +1,9 @@
// services
import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service";
// type
// types
import type { TIssue, IIssueDisplayProperties, TIssueLink, TIssueSubIssues, TIssueActivity } from "@plane/types";
// helper
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
// services
import { APIService } from "@/services/api.service";
export class IssueService extends APIService {
constructor() {

View File

@ -1,8 +1,8 @@
// types
import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types";
// services
import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service";
// types
import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types";
export class ModuleService extends APIService {
constructor() {
@ -106,19 +106,6 @@ export class ModuleService extends APIService {
});
}
async removeIssueFromModule(
workspaceSlug: string,
projectId: string,
moduleId: string,
issueId: string
): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removeIssuesFromModuleBulk(
workspaceSlug: string,
projectId: string,

View File

@ -4,12 +4,12 @@ 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
// types
import { TIssue, TSubGroupedIssues, TGroupedIssues, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types";
// services
import { CycleService } from "@/services/cycle.service";
import { IssueService } from "@/services/issue";
// types
import { TIssue, TSubGroupedIssues, TGroupedIssues, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types";
import { IssueHelperStore } from "../helpers/issue-helper.store";
import { IIssueRootStore } from "../root.store";
@ -59,6 +59,7 @@ export interface ICycleIssues {
fetchAddedIssues?: boolean
) => Promise<void>;
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
transferIssuesFromCycle: (
workspaceSlug: string,
projectId: string,
@ -101,6 +102,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
quickAddIssue: action,
addIssueToCycle: action,
removeIssueFromCycle: action,
addCycleToIssue: action,
transferIssuesFromCycle: action,
fetchActiveCycleIssues: action,
});
@ -303,18 +305,22 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
// add the new issue ids to the cycle issues map
runInAction(() => {
update(this.issues, cycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds)));
});
issueIds.forEach((issueId) => {
const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id;
// remove issue from previous cycle if it exists
if (issueCycleId && issueCycleId !== cycleId) {
runInAction(() => {
pull(this.issues[issueCycleId], issueId);
});
}
// update the root issue map with the new cycle id
this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId });
});
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
} catch (error) {
throw error;
@ -336,6 +342,43 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
}
};
addCycleToIssue = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id;
try {
// add the new issue ids to the cycle issues map
runInAction(() => {
update(this.issues, cycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, [issueId])));
});
// remove issue from previous cycle if it exists
if (issueCycleId && issueCycleId !== cycleId) {
runInAction(() => {
pull(this.issues[issueCycleId], issueId);
});
}
// update the root issue map with the new cycle id
this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId });
await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, {
issues: [issueId],
});
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
} catch (error) {
// remove the new issue ids from the cycle issues map
runInAction(() => {
pull(this.issues[cycleId], issueId);
});
// add issue back to the previous cycle if it exists
if (issueCycleId)
runInAction(() => {
update(this.issues, issueCycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, [issueId])));
});
// update the root issue map with the original cycle id
this.rootStore.issues.updateIssue(issueId, { cycle_id: issueCycleId ?? null });
throw error;
}
};
transferIssuesFromCycle = async (
workspaceSlug: string,
projectId: string,

View File

@ -1,9 +1,10 @@
import { makeObservable } from "mobx";
// services
import { computedFn } from "mobx-utils";
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
// types
import { TIssue } from "@plane/types";
// services
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
// types
import { IIssueDetail } from "./root.store";
export interface IIssueStoreActions {
@ -17,6 +18,7 @@ export interface IIssueStoreActions {
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>;
addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>;
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<any>;
@ -161,6 +163,16 @@ export class IssueStore implements IIssueStore {
archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.rootIssueDetailStore.rootIssueStore.projectIssues.archiveIssue(workspaceSlug, projectId, issueId);
addCycleToIssue = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
await this.rootIssueDetailStore.rootIssueStore.cycleIssues.addCycleToIssue(
workspaceSlug,
projectId,
cycleId,
issueId
);
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
};
addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
await this.rootIssueDetailStore.rootIssueStore.cycleIssues.addIssueToCycle(
workspaceSlug,
@ -208,11 +220,11 @@ export class IssueStore implements IIssueStore {
};
removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeIssueFromModule(
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeIssuesFromModule(
workspaceSlug,
projectId,
moduleId,
issueId
[issueId]
);
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
return currentModule;

View File

@ -191,6 +191,8 @@ export class IssueDetail implements IIssueDetail {
this.issue.removeIssue(workspaceSlug, projectId, issueId);
archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.issue.archiveIssue(workspaceSlug, projectId, issueId);
addCycleToIssue = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) =>
this.issue.addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) =>
this.issue.addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) =>

View File

@ -1,13 +1,13 @@
import isEmpty from "lodash/isEmpty";
import set from "lodash/set";
// store
import { action, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// types
import { IssueService } from "@/services/issue";
import { TIssue } from "@plane/types";
// helpers
import { getCurrentDateTimeInISO } from "@/helpers/date-time.helper";
//services
// services
import { IssueService } from "@/services/issue";
export type IIssueStore = {
// observables

View File

@ -4,13 +4,14 @@ 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
// types
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types";
// services
import { IssueService } from "@/services/issue";
import { ModuleService } from "@/services/module.service";
// types
import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types";
// helpers
import { IssueHelperStore } from "../helpers/issue-helper.store";
// store
import { IIssueRootStore } from "../root.store";
export interface IModuleIssues {
@ -69,7 +70,6 @@ export interface IModuleIssues {
issueId: string,
moduleIds: string[]
) => Promise<void>;
removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>;
}
export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
@ -106,7 +106,6 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
removeIssuesFromModule: action,
addModulesToIssue: action,
removeModulesFromIssue: action,
removeIssueFromModule: action,
});
this.rootIssueStore = _rootStore;
@ -301,27 +300,39 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
fetchAddedIssues = true
) => {
try {
await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, {
issues: issueIds,
});
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
// add the new issue ids to the module issues map
runInAction(() => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (!moduleIssueIds) return [...issueIds];
else return uniq(concat(moduleIssueIds, issueIds));
});
});
// update the root issue map with the new module ids
issueIds.forEach((issueId) => {
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) => {
if (issueModuleIds.includes(moduleId)) return issueModuleIds;
else return uniq(concat(issueModuleIds, [moduleId]));
});
});
await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, {
issues: issueIds,
});
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
} catch (error) {
issueIds.forEach((issueId) => {
runInAction(() => {
// remove the new issue ids from the module issues map
pull(this.issues[moduleId], issueId);
// remove the new module ids from the root issue map
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
pull(issueModuleIds, moduleId)
);
});
});
throw error;
}
};
@ -358,25 +369,38 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
};
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
// keep a copy of the original module ids
const originalModuleIds = this.rootStore.issues.issuesMap[issueId]?.module_ids ?? [];
try {
const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
modules: moduleIds,
});
runInAction(() => {
// add the new issue ids to the module issues map
moduleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
else return uniq(concat(moduleIssueIds, [issueId]));
});
});
// update the root issue map with the new module ids
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
uniq(concat(issueModuleIds, moduleIds))
);
});
const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
modules: moduleIds,
});
return issueToModule;
} catch (error) {
// revert the issue back to its original module ids
set(this.rootStore.issues.issuesMap, [issueId, "module_ids"], originalModuleIds);
// remove the new issue ids from the module issues map
moduleIds.forEach((moduleId) => {
runInAction(() => {
update(this.issues, moduleId, (moduleIssueIds = []) => pull(moduleIssueIds, issueId));
});
});
throw error;
}
};
@ -407,21 +431,4 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
throw error;
}
};
removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
try {
runInAction(() => {
pull(this.issues[moduleId], issueId);
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
pull(issueModuleIds, moduleId)
);
});
const response = await this.moduleService.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
return response;
} catch (error) {
throw error;
}
};
}

View File

@ -1,8 +1,8 @@
import isEmpty from "lodash/isEmpty";
import { autorun, makeObservable, observable } from "mobx";
import { ICycle, IIssueLabel, IModule, IProject, IState, IUserLite } from "@plane/types";
// root store
import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
import { ICycle, IIssueLabel, IModule, IProject, IState, IUserLite } from "@plane/types";
import { RootStore } from "../root.store";
import { IStateStore, StateStore } from "../state.store";
// issues data store