Merge branch 'refactor/mobx-store' of gurusainath:makeplane/plane into refactor/mobx-store

This commit is contained in:
gurusainath 2023-12-12 23:25:34 +05:30
commit 1ccd0a683b
18 changed files with 557 additions and 481 deletions

View File

@ -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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -132,7 +132,7 @@ export class ModuleService extends APIService {
workspaceSlug: string,
projectId: string,
moduleId: string,
data: ModuleLink
data: Partial<ModuleLink>
): Promise<ILinkDetails> {
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<ModuleLink>
): Promise<ILinkDetails> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`,

View File

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

View File

@ -1,5 +1,5 @@
import { action, computed, makeObservable, observable } from "mobx";
import { ParsedUrlQuery } from "querystring";
import { action, makeObservable, observable, computed } from "mobx";
import { ParsedUrlQuery } from "node:querystring";
export interface IRouterStore {
query: ParsedUrlQuery;

View File

@ -1,4 +1,5 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
// 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;
}
};

View File

@ -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/set";
// 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<string, IIssueLabel>;
labelMap: Record<string, IIssueLabel>;
projectLabels: IIssueLabel[] | undefined;
projectLabelsTree: IIssueLabelTree[] | undefined;
fetchProjectLabels: (workspaceSlug: string, projectId: string) => Promise<IIssueLabel[]>;
@ -35,14 +35,14 @@ export interface ILabelStore {
}
export class LabelStore {
labels: Record<string, IIssueLabel> = {};
labelMap: Record<string, IIssueLabel> = {};
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<IIssueLabel[]>
@ -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<IIssueLabel>) => {
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<IIssueLabel>
*/
updateLabel = async (workspaceSlug: string, projectId: string, labelId: string, data: Partial<IIssueLabel>) => {
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;
});
}
};

View File

@ -1,4 +1,5 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
// 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;
});
}
};

234
web/store/page.store.ts Normal file
View File

@ -0,0 +1,234 @@
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";
// services
import { PageService } from "services/page.service";
// types
import { IPage, IRecentPages } from "types";
// store
import { RootStore } from "./root.store";
export interface IPageStore {
pages: Record<string, IPage>;
archivedPages: Record<string, IPage>;
projectPages: IPage[] | undefined;
favoriteProjectPages: IPage[] | undefined;
privateProjectPages: IPage[] | undefined;
sharedProjectPages: IPage[] | undefined;
fetchProjectPages: (workspaceSlug: string, projectId: string) => Promise<IPage[]>;
}
export class PageStore {
pages: Record<string, IPage> = {};
archivedPages: Record<string, IPage> = {};
// services
pageService;
// stores
router;
constructor(_rootStore: RootStore) {
makeObservable(this, {
pages: observable,
archivedPages: observable,
// 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<IPage[]>
*/
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<IPage[]>
*/
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;
}
};
/**
* 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<IPage>) => {
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<IPage>) => {
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;
}
};
}

View File

@ -1,3 +1,4 @@
import set from "lodash/set";
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;
});
}
};

View File

@ -1,4 +1,5 @@
import { observable, action, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
// 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;
});

View File

@ -1,9 +1,11 @@
import set from "lodash/set";
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,11 +50,11 @@ export class ProjectsStore implements IProjectsStore {
projectId: string | null = null;
searchQuery: string = "";
projectsMap: {
projectMap: {
[workspaceSlug: string]: {
[projectId: string]: IProject; // projectId: project Info
};
};
} = {};
// root store
rootStore: RootStore;
@ -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.rootStore.app.router.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<IProject>) => {
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);

View File

@ -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/set";
// 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<string, IState>;
stateMap: Record<string, IState>;
projectStates: IState[] | undefined;
groupedProjectStates: Record<string, IState[]> | undefined;
}
export class StateStore implements IStateStore {
states: Record<string, IState> = {};
stateMap: Record<string, IState> = {};
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<IState>) => {
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<IState>) => {
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;
});
}
};

View File

@ -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) => {