[WEB-1249] feat: Kanban multi dragndrop (#4479)

* Kanban multi dnd

* complete Kanban multi dnd

* add proper brackets to if conditions
This commit is contained in:
rahulramesha 2024-05-16 17:29:01 +05:30 committed by GitHub
parent bab52a2672
commit 1ad7011aac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 565 additions and 333 deletions

View file

@ -60,6 +60,7 @@ export interface ICycleIssues {
) => Promise<void>;
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
removeCycleFromIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>
transferIssuesFromCycle: (
workspaceSlug: string,
projectId: string,
@ -273,7 +274,13 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
if (data.module_ids && data.module_ids.length > 0)
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
await this.rootStore.moduleIssues.changeModulesInIssue(
workspaceSlug,
projectId,
response.id,
data.module_ids,
[]
);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
@ -327,6 +334,36 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
}
};
/**
* Remove a cycle from issue
* @param workspaceSlug
* @param projectId
* @param issueId
* @returns
*/
removeCycleFromIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id;
if(!issueCycleId) return;
try {
// perform optimistic update, update store
runInAction(() => {
pull(this.issues[issueCycleId], issueId);
});
this.rootStore.issues.updateIssue(issueId, { cycle_id: null });
// make API call
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, issueCycleId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, issueCycleId);
} catch (error) {
// revert back changes if fails
runInAction(() => {
update(this.issues, issueCycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, [issueId])));
});
this.rootStore.issues.updateIssue(issueId, { cycle_id: issueCycleId });
throw error;
}
};
removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
runInAction(() => {

View file

@ -197,11 +197,12 @@ export class IssueStore implements IIssueStore {
};
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.addModulesToIssue(
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.changeModulesInIssue(
workspaceSlug,
projectId,
issueId,
moduleIds
moduleIds,
[]
);
if (moduleIds && moduleIds.length > 0)
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
@ -209,10 +210,11 @@ export class IssueStore implements IIssueStore {
};
removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeModulesFromIssue(
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.changeModulesInIssue(
workspaceSlug,
projectId,
issueId,
[],
moduleIds
);
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);

View file

@ -22,6 +22,8 @@ export interface IIssueKanBanViewStore {
setIsDragging: (isDragging: boolean) => void;
}
const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = ["state", "priority", "assignees", "labels", "module", "cycle"];
export class IssueKanBanViewStore implements IIssueKanBanViewStore {
kanBanToggle: {
groupByHeaderMinMax: string[];
@ -53,9 +55,9 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
getCanUserDragDrop = computedFn(
(group_by: TIssueGroupByOptions | undefined, sub_group_by: TIssueGroupByOptions | undefined) => {
if (group_by && ["state", "priority"].includes(group_by)) {
if (group_by && DRAG_ALLOWED_GROUPS.includes(group_by)) {
if (!sub_group_by) return true;
if (sub_group_by && ["state", "priority"].includes(sub_group_by)) return true;
if (sub_group_by && DRAG_ALLOWED_GROUPS.includes(sub_group_by)) return true;
}
return false;
}

View file

@ -1,5 +1,6 @@
import concat from "lodash/concat";
import pull from "lodash/pull";
import isEmpty from "lodash/isEmpty";
import set from "lodash/set";
import uniq from "lodash/uniq";
import update from "lodash/update";
@ -63,12 +64,12 @@ export interface IModuleIssues {
moduleId: string,
issueIds: string[]
) => Promise<void>;
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<void>;
removeModulesFromIssue: (
changeModulesInIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
moduleIds: string[]
addModuleIds: string[],
removeModuleIds: string[]
) => Promise<void>;
}
@ -104,8 +105,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
quickAddIssue: action,
addIssuesToModule: action,
removeIssuesFromModule: action,
addModulesToIssue: action,
removeModulesFromIssue: action,
changeModulesInIssue: action,
});
this.rootIssueStore = _rootStore;
@ -368,67 +368,78 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
}
};
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
/**
* change modules array in issue
* @param workspaceSlug
* @param projectId
* @param issueId
* @param addModuleIds array of modules to be added
* @param removeModuleIds array of modules to be removed
*/
changeModulesInIssue = async (
workspaceSlug: string,
projectId: string,
issueId: string,
addModuleIds: string[],
removeModuleIds: string[]
) => {
// keep a copy of the original module ids
const originalModuleIds = this.rootStore.issues.issuesMap[issueId]?.module_ids ?? [];
const originalModuleIds = this.rootStore.issues.issuesMap[issueId]?.module_ids
? [...this.rootStore.issues.issuesMap[issueId].module_ids!]
: [];
try {
runInAction(() => {
// add the new issue ids to the module issues map
moduleIds.forEach((moduleId) => {
// remove the new issue id to the module issues map
removeModuleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
else return moduleIssueIds;
});
});
// add the new issue id to the module issues map
addModuleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
else return uniq(concat(moduleIssueIds, [issueId]));
});
});
});
if(originalModuleIds){
// update the root issue map with the new module ids
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
uniq(concat(issueModuleIds, moduleIds))
);
});
let currentModuleIds = concat([...originalModuleIds], addModuleIds);
currentModuleIds = pull(currentModuleIds, ...removeModuleIds);
this.rootStore.issues.updateIssue(issueId, { module_ids: uniq(currentModuleIds) });
}
const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
modules: moduleIds,
});
//Perform API calls
if (!isEmpty(addModuleIds)) {
await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
modules: addModuleIds,
});
}
if (!isEmpty(removeModuleIds)) {
await this.moduleService.removeModulesFromIssueBulk(workspaceSlug, projectId, issueId, removeModuleIds);
}
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));
// add the removed issue id to the module issues map
addModuleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
else return moduleIssueIds;
});
});
// remove the added issue id to the module issues map
removeModuleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
else return uniq(concat(moduleIssueIds, [issueId]));
});
});
throw error;
}
};
removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
runInAction(() => {
moduleIds.forEach((moduleId) => {
update(this.issues, moduleId, (moduleIssueIds = []) => {
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
else return uniq(concat(moduleIssueIds, [issueId]));
});
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
pull(issueModuleIds, moduleId)
);
});
});
const response = await this.moduleService.removeModulesFromIssueBulk(
workspaceSlug,
projectId,
issueId,
moduleIds
);
return response;
} catch (error) {
throw error;
}
};
}

View file

@ -243,7 +243,13 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
if (data.module_ids && data.module_ids.length > 0)
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
await this.rootStore.moduleIssues.changeModulesInIssue(
workspaceSlug,
projectId,
response.id,
data.module_ids,
[]
);
const quickAddIssueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === data.id);
if (quickAddIssueIndex >= 0)

View file

@ -227,7 +227,13 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
if (data.module_ids && data.module_ids.length > 0)
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
await this.rootStore.moduleIssues.changeModulesInIssue(
workspaceSlug,
projectId,
response.id,
data.module_ids,
[]
);
const quickAddIssueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === data.id);
if (quickAddIssueIndex >= 0)