From be41a91a726469676548a9e5b10d7cac7a4cdae0 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 12 Dec 2023 17:47:02 +0530 Subject: [PATCH 1/5] fix: pages store changes --- web/services/page.service.ts | 8 ++ web/store/application/router.store.ts | 2 +- web/store/page.store.ts | 196 ++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 web/store/page.store.ts diff --git a/web/services/page.service.ts b/web/services/page.service.ts index 83b19c926..5ee2692ed 100644 --- a/web/services/page.service.ts +++ b/web/services/page.service.ts @@ -50,6 +50,14 @@ export class PageService extends APIService { }); } + async getProjectPages(workspaceSlug: string, projectId: string) { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async getPagesWithParams( workspaceSlug: string, projectId: string, diff --git a/web/store/application/router.store.ts b/web/store/application/router.store.ts index f275f0898..222d896e3 100644 --- a/web/store/application/router.store.ts +++ b/web/store/application/router.store.ts @@ -1,5 +1,5 @@ import { action, makeObservable, observable } from "mobx"; -import { ParsedUrlQuery } from "querystring"; +import { ParsedUrlQuery } from "node:querystring"; export interface IRouterStore { query: ParsedUrlQuery; diff --git a/web/store/page.store.ts b/web/store/page.store.ts new file mode 100644 index 000000000..b27beec86 --- /dev/null +++ b/web/store/page.store.ts @@ -0,0 +1,196 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import keyBy from "lodash/keyBy"; +import isToday from "date-fns/isToday"; +import isThisWeek from "date-fns/isThisWeek"; +import isYesterday from "date-fns/isYesterday"; +// services +import { PageService } from "services/page.service"; +// types +import { IPage, IRecentPages } from "types"; +// store +import { RootStore } from "./root.store"; + +export interface IPageStore { + pages: Record; + archivedPages: Record; + + projectPages: IPage[] | undefined; + favoriteProjectPages: IPage[] | undefined; + privateProjectPages: IPage[] | undefined; + sharedProjectPages: IPage[] | undefined; + + fetchProjectPages: (workspaceSlug: string, projectId: string) => Promise; +} + +export class PageStore { + pages: Record = {}; + archivedPages: Record = {}; + // services + pageService; + // stores + router; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + pages: observable.ref, + archivedPages: observable.ref, + // computed + projectPages: computed, + favoriteProjectPages: computed, + sharedProjectPages: computed, + privateProjectPages: computed, + // actions + fetchProjectPages: action, + }); + // stores + this.router = _rootStore.app.router; + // services + this.pageService = new PageService(); + } + + /** + * retrieves all pages for a projectId that is available in the url. + */ + get projectPages() { + if (!this.router.projectId) return; + return Object.values(this.pages).filter((page) => page.project === this.router.query.projectId); + } + + /** + * retrieves all favorite pages for a projectId that is available in the url. + */ + get favoriteProjectPages() { + if (!this.projectPages) return; + return this.projectPages.filter((page) => page.is_favorite); + } + + /** + * retrieves all private pages for a projectId that is available in the url. + */ + get privateProjectPages() { + if (!this.projectPages) return; + return this.projectPages.filter((page) => page.access === 1); + } + + /** + * retrieves all shared pages which are public to everyone in the project for a projectId that is available in the url. + */ + get sharedProjectPages() { + if (!this.projectPages) return; + return this.projectPages.filter((page) => page.access === 0); + } + + /** + * retrieves all recent pages for a projectId that is available in the url. + * In format where today, yesterday, this_week, older are keys. + */ + get recentProjectPages() { + if (!this.projectPages) return; + const data: IRecentPages = { today: [], yesterday: [], this_week: [], older: [] }; + data.today = this.projectPages.filter((p) => isToday(new Date(p.created_at))) || []; + data.yesterday = this.projectPages.filter((p) => isYesterday(new Date(p.created_at))) || []; + data.this_week = + this.projectPages.filter( + (p) => + isThisWeek(new Date(p.created_at)) && !isToday(new Date(p.created_at)) && !isYesterday(new Date(p.created_at)) + ) || []; + data.older = + this.projectPages.filter((p) => !isThisWeek(new Date(p.created_at)) && !isYesterday(new Date(p.created_at))) || + []; + return data; + } + + /** + * retrieves all archived pages for a projectId that is available in the url. + */ + get archivedProjectPages() { + if (!this.router.projectId) return; + return Object.values(this.archivedPages).filter((page) => page.project === this.router.projectId); + } + + /** + * fetches all pages for a project. + * @param workspaceSlug + * @param projectId + * @returns Promise + */ + async fetchProjectPages(workspaceSlug: string, projectId: string) { + const response = await this.pageService.getProjectPages(workspaceSlug, projectId); + runInAction(() => { + this.pages = { + ...this.pages, + ...keyBy(response, "id"), + }; + }); + return response; + } + + /** + * fetches all archived pages for a project. + * @param workspaceSlug + * @param projectId + * @returns Promise + */ + async fetchArchivedProjectPages(workspaceSlug: string, projectId: string) { + const response = await this.pageService.getArchivedPages(workspaceSlug, projectId); + runInAction(() => { + this.archivedPages = { + ...this.archivedPages, + ...keyBy(response, "id"), + }; + }); + return response; + } + + /** + * Add Page to users favorites list + * @param workspaceSlug + * @param projectId + * @param pageId + */ + addToFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => { + try { + runInAction(() => { + this.pages = { + ...this.pages, + [pageId]: { ...this.pages[pageId], is_favorite: true }, + }; + }); + await this.pageService.addPageToFavorites(workspaceSlug, projectId, pageId); + } catch (error) { + runInAction(() => { + this.pages = { + ...this.pages, + [pageId]: { ...this.pages[pageId], is_favorite: false }, + }; + }); + throw error; + } + }; + + /** + * Remove page from the users favorites list + * @param workspaceSlug + * @param projectId + * @param pageId + */ + removeFromFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => { + try { + runInAction(() => { + this.pages = { + ...this.pages, + [pageId]: { ...this.pages[pageId], is_favorite: false }, + }; + }); + await this.pageService.removePageFromFavorites(workspaceSlug, projectId, pageId); + } catch (error) { + runInAction(() => { + this.pages = { + ...this.pages, + [pageId]: { ...this.pages[pageId], is_favorite: true }, + }; + }); + throw error; + } + }; +} From 81143b71982b5c33c27dbb6f8a76d20d73985ed5 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Tue, 12 Dec 2023 18:08:04 +0530 Subject: [PATCH 2/5] change observables and retain object reference --- web/store/cycle.store.ts | 113 +++++---------- web/store/label.store.ts | 68 ++++----- web/store/module.store.ts | 125 +++++------------ web/store/project-view.store.ts | 80 +++-------- web/store/project/project-publish.store.ts | 41 +++--- web/store/project/projects.store.ts | 153 ++++++++++----------- web/store/state.store.ts | 90 ++++++------ 7 files changed, 262 insertions(+), 408 deletions(-) diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index b838ee501..10e2a97bc 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -1,4 +1,5 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; +import { set } from "lodash"; // types import { ICycle, TCycleView, CycleDateCheckData } from "types"; // mobx @@ -87,8 +88,8 @@ export class CycleStore implements ICycleStore { error: observable.ref, cycleId: observable.ref, - cycleMap: observable.ref, - cycles: observable.ref, + cycleMap: observable, + cycles: observable, // computed projectCycles: computed, @@ -118,14 +119,14 @@ export class CycleStore implements ICycleStore { // computed get projectCycles() { - const projectId = this.rootStore.project.projectId; + const projectId = this.rootStore.app.router.projectId; if (!projectId) return null; return this.cycles[projectId]?.all || null; } get projectCompletedCycles() { - const projectId = this.rootStore.project.projectId; + const projectId = this.rootStore.app.router.projectId; if (!projectId) return null; @@ -133,7 +134,7 @@ export class CycleStore implements ICycleStore { } get projectUpcomingCycles() { - const projectId = this.rootStore.project.projectId; + const projectId = this.rootStore.app.router.projectId; if (!projectId) return null; @@ -141,18 +142,22 @@ export class CycleStore implements ICycleStore { } get projectDraftCycles() { - const projectId = this.rootStore.project.projectId; + const projectId = this.rootStore.app.router.projectId; if (!projectId) return null; return this.cycles[projectId]?.draft || null; } - getCycleById = (cycleId: string) => this.cycleMap[this.rootStore.project][cycleId] || null; + getCycleById = (cycleId: string) => { + const projectId = this.rootStore.app.router.projectId; + + if (!projectId) return null; + + return this.cycleMap?.[projectId]?.[cycleId] || null; + }; // actions - setCycleView = (_cycleView: TCycleView) => (this.cycleView = _cycleView); - validateDate = async (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => { try { const response = await this.cycleService.cycleDateCheck(workspaceSlug, projectId, payload); @@ -174,18 +179,12 @@ export class CycleStore implements ICycleStore { const cyclesResponse = await this.cycleService.getCyclesWithParams(workspaceSlug, projectId, params); + const _cycleMap = set(this.cycleMap, [projectId], cyclesResponse); + const _cycles = set(this.cycles, [projectId, params], Object.keys(cyclesResponse)); + runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - ...cyclesResponse, - }, - }; - this.cycles = { - ...this.cycles, - [projectId]: { ...this.cycles[projectId], [params]: Object.keys(cyclesResponse) }, - }; + this.cycleMap = _cycleMap; + this.cycles = _cycles; this.loader = false; this.error = null; }); @@ -200,14 +199,9 @@ export class CycleStore implements ICycleStore { try { const response = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId); + const _cycleMap = set(this.cycleMap, [projectId, response?.id], response); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [response?.id]: response, - }, - }; + this.cycleMap = _cycleMap; }); return response; @@ -221,14 +215,9 @@ export class CycleStore implements ICycleStore { try { const response = await this.cycleService.createCycle(workspaceSlug, projectId, data); + const _cycleMap = set(this.cycleMap, [projectId, response?.id], response); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [response?.id]: response, - }, - }; + this.cycleMap = _cycleMap; }); const _currentView = this.cycleView === "active" ? "current" : this.cycleView; @@ -247,14 +236,9 @@ export class CycleStore implements ICycleStore { const currentCycle = this.cycleMap[projectId][cycleId]; + const _cycleMap = set(this.cycleMap, [projectId, cycleId], { ...currentCycle, ...data }); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [cycleId]: { ...currentCycle, ...data }, - }, - }; + this.cycleMap = _cycleMap; }); const _currentView = this.cycleView === "active" ? "current" : this.cycleView; @@ -272,11 +256,9 @@ export class CycleStore implements ICycleStore { const currentProjectCycles = this.cycleMap[projectId]; delete currentProjectCycles[cycleId]; + const _cycleMap = set(this.cycleMap, [projectId], currentProjectCycles); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: currentProjectCycles, - }; + this.cycleMap = _cycleMap; }); const _response = await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId); @@ -294,17 +276,11 @@ export class CycleStore implements ICycleStore { addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => { try { const currentCycle = this.cycleMap[projectId][cycleId]; - if (currentCycle.is_favorite) return; + const _cycleMap = set(this.cycleMap, [projectId, cycleId, "is_favorite"], true); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [cycleId]: { ...currentCycle, is_favorite: true }, - }, - }; + this.cycleMap = _cycleMap; }); // updating through api. @@ -315,16 +291,9 @@ export class CycleStore implements ICycleStore { console.log("Failed to add cycle to favorites in the cycles store", error); // reset on error - const currentCycle = this.cycleMap[projectId][cycleId]; - + const _cycleMap = set(this.cycleMap, [projectId, cycleId, "is_favorite"], false); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [cycleId]: { ...currentCycle, is_favorite: false }, - }, - }; + this.cycleMap = _cycleMap; }); throw error; @@ -337,14 +306,9 @@ export class CycleStore implements ICycleStore { if (!currentCycle.is_favorite) return; + const _cycleMap = set(this.cycleMap, [projectId, cycleId, "is_favorite"], false); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [cycleId]: { ...currentCycle, is_favorite: false }, - }, - }; + this.cycleMap = _cycleMap; }); const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId); @@ -354,16 +318,11 @@ export class CycleStore implements ICycleStore { console.log("Failed to remove cycle from favorites - Cycle Store", error); // reset on error - const currentCycle = this.cycleMap[projectId][cycleId]; + const _cycleMap = set(this.cycleMap, [projectId, cycleId, "is_favorite"], true); runInAction(() => { - this.cycleMap = { - ...this.cycleMap, - [projectId]: { - ...this.cycleMap[projectId], - [cycleId]: { ...currentCycle, is_favorite: true }, - }, - }; + this.cycleMap = _cycleMap; }); + throw error; } }; diff --git a/web/store/label.store.ts b/web/store/label.store.ts index 83aeb930c..ccf3ac646 100644 --- a/web/store/label.store.ts +++ b/web/store/label.store.ts @@ -1,6 +1,6 @@ import { makeObservable, observable, action, runInAction, computed } from "mobx"; import keyBy from "lodash/keyBy"; -import omit from "lodash/omit"; +import { set } from "lodash"; // services import { IssueLabelService } from "services/issue"; // types @@ -11,7 +11,7 @@ import { buildTree } from "helpers/array.helper"; import { RootStore } from "./root.store"; export interface ILabelStore { - labels: Record; + labelMap: Record; projectLabels: IIssueLabel[] | undefined; projectLabelsTree: IIssueLabelTree[] | undefined; fetchProjectLabels: (workspaceSlug: string, projectId: string) => Promise; @@ -35,14 +35,14 @@ export interface ILabelStore { } export class LabelStore { - labels: Record = {}; + labelMap: Record = {}; issueLabelService; router; constructor(_rootStore: RootStore) { makeObservable(this, { // observables - labels: observable.ref, + labelMap: observable, // computed projectLabels: computed, projectLabelsTree: computed, @@ -57,15 +57,15 @@ export class LabelStore { } /** - * Returns the labels belongs to a specific project + * Returns the labelMap belongs to a specific project */ get projectLabels() { if (!this.router.query?.projectId) return; - return Object.values(this.labels).filter((label) => label.project === this.router.query.projectId); + return Object.values(this.labelMap).filter((label) => label.project === this.router.query.projectId); } /** - * Returns the labels in a tree format + * Returns the labelMap in a tree format */ get projectLabelsTree() { if (!this.projectLabels) return; @@ -73,7 +73,7 @@ export class LabelStore { } /** - * Fetches all the labels belongs to a specific project + * Fetches all the labelMap belongs to a specific project * @param workspaceSlug * @param projectId * @returns Promise @@ -81,8 +81,9 @@ export class LabelStore { fetchProjectLabels = async (workspaceSlug: string, projectId: string) => { const response = await this.issueLabelService.getProjectIssueLabels(workspaceSlug, projectId); runInAction(() => { - this.labels = { - ...this.labels, + //todo add iteratively without modifying original reference + this.labelMap = { + ...this.labelMap, ...keyBy(response, "id"), }; }); @@ -98,11 +99,10 @@ export class LabelStore { */ createLabel = async (workspaceSlug: string, projectId: string, data: Partial) => { const response = await this.issueLabelService.createIssueLabel(workspaceSlug, projectId, data); + + const _labelMap = set(this.labelMap, [response.id], response); runInAction(() => { - this.labels = { - ...this.labels, - [response.id]: response, - }; + this.labelMap = _labelMap; }); return response; }; @@ -116,22 +116,22 @@ export class LabelStore { * @returns Promise */ updateLabel = async (workspaceSlug: string, projectId: string, labelId: string, data: Partial) => { - const originalLabel = this.labels[labelId]; + const originalLabel = this.labelMap[labelId]; try { + const _labelMap = set(this.labelMap, [labelId], { ...this.labelMap[labelId], ...data }); runInAction(() => { - this.labels = { - ...this.labels, - [labelId]: { ...this.labels[labelId], ...data }, - }; + this.labelMap = _labelMap; }); + const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, projectId, labelId, data); return response; } catch (error) { console.log("Failed to update label from project store"); + const _labelMap = set(this.labelMap, [labelId], { ...this.labelMap[labelId], ...data }); runInAction(() => { - this.labels = { - ...this.labels, - [labelId]: { ...this.labels[labelId], ...originalLabel }, + this.labelMap = { + ...this.labelMap, + [labelId]: { ...this.labelMap[labelId], ...originalLabel }, }; }); throw error; @@ -158,7 +158,7 @@ export class LabelStore { isSameParent: boolean, prevIndex: number | undefined ) => { - const currLabel = this.labels?.[labelId]; + const currLabel = this.labelMap?.[labelId]; const labelTree = this.projectLabelsTree; let currentArray: IIssueLabel[]; @@ -189,7 +189,7 @@ export class LabelStore { let sortOrder: number; - //based on the next and previous labels calculate current sort order + //based on the next and previous labelMap calculate current sort order if (prevSortOrder && nextSortOrder) { sortOrder = (prevSortOrder + nextSortOrder) / 2; } else if (nextSortOrder) { @@ -205,27 +205,29 @@ export class LabelStore { }; /** - * Delete the label from the project and remove it from the labels object + * Delete the label from the project and remove it from the labelMap object * @param workspaceSlug * @param projectId * @param labelId */ deleteLabel = async (workspaceSlug: string, projectId: string, labelId: string) => { - const originalLabel = this.labels[labelId]; - runInAction(() => { - this.labels = omit(this.labels, labelId); - }); + const originalLabel = this.labelMap[labelId]; + try { + const _labelMap = this.labelMap; + delete _labelMap[labelId]; + runInAction(() => { + this.labelMap = _labelMap; + }); + // deleting using api await this.issueLabelService.deleteIssueLabel(workspaceSlug, projectId, labelId); } catch (error) { console.log("Failed to delete label from project store"); // reverting back to original label list + const labelMap = set(this.labelMap, [labelId], originalLabel); runInAction(() => { - this.labels = { - ...this.labels, - [labelId]: originalLabel, - }; + this.labelMap = labelMap; }); } }; diff --git a/web/store/module.store.ts b/web/store/module.store.ts index c1300882b..4e7ebf380 100644 --- a/web/store/module.store.ts +++ b/web/store/module.store.ts @@ -1,4 +1,5 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; +import { set } from "lodash"; // services import { ProjectService } from "services/project"; import { ModuleService } from "services/module.service"; @@ -79,12 +80,12 @@ export class ModulesStore implements IModuleStore { constructor(_rootStore: RootStore) { makeObservable(this, { // states - loader: observable, + loader: observable.ref, error: observable.ref, // observables moduleId: observable.ref, - moduleMap: observable.ref, + moduleMap: observable, // actions getModuleById: action, @@ -116,12 +117,16 @@ export class ModulesStore implements IModuleStore { // computed get projectModules() { - if (!this.rootStore.project.projectId) return null; + if (!this.rootStore.app.router.projectId) return null; - return Object.keys(this.moduleMap[this.rootStore.project.projectId]) || null; + return Object.keys(this.moduleMap[this.rootStore.app.router.projectId]) || null; } - getModuleById = (moduleId: string) => this.moduleMap[this.rootStore.project.projectId][moduleId] || null; + getModuleById = (moduleId: string) => { + if (!this.rootStore.app.router.projectId) return null; + + return this.moduleMap?.[this.rootStore.app.router.projectId]?.[moduleId] || null; + }; // actions @@ -134,11 +139,9 @@ export class ModulesStore implements IModuleStore { const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectId); + const _moduleMap = set(this.moduleMap, [projectId], modulesResponse); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: modulesResponse, - }; + this.moduleMap = _moduleMap; this.loader = false; this.error = null; }); @@ -161,14 +164,9 @@ export class ModulesStore implements IModuleStore { const response = await this.moduleService.getModuleDetails(workspaceSlug, projectId, moduleId); + const _moduleMap = set(this.moduleMap, [projectId, moduleId], response); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: response, - }, - }; + this.moduleMap = _moduleMap; this.loader = false; this.error = null; }); @@ -190,14 +188,9 @@ export class ModulesStore implements IModuleStore { try { const response = await this.moduleService.createModule(workspaceSlug, projectId, data); + const _moduleMap = set(this.moduleMap, [projectId, response?.id], response); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [response.id]: response, - }, - }; + this.moduleMap = _moduleMap; this.loader = false; this.error = null; }); @@ -219,14 +212,9 @@ export class ModulesStore implements IModuleStore { try { const currentModule = this.moduleMap[projectId][moduleId]; + const _moduleMap = set(this.moduleMap, [projectId, moduleId], { ...currentModule, ...data }); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, ...data }, - }, - }; + this.moduleMap = _moduleMap; }); const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data); @@ -251,11 +239,9 @@ export class ModulesStore implements IModuleStore { const currentProjectModules = this.moduleMap[projectId]; delete currentProjectModules[moduleId]; + const _moduleMap = set(this.moduleMap, [projectId], currentProjectModules); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: currentProjectModules, - }; + this.moduleMap = _moduleMap; }); await this.moduleService.deleteModule(workspaceSlug, projectId, moduleId); @@ -281,14 +267,13 @@ export class ModulesStore implements IModuleStore { const currentModule = this.moduleMap[projectId][moduleId]; + const _moduleMap = set( + this.moduleMap, + [projectId, moduleId, "link_module"], + [response, ...currentModule.link_module] + ); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, link_module: [response, ...currentModule.link_module] }, - }, - }; + this.moduleMap = _moduleMap; }); return response; @@ -319,14 +304,9 @@ export class ModulesStore implements IModuleStore { const currentModule = this.moduleMap[projectId][moduleId]; const linkModules = currentModule.link_module.map((link) => (link.id === linkId ? response : link)); + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "link_module"], linkModules); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, link_module: linkModules }, - }, - }; + this.moduleMap = _moduleMap; }); return response; @@ -349,14 +329,9 @@ export class ModulesStore implements IModuleStore { const currentModule = this.moduleMap[projectId][moduleId]; const linkModules = currentModule.link_module.filter((link) => link.id !== linkId); + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "link_module"], linkModules); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, link_module: linkModules }, - }, - }; + this.moduleMap = _moduleMap; }); await this.moduleService.deleteModuleLink(workspaceSlug, projectId, moduleId, linkId); @@ -380,14 +355,9 @@ export class ModulesStore implements IModuleStore { if (currentModule.is_favorite) return; + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "is_favorite"], true); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, is_favorite: true }, - }, - }; + this.moduleMap = _moduleMap; }); await this.moduleService.addModuleToFavorites(workspaceSlug, projectId, { @@ -396,16 +366,9 @@ export class ModulesStore implements IModuleStore { } catch (error) { console.error("Failed to add module to favorites in module store", error); - const currentModule = this.moduleMap[projectId][moduleId]; - + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "is_favorite"], false); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, is_favorite: false }, - }, - }; + this.moduleMap = _moduleMap; }); } }; @@ -416,30 +379,18 @@ export class ModulesStore implements IModuleStore { if (!currentModule.is_favorite) return; + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "is_favorite"], false); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, is_favorite: false }, - }, - }; + this.moduleMap = _moduleMap; }); await this.moduleService.removeModuleFromFavorites(workspaceSlug, projectId, moduleId); } catch (error) { console.error("Failed to remove module from favorites in module store", error); - const currentModule = this.moduleMap[projectId][moduleId]; - + const _moduleMap = set(this.moduleMap, [projectId, moduleId, "is_favorite"], true); runInAction(() => { - this.moduleMap = { - ...this.moduleMap, - [projectId]: { - ...this.moduleMap[projectId], - [moduleId]: { ...currentModule, is_favorite: true }, - }, - }; + this.moduleMap = _moduleMap; }); } }; diff --git a/web/store/project-view.store.ts b/web/store/project-view.store.ts index 900e67af7..3981262f0 100644 --- a/web/store/project-view.store.ts +++ b/web/store/project-view.store.ts @@ -1,3 +1,4 @@ +import { set } from "lodash"; import { observable, action, makeObservable, runInAction } from "mobx"; // services import { ViewService } from "services/view.service"; @@ -60,7 +61,7 @@ export class ProjectViewsStore implements IProjectViewsStore { // observables viewId: observable.ref, - viewMap: observable.ref, + viewMap: observable, // actions fetchViews: action, @@ -89,12 +90,10 @@ export class ProjectViewsStore implements IProjectViewsStore { const response = await this.viewService.getViews(workspaceSlug, projectId); + const _viewMap = set(this.viewMap, [projectId], response); runInAction(() => { this.loader = false; - this.viewMap = { - ...this.viewMap, - [projectId]: response, - }; + this.viewMap = _viewMap; }); return response; @@ -116,15 +115,10 @@ export class ProjectViewsStore implements IProjectViewsStore { const response = await this.viewService.getViewDetails(workspaceSlug, projectId, viewId); + const _viewMap = set(this.viewMap, [projectId, viewId], response); runInAction(() => { this.loader = false; - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [response.id]: response, - }, - }; + this.viewMap = _viewMap; }); return response; @@ -142,15 +136,10 @@ export class ProjectViewsStore implements IProjectViewsStore { try { const response = await this.viewService.createView(workspaceSlug, projectId, data); + const _viewMap = set(this.viewMap, [projectId, response.id], response); runInAction(() => { this.loader = false; - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [response.id]: response, - }, - }; + this.viewMap = _viewMap; }); return response; @@ -172,14 +161,9 @@ export class ProjectViewsStore implements IProjectViewsStore { try { const currentView = this.viewMap[projectId][viewId]; + const _viewMap = set(this.viewMap, [projectId, viewId], { ...currentView, ...data }); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [viewId]: { ...currentView, ...data }, - }, - }; + this.viewMap = _viewMap; }); const response = await this.viewService.patchView(workspaceSlug, projectId, viewId, data); @@ -201,11 +185,9 @@ export class ProjectViewsStore implements IProjectViewsStore { const currentProjectViews = this.viewMap[projectId]; delete currentProjectViews[viewId]; + const _viewMap = set(this.viewMap, [projectId], currentProjectViews); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: currentProjectViews, - }; + this.viewMap = _viewMap; }); await this.viewService.deleteView(workspaceSlug, projectId, viewId); @@ -226,14 +208,9 @@ export class ProjectViewsStore implements IProjectViewsStore { if (currentView.is_favorite) return; + const _viewMap = set(this.viewMap, [projectId, viewId, "is_favorite"], true); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [viewId]: { ...currentView, is_favorite: true }, - }, - }; + this.viewMap = _viewMap; }); await this.viewService.addViewToFavorites(workspaceSlug, projectId, { @@ -242,15 +219,9 @@ export class ProjectViewsStore implements IProjectViewsStore { } catch (error) { console.error("Failed to add view to favorites in view store", error); - const currentView = this.viewMap[projectId][viewId]; + const _viewMap = set(this.viewMap, [projectId, viewId, "is_favorite"], false); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [viewId]: { ...currentView, is_favorite: false }, - }, - }; + this.viewMap = _viewMap; }); } }; @@ -261,29 +232,18 @@ export class ProjectViewsStore implements IProjectViewsStore { if (!currentView.is_favorite) return; + const _viewMap = set(this.viewMap, [projectId, viewId, "is_favorite"], false); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [viewId]: { ...currentView, is_favorite: false }, - }, - }; + this.viewMap = _viewMap; }); await this.viewService.removeViewFromFavorites(workspaceSlug, projectId, viewId); } catch (error) { console.error("Failed to remove view from favorites in view store", error); - const currentView = this.viewMap[projectId][viewId]; + const _viewMap = set(this.viewMap, [projectId, viewId, "is_favorite"], true); runInAction(() => { - this.viewMap = { - ...this.viewMap, - [projectId]: { - ...this.viewMap[projectId], - [viewId]: { ...currentView, is_favorite: true }, - }, - }; + this.viewMap = _viewMap; }); } }; diff --git a/web/store/project/project-publish.store.ts b/web/store/project/project-publish.store.ts index 1e040f339..535da8871 100644 --- a/web/store/project/project-publish.store.ts +++ b/web/store/project/project-publish.store.ts @@ -1,4 +1,5 @@ import { observable, action, makeObservable, runInAction } from "mobx"; +import { set } from "lodash"; // types import { ProjectRootStore } from "./"; // services @@ -57,12 +58,12 @@ export class ProjectPublishStore implements IProjectPublishStore { constructor(_projectRootStore: ProjectRootStore) { makeObservable(this, { // states - generalLoader: observable, - fetchSettingsLoader: observable, - error: observable, + generalLoader: observable.ref, + fetchSettingsLoader: observable.ref, + error: observable.ref, // observables - project_id: observable, + project_id: observable.ref, projectPublishSettings: observable.ref, // actions @@ -147,18 +148,14 @@ export class ProjectPublishStore implements IProjectPublishStore { project: response?.project || null, }; + const _projectMap = set( + this.projectRootStore.projects.projectMap, + [workspaceSlug, projectId, "is_deployed"], + true + ); runInAction(() => { this.projectPublishSettings = _projectPublishSettings; - this.projectRootStore.projects.projectsMap = { - ...this.projectRootStore.projects.projectsMap, - [workspaceSlug]: { - ...this.projectRootStore.projects.projectsMap[workspaceSlug], - [projectId]: { - ...this.projectRootStore.projects.projectsMap[workspaceSlug][projectId], - is_deployed: true, - }, - }, - }; + this.projectRootStore.projects.projectMap = _projectMap; this.generalLoader = false; this.error = null; }); @@ -236,18 +233,14 @@ export class ProjectPublishStore implements IProjectPublishStore { projectPublishId ); + const _projectMap = set( + this.projectRootStore.projects.projectMap, + [workspaceSlug, projectId, "is_deployed"], + false + ); runInAction(() => { this.projectPublishSettings = "not-initialized"; - this.projectRootStore.projects.projectsMap = { - ...this.projectRootStore.projects.projectsMap, - [workspaceSlug]: { - ...this.projectRootStore.projects.projectsMap[workspaceSlug], - [projectId]: { - ...this.projectRootStore.projects.projectsMap[workspaceSlug][projectId], - is_deployed: false, - }, - }, - }; + this.projectRootStore.projects.projectMap = _projectMap; this.generalLoader = false; this.error = null; }); diff --git a/web/store/project/projects.store.ts b/web/store/project/projects.store.ts index f8fd206d5..d6a2a9086 100644 --- a/web/store/project/projects.store.ts +++ b/web/store/project/projects.store.ts @@ -1,9 +1,11 @@ +import { set } from "lodash"; import { observable, action, computed, makeObservable, runInAction } from "mobx"; +//types +import { RootStore } from "../root.store"; +import { IProject } from "types"; +//services import { IssueLabelService, IssueService } from "services/issue"; import { ProjectService, ProjectStateService } from "services/project"; -import { RootStore } from "store/root.store"; - -import { IProject } from "types"; export interface IProjectsStore { loader: boolean; @@ -11,7 +13,7 @@ export interface IProjectsStore { searchQuery: string; projectId: string | null; - projectsMap: { + projectMap: { [workspaceSlug: string]: { [projectId: string]: IProject; // projectId: project Info }; @@ -48,7 +50,7 @@ export class ProjectsStore implements IProjectsStore { projectId: string | null = null; searchQuery: string = ""; - projectsMap: { + projectMap: { [workspaceSlug: string]: { [projectId: string]: IProject; // projectId: project Info }; @@ -65,12 +67,12 @@ export class ProjectsStore implements IProjectsStore { constructor(_rootStore: RootStore) { makeObservable(this, { // observable - loader: observable, - error: observable, + loader: observable.ref, + error: observable.ref, searchQuery: observable.ref, projectId: observable.ref, - projectsMap: observable.ref, + projectMap: observable, // computed searchedProjects: computed, @@ -104,48 +106,48 @@ export class ProjectsStore implements IProjectsStore { } get searchedProjects() { - if (!this.rootStore.app.router.query.workspaceSlug) return []; + if (!this.rootStore.app.router.workspaceSlug) return []; - const currentProjectsMap = this.projectsMap[this.rootStore.app.router.query.workspaceSlug.toString()]; - const projectIds = Object.keys(currentProjectsMap); + const currentProjectMap = this.projectMap[this.rootStore.app.router.workspaceSlug]; + const projectIds = Object.keys(currentProjectMap); return this.searchQuery === "" ? projectIds : projectIds?.filter((projectId) => { - currentProjectsMap[projectId].name.toLowerCase().includes(this.searchQuery.toLowerCase()) || - currentProjectsMap[projectId].identifier.toLowerCase().includes(this.searchQuery.toLowerCase()); + currentProjectMap[projectId].name.toLowerCase().includes(this.searchQuery.toLowerCase()) || + currentProjectMap[projectId].identifier.toLowerCase().includes(this.searchQuery.toLowerCase()); }); } get workspaceProjects() { if (!this.rootStore.app.router.workspaceSlug) return null; - const currentProjectsMap = this.projectsMap[this.rootStore.app.router.query.workspaceSlug.toString()]; + const currentProjectMap = this.projectMap[this.rootStore.app.router.workspaceSlug]; - const projectIds = Object.keys(currentProjectsMap); + const projectIds = Object.keys(currentProjectMap); if (!projectIds) return null; return projectIds; } get currentProjectDetails() { - if (!this.rootStore.app.router.query.projectId || !this.rootStore.app.router.query.workspaceSlug) return; - return this.projectsMap[!this.rootStore.app.router.query.workspaceSlug][this.projectId]; + if (!this.rootStore.app.router.projectId || !this.rootStore.app.router.workspaceSlug) return; + return this.projectMap[this.rootStore.app.router.workspaceSlug][this.projectId]; } get joinedProjects() { - if (!this.rootStore.workspace.workspaceSlug) return []; + if (!this.rootStore.app.router.workspaceSlug) return []; - const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug]; - const projectIds = Object.keys(currentProjectsMap); + const currentProjectMap = this.projectMap[this.rootStore.app.router.workspaceSlug]; + const projectIds = Object.keys(currentProjectMap); - return projectIds?.filter((projectId) => currentProjectsMap[projectId].is_member); + return projectIds?.filter((projectId) => currentProjectMap[projectId].is_member); } get favoriteProjects() { - if (!this.rootStore.workspace.workspaceSlug) return []; + if (!this.rootStore.app.router.workspaceSlug) return []; - const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug]; - const projectIds = Object.keys(currentProjectsMap); + const currentProjectMap = this.projectMap[this.rootStore.app.router.workspaceSlug]; + const projectIds = Object.keys(currentProjectMap); - return projectIds?.filter((projectId) => currentProjectsMap[projectId].is_favorite); + return projectIds?.filter((projectId) => currentProjectMap[projectId].is_favorite); } setSearchQuery = (query: string) => { @@ -160,12 +162,11 @@ export class ProjectsStore implements IProjectsStore { */ fetchProjects = async (workspaceSlug: string) => { try { - const currentProjectsMap = await this.projectService.getProjects(workspaceSlug); + const currentProjectMap = await this.projectService.getProjects(workspaceSlug); + + const _projectMap = set(this.projectMap, [workspaceSlug], currentProjectMap); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: currentProjectsMap, - }; + this.projectMap = _projectMap; }); } catch (error) { console.log("Failed to fetch project from workspace store"); @@ -177,14 +178,9 @@ export class ProjectsStore implements IProjectsStore { try { const response = await this.projectService.getProject(workspaceSlug, projectId); + const _projectMap = set(this.projectMap, [workspaceSlug, projectId], response); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { - ...this.projectsMap[workspaceSlug], - [projectId]: response, - }, - }; + this.projectMap = _projectMap; }); return response; } catch (error) { @@ -194,48 +190,47 @@ export class ProjectsStore implements IProjectsStore { }; getProjectById = (workspaceSlug: string, projectId: string) => { - const currentProjectsMap = this.projectsMap?.[workspaceSlug]; - if (!currentProjectsMap) return null; + const currentProjectMap = this.projectMap?.[workspaceSlug]; + if (!currentProjectMap) return null; - const projectInfo: IProject | null = currentProjectsMap[projectId] || null; + const projectInfo: IProject | null = currentProjectMap[projectId] || null; return projectInfo; }; addProjectToFavorites = async (workspaceSlug: string, projectId: string) => { try { - const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId]; + const currentProject = this.projectMap?.[workspaceSlug]?.[projectId]; + if (currentProject.is_favorite) return; + + const _projectMap = set(this.projectMap, [workspaceSlug, projectId, "is_favorite"], true); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { - ...this.projectsMap[workspaceSlug], - [projectId]: { ...currentProject, is_favorite: true }, - }, - }; + this.projectMap = _projectMap; }); const response = await this.projectService.addProjectToFavorites(workspaceSlug, projectId); return response; } catch (error) { console.log("Failed to add project to favorite"); - await this.fetchProjects(workspaceSlug); + + const _projectMap = set(this.projectMap, [workspaceSlug, projectId, "is_favorite"], false); + runInAction(() => { + this.projectMap = _projectMap; + }); + throw error; } }; removeProjectFromFavorites = async (workspaceSlug: string, projectId: string) => { try { - const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId]; + const currentProject = this.projectMap?.[workspaceSlug]?.[projectId]; + if (!currentProject.is_favorite) return; + + const _projectMap = set(this.projectMap, [workspaceSlug, projectId, "is_favorite"], false); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { - ...this.projectsMap[workspaceSlug], - [projectId]: { ...currentProject, is_favorite: false }, - }, - }; + this.projectMap = _projectMap; }); const response = await this.projectService.removeProjectFromFavorites(workspaceSlug, projectId); @@ -243,16 +238,21 @@ export class ProjectsStore implements IProjectsStore { return response; } catch (error) { console.log("Failed to add project to favorite"); + + const _projectMap = set(this.projectMap, [workspaceSlug, projectId, "is_favorite"], true); + runInAction(() => { + this.projectMap = _projectMap; + }); throw error; } }; orderProjectsWithSortOrder = (sortIndex: number, destinationIndex: number, projectId: string) => { try { - const workspaceSlug = this.rootStore.workspace.workspaceSlug; + const workspaceSlug = this.rootStore.app.router.workspaceSlug; if (!workspaceSlug) return 0; - const projectsList = Object.values(this.projectsMap[workspaceSlug] || {}) || []; + const projectsList = Object.values(this.projectMap[workspaceSlug] || {}) || []; let updatedSortOrder = projectsList[sortIndex].sort_order; if (destinationIndex === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000; @@ -268,16 +268,9 @@ export class ProjectsStore implements IProjectsStore { updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2; } - const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId]; - + const _projectMap = set(this.projectMap, [workspaceSlug, projectId, "sort_order"], updatedSortOrder); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { - ...this.projectsMap[workspaceSlug], - [projectId]: { ...currentProject, sort_order: updatedSortOrder }, - }, - }; + this.projectMap = _projectMap; }); return updatedSortOrder; @@ -302,12 +295,12 @@ export class ProjectsStore implements IProjectsStore { createProject = async (workspaceSlug: string, data: any) => { try { const response = await this.projectService.createProject(workspaceSlug, data); + + const _projectMap = set(this.projectMap, [workspaceSlug, response.id], response); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { ...this.projectsMap[workspaceSlug], [response.id]: response }, - }; + this.projectMap = _projectMap; }); + return response; } catch (error) { console.log("Failed to create project from project store"); @@ -317,13 +310,11 @@ export class ProjectsStore implements IProjectsStore { updateProject = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId]; + const currentProject = this.projectMap?.[workspaceSlug]?.[projectId]; + const _projectMap = set(this.projectMap, [workspaceSlug, projectId], { ...currentProject, ...data }); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { ...this.projectsMap[workspaceSlug], [projectId]: { ...currentProject, ...data } }, - }; + this.projectMap = _projectMap; }); const response = await this.projectService.updateProject(workspaceSlug, projectId, data); @@ -339,15 +330,13 @@ export class ProjectsStore implements IProjectsStore { deleteProject = async (workspaceSlug: string, projectId: string) => { try { - const workspaceProjects = { ...this.projectsMap[workspaceSlug] }; + const workspaceProjects = { ...this.projectMap[workspaceSlug] }; delete workspaceProjects[projectId]; + const _projectMap = set(this.projectMap, [workspaceSlug], workspaceProjects); runInAction(() => { - this.projectsMap = { - ...this.projectsMap, - [workspaceSlug]: { ...workspaceProjects }, - }; + this.projectMap = _projectMap; }); await this.projectService.deleteProject(workspaceSlug, projectId); diff --git a/web/store/state.store.ts b/web/store/state.store.ts index 15d8c0d34..81ad88e4e 100644 --- a/web/store/state.store.ts +++ b/web/store/state.store.ts @@ -1,7 +1,7 @@ import { makeObservable, observable, computed, action, runInAction } from "mobx"; import groupBy from "lodash/groupBy"; import keyBy from "lodash/keyBy"; -import omit from "lodash/omit"; +import { set } from "lodash"; // store import { RootStore } from "./root.store"; // types @@ -10,20 +10,20 @@ import { IState } from "types"; import { ProjectStateService } from "services/project"; export interface IStateStore { - states: Record; + stateMap: Record; projectStates: IState[] | undefined; groupedProjectStates: Record | undefined; } export class StateStore implements IStateStore { - states: Record = {}; + stateMap: Record = {}; router; stateService; constructor(_rootStore: RootStore) { makeObservable(this, { // observables - states: observable.ref, + stateMap: observable, // computed projectStates: computed, groupedProjectStates: computed, @@ -39,15 +39,15 @@ export class StateStore implements IStateStore { } /** - * Returns the states belongs to a specific project + * Returns the stateMap belongs to a specific project */ get projectStates() { if (!this.router.query?.projectId) return; - return Object.values(this.states).filter((state) => state.project === this.router.query.projectId); + return Object.values(this.stateMap).filter((state) => state.project === this.router.query.projectId); } /** - * Returns the states belongs to a specific project grouped by group + * Returns the stateMap belongs to a specific project grouped by group */ get groupedProjectStates() { if (!this.router.query?.projectId) return; @@ -55,29 +55,30 @@ export class StateStore implements IStateStore { } /** - * Returns the states belongs to a project by projectId + * Returns the stateMap belongs to a project by projectId * @param projectId * @returns IState[] */ getProjectStates(projectId: string) { - return Object.values(this.states).filter((state) => state.project === projectId); + return Object.values(this.stateMap).filter((state) => state.project === projectId); } /** - * fetches the states of a project + * fetches the stateMap of a project * @param workspaceSlug * @param projectId * @returns */ fetchProjectStates = async (workspaceSlug: string, projectId: string) => { - const states = await this.stateService.getStates(workspaceSlug, projectId); + const stateMap = await this.stateService.getStates(workspaceSlug, projectId); runInAction(() => { - this.states = { - ...this.states, - ...keyBy(states, "id"), + //todo add iteratively without modifying original reference + this.stateMap = { + ...this.stateMap, + ...keyBy(stateMap, "id"), }; }); - return states; + return stateMap; }; /** @@ -89,11 +90,10 @@ export class StateStore implements IStateStore { */ createState = async (workspaceSlug: string, projectId: string, data: Partial) => { const response = await this.stateService.createState(workspaceSlug, projectId, data); + + const _stateMap = set(this.stateMap, [response?.id], response); runInAction(() => { - this.states = { - ...this.states, - [response?.id]: response, - }; + this.stateMap = _stateMap; }); return response; }; @@ -107,20 +107,18 @@ export class StateStore implements IStateStore { * @returns */ updateState = async (workspaceSlug: string, projectId: string, stateId: string, data: Partial) => { - const originalState = this.states[stateId]; + const originalState = this.stateMap[stateId]; try { + const _stateMap = set(this.stateMap, [stateId], { ...this.stateMap?.[stateId], ...data }); runInAction(() => { - this.states = { - ...this.states, - [stateId]: { ...this.states?.[stateId], ...data }, - }; + this.stateMap = _stateMap; }); const response = await this.stateService.patchState(workspaceSlug, projectId, stateId, data); return response; } catch (error) { runInAction(() => { - this.states = { - ...this.states, + this.stateMap = { + ...this.stateMap, [stateId]: originalState, }; }); @@ -135,15 +133,18 @@ export class StateStore implements IStateStore { * @param stateId */ deleteState = async (workspaceSlug: string, projectId: string, stateId: string) => { - const originalStates = this.states; + const originalStates = this.stateMap; try { + const _stateMap = this.stateMap; + delete this.stateMap[stateId]; + runInAction(() => { - this.states = omit(this.states, stateId); + this.stateMap = _stateMap; }); await this.stateService.deleteState(workspaceSlug, projectId, stateId); } catch (error) { runInAction(() => { - this.states = originalStates; + this.stateMap = originalStates; }); throw error; } @@ -156,18 +157,17 @@ export class StateStore implements IStateStore { * @param stateId */ markStateAsDefault = async (workspaceSlug: string, projectId: string, stateId: string) => { - const originalStates = this.states; + const originalStates = this.stateMap; try { + const _stateMap = set(this.stateMap, [stateId, "default"], true); runInAction(() => { - this.states = { - ...this.states, - [stateId]: { ...this.states[stateId], default: true }, - }; + this.stateMap = _stateMap; }); + await this.stateService.markDefault(workspaceSlug, projectId, stateId); } catch (error) { runInAction(() => { - this.states = originalStates; + this.stateMap = originalStates; }); throw error; } @@ -189,12 +189,12 @@ export class StateStore implements IStateStore { groupIndex: number ) => { const SEQUENCE_GAP = 15000; - const originalStates = this.states; + const originalStates = this.stateMap; try { let newSequence = SEQUENCE_GAP; - const states = this.projectStates || []; - const selectedState = states?.find((state) => state.id === stateId); - const groupStates = states?.filter((state) => state.group === selectedState?.group); + const stateMap = this.projectStates || []; + const selectedState = stateMap?.find((state) => state.id === stateId); + const groupStates = stateMap?.filter((state) => state.group === selectedState?.group); const groupLength = groupStates.length; if (direction === "up") { if (groupIndex === 1) newSequence = groupStates[0].sequence - SEQUENCE_GAP; @@ -203,18 +203,18 @@ export class StateStore implements IStateStore { if (groupIndex === groupLength - 2) newSequence = groupStates[groupLength - 1].sequence + SEQUENCE_GAP; else newSequence = (groupStates[groupIndex + 2].sequence + groupStates[groupIndex + 1].sequence) / 2; } - // updating using api + + const _stateMap = set(this.stateMap, [stateId, "sequence"], newSequence); runInAction(() => { - this.states = { - ...this.states, - [stateId]: { ...this.states[stateId], sequence: newSequence }, - }; + this.stateMap = _stateMap; }); + + // updating using api await this.stateService.patchState(workspaceSlug, projectId, stateId, { sequence: newSequence }); } catch (err) { // reverting back to old state group if api fails runInAction(() => { - this.states = originalStates; + this.stateMap = originalStates; }); } }; From 540e78deb76d7855999d20bdcd03ee131eedc777 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Tue, 12 Dec 2023 18:35:17 +0530 Subject: [PATCH 3/5] fix build errors --- web/services/module.service.ts | 4 ++-- web/store/cycle.store.ts | 2 +- web/store/label.store.ts | 2 +- web/store/module.store.ts | 2 +- web/store/project-view.store.ts | 2 +- web/store/project/project-publish.store.ts | 2 +- web/store/project/projects.store.ts | 6 +++--- web/store/state.store.ts | 2 +- web/store/user/user-membership.store.ts | 24 +++++++++++----------- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/web/services/module.service.ts b/web/services/module.service.ts index 12bb69055..2ebff6a75 100644 --- a/web/services/module.service.ts +++ b/web/services/module.service.ts @@ -132,7 +132,7 @@ export class ModuleService extends APIService { workspaceSlug: string, projectId: string, moduleId: string, - data: ModuleLink + data: Partial ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/`, data) .then((response) => response?.data) @@ -146,7 +146,7 @@ export class ModuleService extends APIService { projectId: string, moduleId: string, linkId: string, - data: ModuleLink + data: Partial ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`, diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index 10e2a97bc..ea10c58af 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -1,5 +1,5 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; -import { set } from "lodash"; +import set from "lodash/set"; // types import { ICycle, TCycleView, CycleDateCheckData } from "types"; // mobx diff --git a/web/store/label.store.ts b/web/store/label.store.ts index ccf3ac646..59c6cae81 100644 --- a/web/store/label.store.ts +++ b/web/store/label.store.ts @@ -1,6 +1,6 @@ import { makeObservable, observable, action, runInAction, computed } from "mobx"; import keyBy from "lodash/keyBy"; -import { set } from "lodash"; +import set from "lodash/set"; // services import { IssueLabelService } from "services/issue"; // types diff --git a/web/store/module.store.ts b/web/store/module.store.ts index 4e7ebf380..cfd858d92 100644 --- a/web/store/module.store.ts +++ b/web/store/module.store.ts @@ -1,5 +1,5 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; -import { set } from "lodash"; +import set from "lodash/set"; // services import { ProjectService } from "services/project"; import { ModuleService } from "services/module.service"; diff --git a/web/store/project-view.store.ts b/web/store/project-view.store.ts index 3981262f0..b9a1cd199 100644 --- a/web/store/project-view.store.ts +++ b/web/store/project-view.store.ts @@ -1,4 +1,4 @@ -import { set } from "lodash"; +import set from "lodash/set"; import { observable, action, makeObservable, runInAction } from "mobx"; // services import { ViewService } from "services/view.service"; diff --git a/web/store/project/project-publish.store.ts b/web/store/project/project-publish.store.ts index 535da8871..4cc420482 100644 --- a/web/store/project/project-publish.store.ts +++ b/web/store/project/project-publish.store.ts @@ -1,5 +1,5 @@ import { observable, action, makeObservable, runInAction } from "mobx"; -import { set } from "lodash"; +import set from "lodash/set"; // types import { ProjectRootStore } from "./"; // services diff --git a/web/store/project/projects.store.ts b/web/store/project/projects.store.ts index d6a2a9086..7710e5d22 100644 --- a/web/store/project/projects.store.ts +++ b/web/store/project/projects.store.ts @@ -1,4 +1,4 @@ -import { set } from "lodash"; +import set from "lodash/set"; import { observable, action, computed, makeObservable, runInAction } from "mobx"; //types import { RootStore } from "../root.store"; @@ -54,7 +54,7 @@ export class ProjectsStore implements IProjectsStore { [workspaceSlug: string]: { [projectId: string]: IProject; // projectId: project Info }; - }; + } = {}; // root store rootStore: RootStore; @@ -129,7 +129,7 @@ export class ProjectsStore implements IProjectsStore { get currentProjectDetails() { if (!this.rootStore.app.router.projectId || !this.rootStore.app.router.workspaceSlug) return; - return this.projectMap[this.rootStore.app.router.workspaceSlug][this.projectId]; + return this.projectMap[this.rootStore.app.router.workspaceSlug][this.rootStore.app.router.projectId]; } get joinedProjects() { diff --git a/web/store/state.store.ts b/web/store/state.store.ts index 81ad88e4e..ea6320a01 100644 --- a/web/store/state.store.ts +++ b/web/store/state.store.ts @@ -1,7 +1,7 @@ import { makeObservable, observable, computed, action, runInAction } from "mobx"; import groupBy from "lodash/groupBy"; import keyBy from "lodash/keyBy"; -import { set } from "lodash"; +import set from "lodash/set"; // store import { RootStore } from "./root.store"; // types diff --git a/web/store/user/user-membership.store.ts b/web/store/user/user-membership.store.ts index e7cd475ed..6329d6ad4 100644 --- a/web/store/user/user-membership.store.ts +++ b/web/store/user/user-membership.store.ts @@ -87,33 +87,33 @@ export class UserMembershipStore implements IUserMembershipStore { } get currentWorkspaceMemberInfo() { - if (!this.router.query?.workspaceSlug) return; - return this.workspaceMemberInfo[this.router.query?.workspaceSlug]; + if (!this.router.workspaceSlug) return; + return this.workspaceMemberInfo[this.router.workspaceSlug]; } get currentWorkspaceRole() { - if (!this.router.query?.workspaceSlug) return; - return this.workspaceMemberInfo[this.router.query?.workspaceSlug]?.role; + if (!this.router.workspaceSlug) return; + return this.workspaceMemberInfo[this.router.workspaceSlug]?.role; } get currentProjectMemberInfo() { - if (!this.router.query?.projectId) return; - return this.projectMemberInfo[this.router.query?.projectId]; + if (!this.router.projectId) return; + return this.projectMemberInfo[this.router.projectId]; } get currentProjectRole() { - if (!this.router.query?.projectId) return; - return this.projectMemberInfo[this.router.query?.projectId]?.role; + if (!this.router.projectId) return; + return this.projectMemberInfo[this.router.projectId]?.role; } get hasPermissionToCurrentWorkspace() { - if (!this.router.query?.workspaceSlug) return; - return this.hasPermissionToWorkspace[this.router.query?.workspaceSlug]; + if (!this.router.workspaceSlug) return; + return this.hasPermissionToWorkspace[this.router.workspaceSlug]; } get hasPermissionToCurrentProject() { - if (!this.router.query?.projectId) return; - return this.hasPermissionToProject[this.router.query?.projectId]; + if (!this.router.projectId) return; + return this.hasPermissionToProject[this.router.projectId]; } fetchUserWorkspaceInfo = async (workspaceSlug: string) => { From 9ad33a24c5a3ad0e922a097f5d1c2d536517bbf8 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 12 Dec 2023 19:05:22 +0530 Subject: [PATCH 4/5] chore: changed the structure of workspace, project, cycle, module and pages --- apiserver/plane/app/serializers/module.py | 4 +-- apiserver/plane/app/views/cycle.py | 44 +++-------------------- apiserver/plane/app/views/module.py | 7 ++++ apiserver/plane/app/views/page.py | 15 ++++---- apiserver/plane/app/views/project.py | 8 ++--- apiserver/plane/app/views/workspace.py | 14 +++++--- 6 files changed, 36 insertions(+), 56 deletions(-) diff --git a/apiserver/plane/app/serializers/module.py b/apiserver/plane/app/serializers/module.py index 48f773b0f..1da771d4d 100644 --- a/apiserver/plane/app/serializers/module.py +++ b/apiserver/plane/app/serializers/module.py @@ -2,7 +2,7 @@ from rest_framework import serializers # Module imports -from .base import BaseSerializer +from .base import BaseSerializer, DynamicBaseSerializer from .user import UserLiteSerializer from .project import ProjectLiteSerializer from .workspace import WorkspaceLiteSerializer @@ -159,7 +159,7 @@ class ModuleLinkSerializer(BaseSerializer): return ModuleLink.objects.create(**validated_data) -class ModuleSerializer(BaseSerializer): +class ModuleSerializer(DynamicBaseSerializer): project_detail = ProjectLiteSerializer(read_only=True, source="project") lead_detail = UserLiteSerializer(read_only=True, source="lead") members_detail = UserLiteSerializer(read_only=True, many=True, source="members") diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index d2f82d75b..c439f1f49 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -176,6 +176,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): def list(self, request, slug, project_id): queryset = self.get_queryset() cycle_view = request.GET.get("cycle_view", "all") + fields = [field for field in request.GET.get("fields", "").split(",") if field] queryset = queryset.order_by("-is_favorite","-created_at") @@ -280,45 +281,10 @@ class CycleViewSet(WebhookMixin, BaseViewSet): ) return Response(data, status=status.HTTP_200_OK) - - # Upcoming Cycles - if cycle_view == "upcoming": - queryset = queryset.filter(start_date__gt=timezone.now()) - return Response( - CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) - - # Completed Cycles - if cycle_view == "completed": - queryset = queryset.filter(end_date__lt=timezone.now()) - return Response( - CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) - - # Draft Cycles - if cycle_view == "draft": - queryset = queryset.filter( - end_date=None, - start_date=None, - ) - - return Response( - CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) - - # Incomplete Cycles - if cycle_view == "incomplete": - queryset = queryset.filter( - Q(end_date__gte=timezone.now().date()) | Q(end_date__isnull=True), - ) - return Response( - CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) - - # If no matching view is found return all cycles - return Response( - CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) + + cycles = CycleSerializer(queryset, many=True, fields=fields if fields else None).data + cycle_dict = {str(cycle["id"]): cycle for cycle in cycles} + return Response(cycle_dict, status=status.HTTP_200_OK) def create(self, request, slug, project_id): if ( diff --git a/apiserver/plane/app/views/module.py b/apiserver/plane/app/views/module.py index a8a8655c3..816c78897 100644 --- a/apiserver/plane/app/views/module.py +++ b/apiserver/plane/app/views/module.py @@ -152,6 +152,13 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): serializer = ModuleSerializer(module) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def list(self, request, slug, project_id): + queryset = self.get_queryset() + fields = [field for field in request.GET.get("fields", "").split(",") if field] + modules = ModuleSerializer(queryset, many=True, fields=fields if fields else None).data + modules_dict = {str(module["id"]): module for module in modules} + return Response(modules_dict, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk): queryset = self.get_queryset().get(pk=pk) diff --git a/apiserver/plane/app/views/page.py b/apiserver/plane/app/views/page.py index 9bd1f1dd4..497fa6bb1 100644 --- a/apiserver/plane/app/views/page.py +++ b/apiserver/plane/app/views/page.py @@ -157,9 +157,10 @@ class PageViewSet(BaseViewSet): def list(self, request, slug, project_id): queryset = self.get_queryset().filter(archived_at__isnull=True) - return Response( - PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) + fields = [field for field in request.GET.get("fields", "").split(",") if field] + pages = PageSerializer(queryset, many=True, fields=fields if fields else None).data + pages_dict = {str(page["id"]): page for page in pages} + return Response(pages_dict, status=status.HTTP_200_OK) def archive(self, request, slug, project_id, page_id): page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) @@ -205,14 +206,16 @@ class PageViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) def archive_list(self, request, slug, project_id): + fields = [field for field in request.GET.get("fields", "").split(",") if field] pages = Page.objects.filter( project_id=project_id, workspace__slug=slug, ).filter(archived_at__isnull=False) - return Response( - PageSerializer(pages, many=True).data, status=status.HTTP_200_OK - ) + pages = PageSerializer(pages, many=True, fields=fields if fields else None).data + pages_dict = {str(page["id"]): page for page in pages} + return Response(pages_dict, status=status.HTTP_200_OK) + def destroy(self, request, slug, project_id, pk): page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) diff --git a/apiserver/plane/app/views/project.py b/apiserver/plane/app/views/project.py index 2ed82e7e9..840266dc4 100644 --- a/apiserver/plane/app/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -179,12 +179,10 @@ class ProjectViewSet(WebhookMixin, BaseViewSet): projects, many=True ).data, ) + projects = ProjectListSerializer(projects, many=True, fields=fields if fields else None).data + project_dict = {str(project["id"]): project for project in projects} + return Response(project_dict, status=status.HTTP_200_OK) - return Response( - ProjectListSerializer( - projects, many=True, fields=fields if fields else None - ).data - ) def create(self, request, slug): try: diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py index ed72dbcf1..f522f1781 100644 --- a/apiserver/plane/app/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -75,6 +75,7 @@ from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.utils.issue_filters import issue_filters from plane.bgtasks.event_tracking_task import workspace_invite_event + class WorkSpaceViewSet(BaseViewSet): model = Workspace serializer_class = WorkSpaceSerializer @@ -172,6 +173,7 @@ class UserWorkSpacesEndpoint(BaseAPIView): ] def get(self, request): + fields = [field for field in request.GET.get("fields", "").split(",") if field] member_count = ( WorkspaceMember.objects.filter( workspace=OuterRef("id"), @@ -207,9 +209,13 @@ class UserWorkSpacesEndpoint(BaseAPIView): ) .distinct() ) - - serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True) - return Response(serializer.data, status=status.HTTP_200_OK) + workspaces = WorkSpaceSerializer( + self.filter_queryset(workspace), + fields=fields if fields else None, + many=True, + ).data + workspace_dict = {str(workspaces["id"]): workspaces for workspace in workspaces} + return Response(workspace_dict, status=status.HTTP_200_OK) class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView): @@ -406,7 +412,7 @@ class WorkspaceJoinEndpoint(BaseAPIView): # Delete the invitation workspace_invite.delete() - + # Send event workspace_invite_event.delay( user=user.id if user is not None else None, From 9a92e5a3d0f502374adda3c987a86dcb779cff53 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 12 Dec 2023 20:51:09 +0530 Subject: [PATCH 5/5] fix: pages fixes --- web/store/page.store.ts | 42 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/web/store/page.store.ts b/web/store/page.store.ts index b27beec86..b787f742b 100644 --- a/web/store/page.store.ts +++ b/web/store/page.store.ts @@ -1,5 +1,6 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"; import keyBy from "lodash/keyBy"; +import set from "lodash/set"; import isToday from "date-fns/isToday"; import isThisWeek from "date-fns/isThisWeek"; import isYesterday from "date-fns/isYesterday"; @@ -32,8 +33,8 @@ export class PageStore { constructor(_rootStore: RootStore) { makeObservable(this, { - pages: observable.ref, - archivedPages: observable.ref, + pages: observable, + archivedPages: observable, // computed projectPages: computed, favoriteProjectPages: computed, @@ -193,4 +194,41 @@ export class PageStore { throw error; } }; + + /** + * Creates a new page using the api and updated the local state in store + * @param workspaceSlug + * @param projectId + * @param data + */ + createPage = async (workspaceSlug: string, projectId: string, data: Partial) => { + const response = await this.pageService.createPage(workspaceSlug, projectId, data); + runInAction(() => { + this.pages = set(this.pages, [response.id], response); + }); + }; + + /** + * updates the page using the api and updates the local state in store + * @param workspaceSlug + * @param projectId + * @param pageId + * @param data + * @returns + */ + updatePage = async (workspaceSlug: string, projectId: string, pageId: string, data: Partial) => { + const originalPage = this.pages[pageId]; + try { + runInAction(() => { + this.pages[pageId] = { ...originalPage, ...data }; + }); + const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, data); + return response; + } catch (error) { + runInAction(() => { + this.pages[pageId] = originalPage; + }); + throw error; + } + }; }