plane/web/store/issue/issue_detail.store.ts
Anmol Singh Bhatia c33cfeb227
chore: issue link mobx integration (#3072)
* chore: issue link mobx integration

* chore: issue link code refactor and build fix
2023-12-11 22:26:29 +05:30

754 lines
21 KiB
TypeScript

import { observable, action, makeObservable, runInAction, computed, autorun } from "mobx";
// services
import { IssueService, IssueReactionService, IssueCommentService } from "services/issue";
import { NotificationService } from "services/notification.service";
// types
import { RootStore } from "../root";
import type { IIssue, IIssueActivity, IIssueLink, ILinkDetails } from "types";
// constants
import { groupReactionEmojis } from "constants/issue";
export interface IIssueDetailStore {
loader: boolean;
error: any | null;
peekId: string | null;
issues: {
[issueId: string]: IIssue;
};
issueReactions: {
[issueId: string]: any;
};
issueActivity: {
[issueId: string]: IIssueActivity[];
};
issueCommentReactions: {
[issueId: string]: {
[comment_id: string]: any;
};
};
issueSubscription: {
[issueId: string]: any;
};
setPeekId: (issueId: string | null) => void;
// computed
getIssue: IIssue | null;
getIssueReactions: any | null;
getIssueActivity: IIssueActivity[] | null;
getIssueCommentReactions: any | null;
getIssueSubscription: any | null;
// fetch issue details
fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
// deleting issue
deleteIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
fetchPeekIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
fetchIssueReactions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
createIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<void>;
removeIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<void>;
createIssueLink: (
workspaceSlug: string,
projectId: string,
issueId: string,
data: IIssueLink
) => Promise<ILinkDetails>;
updateIssueLink: (
workspaceSlug: string,
projectId: string,
issueId: string,
linkId: string,
data: IIssueLink
) => Promise<ILinkDetails>;
deleteIssueLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise<void>;
fetchIssueActivity: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
createIssueComment: (workspaceSlug: string, projectId: string, issueId: string, data: any) => Promise<void>;
updateIssueComment: (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
data: any
) => Promise<void>;
removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise<void>;
fetchIssueCommentReactions: (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string
) => Promise<void>;
creationIssueCommentReaction: (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reaction: string
) => Promise<void>;
removeIssueCommentReaction: (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reaction: string
) => Promise<void>;
fetchIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
createIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
removeIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
}
export class IssueDetailStore implements IIssueDetailStore {
loader: boolean = false;
error: any | null = null;
peekId: string | null = null;
issues: {
[issueId: string]: IIssue;
} = {};
issueReactions: {
[issueId: string]: any;
} = {};
issueActivity: {
[issueId: string]: IIssueActivity[];
} = {};
issueCommentReactions: {
[issueId: string]: any;
} = {};
issueSubscription: {
[issueId: string]: any;
} = {};
// root store
rootStore;
// service
issueService;
issueReactionService;
issueCommentService;
notificationService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observable
loader: observable.ref,
error: observable.ref,
peekId: observable.ref,
issues: observable.ref,
issueReactions: observable.ref,
issueActivity: observable.ref,
issueCommentReactions: observable.ref,
issueSubscription: observable.ref,
getIssue: computed,
getIssueReactions: computed,
getIssueActivity: computed,
getIssueCommentReactions: computed,
getIssueSubscription: computed,
setPeekId: action,
fetchIssueDetails: action,
deleteIssue: action,
fetchPeekIssueDetails: action,
fetchIssueReactions: action,
createIssueReaction: action,
removeIssueReaction: action,
createIssueLink: action,
updateIssueLink: action,
deleteIssueLink: action,
fetchIssueActivity: action,
createIssueComment: action,
updateIssueComment: action,
removeIssueComment: action,
fetchIssueCommentReactions: action,
creationIssueCommentReaction: action,
removeIssueCommentReaction: action,
fetchIssueSubscription: action,
createIssueSubscription: action,
removeIssueSubscription: action,
});
autorun(() => {
const projectId = this.rootStore?.project.projectId;
const peekId = this.peekId;
if (!projectId || !peekId) return;
const issue = this.rootStore.projectIssues.issues?.[projectId]?.[peekId];
if (issue && issue.id)
runInAction(() => {
this.issues = {
...this.issues,
[issue.id]: {
...this.issues[issue.id],
...issue,
},
};
});
});
this.rootStore = _rootStore;
this.issueService = new IssueService();
this.issueReactionService = new IssueReactionService();
this.issueCommentService = new IssueCommentService();
this.notificationService = new NotificationService();
}
get getIssue() {
if (!this.peekId) return null;
const _issue = this.issues[this.peekId];
return _issue || null;
}
get getIssueReactions() {
if (!this.peekId) return null;
const _reactions = this.issueReactions[this.peekId];
return _reactions || null;
}
get getIssueActivity() {
if (!this.peekId) return null;
const activity = this.issueActivity[this.peekId];
return activity || null;
}
get getIssueCommentReactions() {
if (!this.peekId) return null;
const _commentReactions = this.issueCommentReactions[this.peekId];
return _commentReactions || null;
}
get getIssueSubscription() {
if (!this.peekId) return null;
const _commentSubscription = this.issueSubscription[this.peekId];
return _commentSubscription || null;
}
setPeekId = (issueId: string | null) => (this.peekId = issueId);
fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
this.loader = true;
this.error = null;
this.peekId = issueId;
const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = null;
this.issues = {
...this.issues,
[issueId]: issueDetailsResponse,
};
});
return issueDetailsResponse;
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
deleteIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
const newIssues = { ...this.issues };
delete newIssues[issueId];
try {
runInAction(() => {
this.loader = true;
this.error = null;
this.issues = newIssues;
});
const user = this.rootStore.user.currentUser;
if (!user) return;
const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = null;
});
return response;
} catch (error) {
this.fetchIssueDetails(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = error;
});
return error;
}
};
fetchPeekIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
this.loader = true;
this.error = null;
this.peekId = issueId;
const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId);
await this.fetchIssueReactions(workspaceSlug, projectId, issueId);
await this.fetchIssueActivity(workspaceSlug, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = null;
this.issues = {
...this.issues,
[issueId]: issueDetailsResponse,
};
});
return issueDetailsResponse;
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
// reactions
fetchIssueReactions = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _reactions = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId);
const _issueReactions = {
...this.issueReactions,
[issueId]: groupReactionEmojis(_reactions),
};
runInAction(() => {
this.issueReactions = _issueReactions;
});
} catch (error) {
console.warn("error creating the issue reaction", error);
throw error;
}
};
createIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => {
let _currentReactions = this.getIssueReactions;
try {
const _reaction = await this.issueReactionService.createIssueReaction(workspaceSlug, projectId, issueId, {
reaction,
});
_currentReactions = {
..._currentReactions,
[reaction]: [..._currentReactions[reaction], { ..._reaction }],
};
runInAction(() => {
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
} catch (error) {
runInAction(() => {
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
console.warn("error creating the issue reaction", error);
throw error;
}
};
removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => {
let _currentReactions = this.getIssueReactions;
try {
const user = this.rootStore.user.currentUser;
if (user) {
_currentReactions = {
..._currentReactions,
[reaction]: [..._currentReactions[reaction].filter((r: any) => r.actor !== user.id)],
};
runInAction(() => {
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
await this.issueReactionService.deleteIssueReaction(workspaceSlug, projectId, issueId, reaction);
}
} catch (error) {
runInAction(() => {
this.issueReactions = {
...this.issueReactions,
[issueId]: _currentReactions,
};
});
console.warn("error removing the issue reaction", error);
throw error;
}
};
fetchIssueActivity = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const issueActivityResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId);
const _issueComments = {
...this.issueActivity,
[issueId]: [...issueActivityResponse],
};
runInAction(() => {
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error creating the issue comment", error);
throw error;
}
};
// comments
createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
try {
const _issueCommentResponse = await this.issueCommentService.createIssueComment(
workspaceSlug,
projectId,
issueId,
data
);
const _issueComments = {
...this.issueActivity,
[issueId]: [...this.issueActivity[issueId], _issueCommentResponse],
};
runInAction(() => {
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error creating the issue comment", error);
throw error;
}
};
updateIssueComment = async (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
data: any
) => {
try {
const _issueCommentResponse = await this.issueCommentService.patchIssueComment(
workspaceSlug,
projectId,
issueId,
commentId,
data
);
const _issueComments = {
...this.issueActivity,
[issueId]: this.issueActivity[issueId].map((comment) =>
comment.id === commentId ? _issueCommentResponse : comment
),
};
runInAction(() => {
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error updating the issue comment", error);
throw error;
}
};
removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const _issueComments = {
...this.issueActivity,
[issueId]: this.issueActivity[issueId]?.filter((comment) => comment.id != commentId),
};
await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId);
runInAction(() => {
this.issueActivity = _issueComments;
});
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
// comment reactions
fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId);
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issueCommentReactions[issueId],
[commentId]: groupReactionEmojis(_reactions),
},
};
runInAction(() => {
this.issueCommentReactions = _issueCommentReactions;
});
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
creationIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reaction: string
) => {
let _currentReactions = this.getIssueCommentReactions;
_currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null;
try {
const _reaction = await this.issueReactionService.createIssueCommentReaction(
workspaceSlug,
projectId,
commentId,
{
reaction,
}
);
_currentReactions = {
..._currentReactions,
[reaction]: [..._currentReactions?.[reaction], { ..._reaction }],
};
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issueCommentReactions[issueId],
[commentId]: _currentReactions,
},
};
runInAction(() => {
this.issueCommentReactions = _issueCommentReactions;
});
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
removeIssueCommentReaction = async (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
reaction: string
) => {
let _currentReactions = this.getIssueCommentReactions;
_currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null;
try {
const user = this.rootStore.user.currentUser;
if (user) {
_currentReactions = {
..._currentReactions,
[reaction]: [..._currentReactions?.[reaction].filter((r: any) => r.actor !== user.id)],
};
const _issueCommentReactions = {
...this.issueCommentReactions,
[issueId]: {
...this.issueCommentReactions[issueId],
[commentId]: _currentReactions,
},
};
runInAction(() => {
this.issueCommentReactions = _issueCommentReactions;
});
await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
}
} catch (error) {
console.warn("error removing the issue comment", error);
throw error;
}
};
createIssueLink = async (workspaceSlug: string, projectId: string, issueId: string, data: IIssueLink) => {
try {
const response = await this.issueService.createIssueLink(workspaceSlug, projectId, issueId, data);
runInAction(() => {
this.issues = {
...this.issues,
[issueId]: {
...this.issues[issueId],
issue_link: [response, ...this.issues[issueId].issue_link],
},
};
});
return response;
} catch (error) {
console.error("Failed to create link in store", error);
this.fetchIssueDetails(workspaceSlug, projectId, issueId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
updateIssueLink = async (
workspaceSlug: string,
projectId: string,
issueId: string,
linkId: string,
data: IIssueLink
) => {
try {
const response = await this.issueService.updateIssueLink(workspaceSlug, projectId, issueId, linkId, data);
runInAction(() => {
this.issues = {
...this.issues,
[issueId]: {
...this.issues[issueId],
issue_link: this.issues[issueId].issue_link.map((link) => (link.id === linkId ? response : link)),
},
};
});
return response;
} catch (error) {
console.error("Failed to update link in issue store", error);
this.fetchIssueDetails(workspaceSlug, projectId, issueId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
deleteIssueLink = async (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => {
try {
runInAction(() => {
this.issues = {
...this.issues,
[issueId]: {
...this.issues[issueId],
issue_link: this.issues[issueId].issue_link.filter((link) => link.id !== linkId),
},
};
});
await this.issueService.deleteIssueLink(workspaceSlug, projectId, issueId, linkId);
} catch (error) {
console.error("Failed to delete link in issue store", error);
runInAction(() => {
this.error = error;
});
throw error;
}
};
// subscriptions
fetchIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _subscription = await this.notificationService.getIssueNotificationSubscriptionStatus(
workspaceSlug,
projectId,
issueId
);
const _issueSubscription = {
...this.issueSubscription,
[issueId]: _subscription,
};
runInAction(() => {
this.issueSubscription = _issueSubscription;
});
} catch (error) {
console.warn("error fetching the issue subscription", error);
throw error;
}
};
createIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId);
const _issueSubscription = {
...this.issueSubscription,
[issueId]: { subscribed: true },
};
runInAction(() => {
this.issueSubscription = _issueSubscription;
});
} catch (error) {
console.warn("error creating the issue subscription", error);
throw error;
}
};
removeIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _issueSubscription = {
...this.issueSubscription,
[issueId]: { subscribed: false },
};
runInAction(() => {
this.issueSubscription = _issueSubscription;
});
await this.notificationService.unsubscribeFromIssueNotifications(workspaceSlug, projectId, issueId);
} catch (error) {
console.warn("error removing the issue subscription", error);
throw error;
}
};
}