diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index d8d69f26c..9bdd4baaf 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -69,6 +69,9 @@ from .issue import ( RelatedIssueSerializer, IssuePublicSerializer, IssueDetailSerializer, + IssueReactionLiteSerializer, + IssueAttachmentLiteSerializer, + IssueLinkLiteSerializer, ) from .module import ( diff --git a/apiserver/plane/app/serializers/base.py b/apiserver/plane/app/serializers/base.py index 446fdb6d5..6693ba931 100644 --- a/apiserver/plane/app/serializers/base.py +++ b/apiserver/plane/app/serializers/base.py @@ -58,9 +58,12 @@ class DynamicBaseSerializer(BaseSerializer): IssueSerializer, LabelSerializer, CycleIssueSerializer, - IssueFlatSerializer, + IssueLiteSerializer, IssueRelationSerializer, - InboxIssueLiteSerializer + InboxIssueLiteSerializer, + IssueReactionLiteSerializer, + IssueAttachmentLiteSerializer, + IssueLinkLiteSerializer, ) # Expansion mapper @@ -79,12 +82,34 @@ class DynamicBaseSerializer(BaseSerializer): "assignees": UserLiteSerializer, "labels": LabelSerializer, "issue_cycle": CycleIssueSerializer, - "parent": IssueSerializer, + "parent": IssueLiteSerializer, "issue_relation": IssueRelationSerializer, - "issue_inbox" : InboxIssueLiteSerializer, + "issue_inbox": InboxIssueLiteSerializer, + "issue_reactions": IssueReactionLiteSerializer, + "issue_attachment": IssueAttachmentLiteSerializer, + "issue_link": IssueLinkLiteSerializer, + "sub_issues": IssueLiteSerializer, } - - self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle", "issue_relation", "issue_inbox"] else False) + + self.fields[field] = expansion[field]( + many=( + True + if field + in [ + "members", + "assignees", + "labels", + "issue_cycle", + "issue_relation", + "issue_inbox", + "issue_reactions", + "issue_attachment", + "issue_link", + "sub_issues", + ] + else False + ) + ) return self.fields @@ -105,7 +130,11 @@ class DynamicBaseSerializer(BaseSerializer): LabelSerializer, CycleIssueSerializer, IssueRelationSerializer, - InboxIssueLiteSerializer + InboxIssueLiteSerializer, + IssueLiteSerializer, + IssueReactionLiteSerializer, + IssueAttachmentLiteSerializer, + IssueLinkLiteSerializer, ) # Expansion mapper @@ -124,9 +153,13 @@ class DynamicBaseSerializer(BaseSerializer): "assignees": UserLiteSerializer, "labels": LabelSerializer, "issue_cycle": CycleIssueSerializer, - "parent": IssueSerializer, + "parent": IssueLiteSerializer, "issue_relation": IssueRelationSerializer, - "issue_inbox" : InboxIssueLiteSerializer, + "issue_inbox": InboxIssueLiteSerializer, + "issue_reactions": IssueReactionLiteSerializer, + "issue_attachment": IssueAttachmentLiteSerializer, + "issue_link": IssueLinkLiteSerializer, + "sub_issues": IssueLiteSerializer, } # Check if field in expansion then expand the field if expand in expansion: diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index a2cd47e2c..8d4304f92 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -444,6 +444,22 @@ class IssueLinkSerializer(BaseSerializer): return IssueLink.objects.create(**validated_data) +class IssueLinkLiteSerializer(BaseSerializer): + + class Meta: + model = IssueLink + fields = [ + "id", + "issue_id", + "title", + "url", + "metadata", + "created_by_id", + "created_at", + ] + read_only_fields = fields + + class IssueAttachmentSerializer(BaseSerializer): class Meta: model = IssueAttachment @@ -459,6 +475,21 @@ class IssueAttachmentSerializer(BaseSerializer): ] +class IssueAttachmentLiteSerializer(DynamicBaseSerializer): + + class Meta: + model = IssueAttachment + fields = [ + "id", + "asset", + "attributes", + "issue_id", + "updated_at", + "updated_by_id", + ] + read_only_fields = fields + + class IssueReactionSerializer(BaseSerializer): actor_detail = UserLiteSerializer(read_only=True, source="actor") @@ -473,6 +504,18 @@ class IssueReactionSerializer(BaseSerializer): ] +class IssueReactionLiteSerializer(DynamicBaseSerializer): + + class Meta: + model = IssueReaction + fields = [ + "id", + "actor_id", + "issue_id", + "reaction", + ] + + class CommentReactionSerializer(BaseSerializer): class Meta: model = CommentReaction @@ -606,48 +649,39 @@ class IssueSerializer(DynamicBaseSerializer): read_only_fields = fields - class IssueDetailSerializer(IssueSerializer): description_html = serializers.CharField() is_subscribed = serializers.BooleanField(read_only=True) class Meta(IssueSerializer.Meta): - fields = IssueSerializer.Meta.fields + ["description_html", "is_subscribed"] + fields = IssueSerializer.Meta.fields + [ + "description_html", + "is_subscribed", + ] class IssueLiteSerializer(DynamicBaseSerializer): - workspace_detail = WorkspaceLiteSerializer( - read_only=True, source="workspace" - ) - project_detail = ProjectLiteSerializer(read_only=True, source="project") - state_detail = StateLiteSerializer(read_only=True, source="state") - label_details = LabelLiteSerializer( - read_only=True, source="labels", many=True - ) - assignee_details = UserLiteSerializer( - read_only=True, source="assignees", many=True - ) - sub_issues_count = serializers.IntegerField(read_only=True) - cycle_id = serializers.UUIDField(read_only=True) - module_id = serializers.UUIDField(read_only=True) - attachment_count = serializers.IntegerField(read_only=True) - link_count = serializers.IntegerField(read_only=True) - issue_reactions = IssueReactionSerializer(read_only=True, many=True) class Meta: model = Issue - fields = "__all__" - read_only_fields = [ - "start_date", - "target_date", - "completed_at", - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", + fields = [ + "id", + "sequence_id", + "project_id", ] + read_only_fields = fields + + +class IssueDetailSerializer(IssueSerializer): + description_html = serializers.CharField() + is_subscribed = serializers.BooleanField() + + class Meta(IssueSerializer.Meta): + fields = IssueSerializer.Meta.fields + [ + "description_html", + "is_subscribed", + ] + read_only_fields = fields class IssuePublicSerializer(BaseSerializer): diff --git a/apiserver/plane/app/views/inbox.py b/apiserver/plane/app/views/inbox.py index 5707abea0..85e2f38b2 100644 --- a/apiserver/plane/app/views/inbox.py +++ b/apiserver/plane/app/views/inbox.py @@ -3,7 +3,7 @@ import json # Django import from django.utils import timezone -from django.db.models import Q, Count, OuterRef, Func, F, Prefetch +from django.db.models import Q, Count, OuterRef, Func, F, Prefetch, Exists from django.core.serializers.json import DjangoJSONEncoder from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.fields import ArrayField @@ -25,13 +25,14 @@ from plane.db.models import ( IssueLink, IssueAttachment, ProjectMember, + IssueReaction, + IssueSubscriber, ) from plane.app.serializers import ( + IssueCreateSerializer, IssueSerializer, InboxSerializer, InboxIssueSerializer, - IssueCreateSerializer, - IssueDetailSerializer, ) from plane.utils.issue_filters import issue_filters from plane.bgtasks.issue_activites_task import issue_activity @@ -385,9 +386,7 @@ class InboxIssueViewSet(BaseViewSet): if state is not None: issue.state = state issue.save() - issue = self.get_queryset().filter(pk=issue_id).first() - serializer = IssueSerializer(issue, expand=self.expand) - return Response(serializer.data, status=status.HTTP_200_OK) + return Response(status=status.HTTP_204_NO_CONTENT) return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST ) @@ -397,11 +396,45 @@ class InboxIssueViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, inbox_id, issue_id): - issue = self.get_queryset().filter(pk=issue_id).first() - serializer = IssueDetailSerializer( - issue, - expand=self.expand, - ) + issue = ( + self.get_queryset() + .filter(pk=issue_id) + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related( + "issue", "actor" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_attachment", + queryset=IssueAttachment.objects.select_related("issue"), + ) + ) + .prefetch_related( + Prefetch( + "issue_link", + queryset=IssueLink.objects.select_related("created_by"), + ) + ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_id=OuterRef("pk"), + subscriber=request.user, + ) + ) + ) + ).first() + + if issue is None: + return Response({"error": "Requested object was not found"}, status=status.HTTP_404_NOT_FOUND) + + serializer = IssueSerializer(issue) return Response(serializer.data, status=status.HTTP_200_OK) def destroy(self, request, slug, project_id, inbox_id, issue_id): diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py index fa6fc425e..9f95c9b43 100644 --- a/apiserver/plane/app/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -528,13 +528,48 @@ class IssueViewSet(WebhookMixin, BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request, slug, project_id, pk=None): - issue = self.get_queryset().filter(pk=pk).first() - return Response( - IssueDetailSerializer( - issue, fields=self.fields, expand=self.expand - ).data, - status=status.HTTP_200_OK, - ) + issue = ( + self.get_queryset() + .filter(pk=pk) + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related( + "issue", "actor" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_attachment", + queryset=IssueAttachment.objects.select_related("issue"), + ) + ) + .prefetch_related( + Prefetch( + "issue_link", + queryset=IssueLink.objects.select_related("created_by"), + ) + ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_id=OuterRef("pk"), + subscriber=request.user, + ) + ) + ) + ).first() + if not issue: + return Response( + {"error": "The required object does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + + serializer = IssueDetailSerializer(issue, expand=self.expand) + return Response(serializer.data, status=status.HTTP_200_OK) def partial_update(self, request, slug, project_id, pk=None): issue = Issue.objects.get( @@ -560,39 +595,8 @@ class IssueViewSet(WebhookMixin, BaseViewSet): notification=True, origin=request.META.get("HTTP_ORIGIN"), ) - issue = ( - self.get_queryset() - .filter(pk=pk) - .values( - "id", - "name", - "state_id", - "sort_order", - "completed_at", - "estimate_point", - "priority", - "start_date", - "target_date", - "sequence_id", - "project_id", - "parent_id", - "cycle_id", - "module_ids", - "label_ids", - "assignee_ids", - "sub_issues_count", - "created_at", - "updated_at", - "created_by", - "updated_by", - "attachment_count", - "link_count", - "is_draft", - "archived_at", - ) - .first() - ) - return Response(issue, status=status.HTTP_200_OK) + issue = self.get_queryset().filter(pk=pk).first() + return Response(status=status.HTTP_204_NO_CONTENT) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, slug, project_id, pk=None): @@ -1581,13 +1585,47 @@ class IssueArchiveViewSet(BaseViewSet): return Response(issues, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk=None): - issue = self.get_queryset().filter(pk=pk).first() - return Response( - IssueDetailSerializer( - issue, fields=self.fields, expand=self.expand - ).data, - status=status.HTTP_200_OK, - ) + issue = ( + self.get_queryset() + .filter(pk=pk) + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related( + "issue", "actor" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_attachment", + queryset=IssueAttachment.objects.select_related("issue"), + ) + ) + .prefetch_related( + Prefetch( + "issue_link", + queryset=IssueLink.objects.select_related("created_by"), + ) + ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_id=OuterRef("pk"), + subscriber=request.user, + ) + ) + ) + ).first() + if not issue: + return Response( + {"error": "The required object does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + serializer = IssueDetailSerializer(issue, expand=self.expand) + return Response(serializer.data, status=status.HTTP_200_OK) def unarchive(self, request, slug, project_id, pk=None): issue = Issue.objects.get( @@ -2286,17 +2324,52 @@ class IssueDraftViewSet(BaseViewSet): notification=True, origin=request.META.get("HTTP_ORIGIN"), ) - return Response(serializer.data, status=status.HTTP_200_OK) + return Response(status=status.HTTP_204_NO_CONTENT) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request, slug, project_id, pk=None): - issue = self.get_queryset().filter(pk=pk).first() - return Response( - IssueSerializer( - issue, fields=self.fields, expand=self.expand - ).data, - status=status.HTTP_200_OK, - ) + issue = ( + self.get_queryset() + .filter(pk=pk) + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related( + "issue", "actor" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_attachment", + queryset=IssueAttachment.objects.select_related("issue"), + ) + ) + .prefetch_related( + Prefetch( + "issue_link", + queryset=IssueLink.objects.select_related("created_by"), + ) + ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_id=OuterRef("pk"), + subscriber=request.user, + ) + ) + ) + ).first() + + if not issue: + return Response( + {"error": "The required object does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + serializer = IssueDetailSerializer(issue, expand=self.expand) + return Response(serializer.data, status=status.HTTP_200_OK) def destroy(self, request, slug, project_id, pk=None): issue = Issue.objects.get( diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 527abe630..42c95dc4e 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -1,4 +1,7 @@ import { TIssuePriorities } from "../issues"; +import { TIssueAttachment } from "./issue_attachment"; +import { TIssueLink } from "./issue_link"; +import { TIssueReaction } from "./issue_reaction"; // new issue structure types export type TIssue = { @@ -34,7 +37,12 @@ export type TIssue = { updated_by: string; is_draft: boolean; - is_subscribed: boolean; + is_subscribed?: boolean; + + parent?: partial; + issue_reactions?: TIssueReaction[]; + issue_attachment?: TIssueAttachment[]; + issue_link?: TIssueLink[]; // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; diff --git a/packages/types/src/issues/issue_attachment.d.ts b/packages/types/src/issues/issue_attachment.d.ts index 90daa08fa..7c3819e00 100644 --- a/packages/types/src/issues/issue_attachment.d.ts +++ b/packages/types/src/issues/issue_attachment.d.ts @@ -1,17 +1,15 @@ export type TIssueAttachment = { id: string; - created_at: string; - updated_at: string; attributes: { name: string; size: number; }; asset: string; - created_by: string; + issue_id: string; + + //need + updated_at: string; updated_by: string; - project: string; - workspace: string; - issue: string; }; export type TIssueAttachmentMap = { diff --git a/packages/types/src/issues/issue_link.d.ts b/packages/types/src/issues/issue_link.d.ts index 2c469e682..10f0d2792 100644 --- a/packages/types/src/issues/issue_link.d.ts +++ b/packages/types/src/issues/issue_link.d.ts @@ -4,11 +4,13 @@ export type TIssueLinkEditableFields = { }; export type TIssueLink = TIssueLinkEditableFields & { - created_at: Date; - created_by: string; - created_by_detail: IUserLite; + created_by_id: string; id: string; metadata: any; + issue_id: string; + + //need + created_at: Date; }; export type TIssueLinkMap = { diff --git a/packages/types/src/issues/issue_reaction.d.ts b/packages/types/src/issues/issue_reaction.d.ts index 88ef27426..a4eaee0a8 100644 --- a/packages/types/src/issues/issue_reaction.d.ts +++ b/packages/types/src/issues/issue_reaction.d.ts @@ -1,15 +1,8 @@ export type TIssueReaction = { - actor: string; - actor_detail: IUserLite; - created_at: Date; - created_by: string; + actor_id: string; id: string; - issue: string; - project: string; + issue_id: string; reaction: string; - updated_at: Date; - updated_by: string; - workspace: string; }; export type TIssueReactionMap = { diff --git a/web/components/issues/draft-issue-modal.tsx b/web/components/issues/draft-issue-modal.tsx index 0324c1b03..40a79798e 100644 --- a/web/components/issues/draft-issue-modal.tsx +++ b/web/components/issues/draft-issue-modal.tsx @@ -196,9 +196,9 @@ export const CreateUpdateDraftIssueModal: React.FC = observer( const updateDraftIssue = async (payload: Partial) => { await draftIssues .updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload) - .then((res) => { + .then(() => { if (isUpdatingSingleIssue) { - mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); + mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...payload } as TIssue), false); } else { if (payload.parent_id) mutate(SUB_ISSUES(payload.parent_id.toString())); } diff --git a/web/components/issues/issue-detail/links/link-detail.tsx b/web/components/issues/issue-detail/links/link-detail.tsx index c92c13977..6c37f86f9 100644 --- a/web/components/issues/issue-detail/links/link-detail.tsx +++ b/web/components/issues/issue-detail/links/link-detail.tsx @@ -1,7 +1,7 @@ import { FC, useState } from "react"; // hooks import useToast from "hooks/use-toast"; -import { useIssueDetail } from "hooks/store"; +import { useIssueDetail, useMember } from "hooks/store"; // ui import { ExternalLinkIcon, Tooltip } from "@plane/ui"; // icons @@ -26,6 +26,7 @@ export const IssueLinkDetail: FC = (props) => { toggleIssueLinkModal: toggleIssueLinkModalStore, link: { getLinkById }, } = useIssueDetail(); + const { getUserDetails } = useMember(); const { setToastAlert } = useToast(); // state @@ -38,6 +39,8 @@ export const IssueLinkDetail: FC = (props) => { const linkDetail = getLinkById(linkId); if (!linkDetail) return <>; + const createdByDetails = getUserDetails(linkDetail.created_by_id); + return (
= (props) => {

Added {calculateTimeAgo(linkDetail.created_at)}
- by{" "} - {linkDetail.created_by_detail.is_bot - ? linkDetail.created_by_detail.first_name + " Bot" - : linkDetail.created_by_detail.display_name} + {createdByDetails && ( + <> + by {createdByDetails?.is_bot ? createdByDetails?.first_name + " Bot" : createdByDetails?.display_name} + + )}

diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index e0d54e1ea..6252fc03a 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -96,7 +96,7 @@ export const IssueDetailRoot: FC = observer((props) => { showToast: boolean = true ) => { try { - const response = await updateIssue(workspaceSlug, projectId, issueId, data); + await updateIssue(workspaceSlug, projectId, issueId, data); if (showToast) { setToastAlert({ title: "Issue updated successfully", @@ -106,7 +106,7 @@ export const IssueDetailRoot: FC = observer((props) => { } captureIssueEvent({ eventName: ISSUE_UPDATED, - payload: { ...response, state: "SUCCESS", element: "Issue detail page" }, + payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" }, updates: { changed_property: Object.keys(data).join(","), change_details: Object.values(data).join(","), diff --git a/web/components/issues/issue-detail/subscription.tsx b/web/components/issues/issue-detail/subscription.tsx index 603b3ada7..ab5983960 100644 --- a/web/components/issues/issue-detail/subscription.tsx +++ b/web/components/issues/issue-detail/subscription.tsx @@ -1,11 +1,12 @@ -import { FC, useState } from "react"; import { Bell, BellOff } from "lucide-react"; import { observer } from "mobx-react-lite"; +import { FC, useState } from "react"; // UI import { Button, Loader } from "@plane/ui"; // hooks import { useIssueDetail } from "hooks/store"; import useToast from "hooks/use-toast"; +import isNil from "lodash/isNil"; export type TIssueSubscription = { workspaceSlug: string; @@ -25,17 +26,17 @@ export const IssueSubscription: FC = observer((props) => { // state const [loading, setLoading] = useState(false); - const subscription = getSubscriptionByIssueId(issueId); + const isSubscribed = getSubscriptionByIssueId(issueId); const handleSubscription = async () => { setLoading(true); try { - if (subscription?.subscribed) await removeSubscription(workspaceSlug, projectId, issueId); + if (isSubscribed) await removeSubscription(workspaceSlug, projectId, issueId); else await createSubscription(workspaceSlug, projectId, issueId); setToastAlert({ type: "success", - title: `Issue ${subscription?.subscribed ? `unsubscribed` : `subscribed`} successfully.!`, - message: `Issue ${subscription?.subscribed ? `unsubscribed` : `subscribed`} successfully.!`, + title: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`, + message: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`, }); setLoading(false); } catch (error) { @@ -48,42 +49,32 @@ export const IssueSubscription: FC = observer((props) => { } }; - if (!subscription) + if (isNil(isSubscribed)) return ( - + ); return ( - <> - {subscription ? ( -
- -
- ) : ( - <> - - - - - )} - +
+ +
); }); diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index 97d977ace..b6a3eecc3 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -183,7 +183,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop if (!workspaceSlug || !payload.project_id || !data?.id) return; try { - const response = await currentIssueStore.updateIssue(workspaceSlug, payload.project_id, data.id, payload, viewId); + await currentIssueStore.updateIssue(workspaceSlug, payload.project_id, data.id, payload, viewId); setToastAlert({ type: "success", title: "Success!", @@ -191,11 +191,10 @@ export const CreateUpdateIssueModal: React.FC = observer((prop }); captureIssueEvent({ eventName: ISSUE_UPDATED, - payload: { ...response, state: "SUCCESS" }, + payload: { ...payload, issueId: data.id, state: "SUCCESS" }, path: router.asPath, }); handleClose(); - return response; } catch (error) { setToastAlert({ type: "error", diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 564b5a019..76dec5094 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -15,7 +15,6 @@ import { ISSUE_UPDATED, ISSUE_DELETED } from "constants/event-tracker"; interface IIssuePeekOverview { is_archived?: boolean; - onIssueUpdate?: (issue: Partial) => Promise; } export type TIssuePeekOperations = { @@ -46,7 +45,7 @@ export type TIssuePeekOperations = { }; export const IssuePeekOverview: FC = observer((props) => { - const { is_archived = false, onIssueUpdate } = props; + const { is_archived = false } = props; // hooks const { setToastAlert } = useToast(); // router @@ -87,7 +86,6 @@ export const IssuePeekOverview: FC = observer((props) => { ) => { try { const response = await updateIssue(workspaceSlug, projectId, issueId, data); - if (onIssueUpdate) await onIssueUpdate(response); if (showToast) setToastAlert({ title: "Issue updated successfully", @@ -96,7 +94,7 @@ export const IssuePeekOverview: FC = observer((props) => { }); captureIssueEvent({ eventName: ISSUE_UPDATED, - payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" }, + payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" }, updates: { changed_property: Object.keys(data).join(","), change_details: Object.values(data).join(","), @@ -314,7 +312,6 @@ export const IssuePeekOverview: FC = observer((props) => { removeIssueFromModule, removeModulesFromIssue, setToastAlert, - onIssueUpdate, captureIssueEvent, router.asPath, ] diff --git a/web/services/issue/issue.service.ts b/web/services/issue/issue.service.ts index 5d3663dd6..316288278 100644 --- a/web/services/issue/issue.service.ts +++ b/web/services/issue/issue.service.ts @@ -1,14 +1,7 @@ // services import { APIService } from "services/api.service"; // type -import type { - TIssue, - IIssueDisplayProperties, - ILinkDetails, - TIssueLink, - TIssueSubIssues, - TIssueActivity, -} from "@plane/types"; +import type { TIssue, IIssueDisplayProperties, TIssueLink, TIssueSubIssues, TIssueActivity } from "@plane/types"; // helper import { API_BASE_URL } from "helpers/common.helper"; @@ -211,7 +204,7 @@ export class IssueService extends APIService { projectId: string, issueId: string, data: Partial - ): Promise { + ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`, data) .then((response) => response?.data) .catch((error) => { @@ -225,7 +218,7 @@ export class IssueService extends APIService { issueId: string, linkId: string, data: Partial - ): Promise { + ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`, data diff --git a/web/services/issue/issue_archive.service.ts b/web/services/issue/issue_archive.service.ts index 3337f23ce..065f41d7e 100644 --- a/web/services/issue/issue_archive.service.ts +++ b/web/services/issue/issue_archive.service.ts @@ -1,6 +1,7 @@ import { APIService } from "services/api.service"; // type import { API_BASE_URL } from "helpers/common.helper"; +import { TIssue } from "@plane/types"; export class IssueArchiveService extends APIService { constructor() { @@ -25,8 +26,15 @@ export class IssueArchiveService extends APIService { }); } - async retrieveArchivedIssue(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`) + async retrieveArchivedIssue( + workspaceSlug: string, + projectId: string, + issueId: string, + queries?: any + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`, { + params: queries, + }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/web/store/issue/archived/issue.store.ts b/web/store/issue/archived/issue.store.ts index dca00d702..fa3a06f37 100644 --- a/web/store/issue/archived/issue.store.ts +++ b/web/store/issue/archived/issue.store.ts @@ -17,7 +17,7 @@ export interface IArchivedIssues { groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; // actions fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; removeIssueFromArchived: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: undefined; } @@ -111,15 +111,13 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { try { - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const issueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === issueId); if (issueIndex >= 0) runInAction(() => { this.issues[projectId].splice(issueIndex, 1); }); - - return response; } catch (error) { throw error; } diff --git a/web/store/issue/cycle/issue.store.ts b/web/store/issue/cycle/issue.store.ts index 5a9cae62c..41731e134 100644 --- a/web/store/issue/cycle/issue.store.ts +++ b/web/store/issue/cycle/issue.store.ts @@ -41,13 +41,13 @@ export interface ICycleIssues { issueId: string, data: Partial, cycleId?: string | undefined - ) => Promise; + ) => Promise; removeIssue: ( workspaceSlug: string, projectId: string, issueId: string, cycleId?: string | undefined - ) => Promise; + ) => Promise; quickAddIssue: ( workspaceSlug: string, projectId: string, @@ -207,9 +207,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { try { if (!cycleId) throw new Error("Cycle Id is required"); - const response = await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); + await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); - return response; } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); throw error; @@ -225,7 +224,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { try { if (!cycleId) throw new Error("Cycle Id is required"); - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId); const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId); @@ -233,8 +232,6 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues { runInAction(() => { this.issues[cycleId].splice(issueIndex, 1); }); - - return response; } catch (error) { throw error; } diff --git a/web/store/issue/draft/issue.store.ts b/web/store/issue/draft/issue.store.ts index 5e42e9bab..ee6d785ec 100644 --- a/web/store/issue/draft/issue.store.ts +++ b/web/store/issue/draft/issue.store.ts @@ -22,8 +22,8 @@ export interface IDraftIssues { // actions fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; - updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: undefined; } @@ -141,7 +141,7 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues { updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { try { - const response = await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); + await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); if (data.hasOwnProperty("is_draft") && data?.is_draft === false) { runInAction(() => { @@ -151,8 +151,6 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues { }); }); } - - return response; } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation"); throw error; @@ -161,7 +159,7 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues { removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { try { - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); runInAction(() => { update(this.issues, [projectId], (issueIds = []) => { @@ -169,8 +167,6 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues { return issueIds; }); }); - - return response; } catch (error) { throw error; } diff --git a/web/store/issue/issue-details/attachment.store.ts b/web/store/issue/issue-details/attachment.store.ts index 4550e7fda..5341058c1 100644 --- a/web/store/issue/issue-details/attachment.store.ts +++ b/web/store/issue/issue-details/attachment.store.ts @@ -11,6 +11,7 @@ import { IIssueDetail } from "./root.store"; import { TIssueAttachment, TIssueAttachmentMap, TIssueAttachmentIdMap } from "@plane/types"; export interface IIssueAttachmentStoreActions { + addAttachments: (issueId: string, attachments: TIssueAttachment[]) => void; fetchAttachments: (workspaceSlug: string, projectId: string, issueId: string) => Promise; createAttachment: ( workspaceSlug: string, @@ -54,6 +55,7 @@ export class IssueAttachmentStore implements IIssueAttachmentStore { // computed issueAttachments: computed, // actions + addAttachments: action.bound, fetchAttachments: action, createAttachment: action, removeAttachment: action, @@ -83,17 +85,21 @@ export class IssueAttachmentStore implements IIssueAttachmentStore { }; // actions + addAttachments = (issueId: string, attachments: TIssueAttachment[]) => { + if (attachments && attachments.length > 0) { + const _attachmentIds = attachments.map((attachment) => attachment.id); + runInAction(() => { + update(this.attachments, [issueId], (attachmentIds = []) => uniq(concat(attachmentIds, _attachmentIds))); + attachments.forEach((attachment) => set(this.attachmentMap, attachment.id, attachment)); + }); + } + }; + fetchAttachments = async (workspaceSlug: string, projectId: string, issueId: string) => { try { const response = await this.issueAttachmentService.getIssueAttachment(workspaceSlug, projectId, issueId); - if (response && response.length > 0) { - const _attachmentIds = response.map((attachment) => attachment.id); - runInAction(() => { - update(this.attachments, [issueId], (attachmentIds = []) => uniq(concat(attachmentIds, _attachmentIds))); - response.forEach((attachment) => set(this.attachmentMap, attachment.id, attachment)); - }); - } + this.addAttachments(issueId, response); return response; } catch (error) { diff --git a/web/store/issue/issue-details/issue.store.ts b/web/store/issue/issue-details/issue.store.ts index ccde8c26b..8731bf478 100644 --- a/web/store/issue/issue-details/issue.store.ts +++ b/web/store/issue/issue-details/issue.store.ts @@ -2,15 +2,15 @@ import { makeObservable } from "mobx"; // services import { IssueArchiveService, IssueService } from "services/issue"; // types -import { IIssueDetail } from "./root.store"; import { TIssue } from "@plane/types"; import { computedFn } from "mobx-utils"; +import { IIssueDetail } from "./root.store"; export interface IIssueStoreActions { // actions fetchIssue: (workspaceSlug: string, projectId: string, issueId: string, isArchived?: boolean) => Promise; - updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise; addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise; @@ -54,12 +54,13 @@ export class IssueStore implements IIssueStore { fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) => { try { const query = { - expand: "state,assignees,labels,parent", + expand: "issue_reactions,issue_attachment,issue_link,parent", }; - let issue: any; + let issue: TIssue; - if (isArchived) issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId); + if (isArchived) + issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId, query); else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query); if (!issue) throw new Error("Issue not found"); @@ -75,13 +76,15 @@ export class IssueStore implements IIssueStore { // state // issue reactions - this.rootIssueDetailStore.reaction.fetchReactions(workspaceSlug, projectId, issueId); + if (issue.issue_reactions) this.rootIssueDetailStore.addReactions(issueId, issue.issue_reactions); // fetch issue links - this.rootIssueDetailStore.link.fetchLinks(workspaceSlug, projectId, issueId); + if (issue.issue_link) this.rootIssueDetailStore.addLinks(issueId, issue.issue_link); // fetch issue attachments - this.rootIssueDetailStore.attachment.fetchAttachments(workspaceSlug, projectId, issueId); + if (issue.issue_attachment) this.rootIssueDetailStore.addAttachments(issueId, issue.issue_attachment); + + this.rootIssueDetailStore.addSubscription(issueId, issue.is_subscribed); // fetch issue activity this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); @@ -89,9 +92,6 @@ export class IssueStore implements IIssueStore { // fetch issue comments this.rootIssueDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId); - // fetch issue subscription - this.rootIssueDetailStore.subscription.fetchSubscriptions(workspaceSlug, projectId, issueId); - // fetch sub issues this.rootIssueDetailStore.subIssues.fetchSubIssues(workspaceSlug, projectId, issueId); @@ -109,14 +109,8 @@ export class IssueStore implements IIssueStore { }; updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { - const issue = await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue( - workspaceSlug, - projectId, - issueId, - data - ); + await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); - return issue; }; removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => diff --git a/web/store/issue/issue-details/link.store.ts b/web/store/issue/issue-details/link.store.ts index 751cdcaa1..81d13438c 100644 --- a/web/store/issue/issue-details/link.store.ts +++ b/web/store/issue/issue-details/link.store.ts @@ -7,16 +7,22 @@ import { IIssueDetail } from "./root.store"; import { TIssueLink, TIssueLinkMap, TIssueLinkIdMap } from "@plane/types"; export interface IIssueLinkStoreActions { + addLinks: (issueId: string, links: TIssueLink[]) => void; fetchLinks: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - createLink: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + createLink: ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial + ) => Promise; updateLink: ( workspaceSlug: string, projectId: string, issueId: string, linkId: string, data: Partial - ) => Promise; - removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise; + ) => Promise; + removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise; } export interface IIssueLinkStore extends IIssueLinkStoreActions { @@ -47,6 +53,7 @@ export class IssueLinkStore implements IIssueLinkStore { // computed issueLinks: computed, // actions + addLinks: action.bound, fetchLinks: action, createLink: action, updateLink: action, @@ -77,15 +84,17 @@ export class IssueLinkStore implements IIssueLinkStore { }; // actions + addLinks = (issueId: string, links: TIssueLink[]) => { + runInAction(() => { + this.links[issueId] = links.map((link) => link.id); + links.forEach((link) => set(this.linkMap, link.id, link)); + }); + }; + fetchLinks = async (workspaceSlug: string, projectId: string, issueId: string) => { try { const response = await this.issueService.fetchIssueLinks(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.links[issueId] = response.map((link) => link.id); - response.forEach((link) => set(this.linkMap, link.id, link)); - }); - + this.addLinks(issueId, response); return response; } catch (error) { throw error; @@ -136,7 +145,7 @@ export class IssueLinkStore implements IIssueLinkStore { removeLink = async (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => { try { - const response = await this.issueService.deleteIssueLink(workspaceSlug, projectId, issueId, linkId); + await this.issueService.deleteIssueLink(workspaceSlug, projectId, issueId, linkId); const linkIndex = this.links[issueId].findIndex((_comment) => _comment === linkId); if (linkIndex >= 0) @@ -147,7 +156,6 @@ export class IssueLinkStore implements IIssueLinkStore { // fetching activity this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); - return response; } catch (error) { throw error; } diff --git a/web/store/issue/issue-details/reaction.store.ts b/web/store/issue/issue-details/reaction.store.ts index 6b15f4445..6282ac40e 100644 --- a/web/store/issue/issue-details/reaction.store.ts +++ b/web/store/issue/issue-details/reaction.store.ts @@ -14,6 +14,7 @@ import { groupReactions } from "helpers/emoji.helper"; export interface IIssueReactionStoreActions { // actions + addReactions: (issueId: string, reactions: TIssueReaction[]) => void; fetchReactions: (workspaceSlug: string, projectId: string, issueId: string) => Promise; createReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise; removeReaction: ( @@ -50,6 +51,7 @@ export class IssueReactionStore implements IIssueReactionStore { reactions: observable, reactionMap: observable, // actions + addReactions: action.bound, fetchReactions: action, createReaction: action, removeReaction: action, @@ -82,30 +84,35 @@ export class IssueReactionStore implements IIssueReactionStore { if (reactions?.[reaction]) reactions?.[reaction].map((reactionId) => { const currentReaction = this.getReactionById(reactionId); - if (currentReaction && currentReaction.actor === userId) _userReactions.push(currentReaction); + if (currentReaction && currentReaction.actor_id === userId) _userReactions.push(currentReaction); }); }); return _userReactions; }; + addReactions = (issueId: string, reactions: TIssueReaction[]) => { + const groupedReactions = groupReactions(reactions || [], "reaction"); + + const issueReactionIdsMap: { [reaction: string]: string[] } = {}; + + Object.keys(groupedReactions).map((reactionId) => { + const reactionIds = (groupedReactions[reactionId] || []).map((reaction) => reaction.id); + issueReactionIdsMap[reactionId] = reactionIds; + }); + + runInAction(() => { + set(this.reactions, issueId, issueReactionIdsMap); + reactions.forEach((reaction) => set(this.reactionMap, reaction.id, reaction)); + }); + }; + // actions fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) => { try { const response = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId); - const groupedReactions = groupReactions(response || [], "reaction"); - const issueReactionIdsMap: { [reaction: string]: string[] } = {}; - - Object.keys(groupedReactions).map((reactionId) => { - const reactionIds = (groupedReactions[reactionId] || []).map((reaction) => reaction.id); - issueReactionIdsMap[reactionId] = reactionIds; - }); - - runInAction(() => { - set(this.reactions, issueId, issueReactionIdsMap); - response.forEach((reaction) => set(this.reactionMap, reaction.id, reaction)); - }); + this.addReactions(issueId, response); return response; } catch (error) { @@ -144,7 +151,7 @@ export class IssueReactionStore implements IIssueReactionStore { ) => { try { const userReactions = this.reactionsByUser(issueId, userId); - const currentReaction = find(userReactions, { actor: userId, reaction: reaction }); + const currentReaction = find(userReactions, { actor_id: userId, reaction: reaction }); if (currentReaction && currentReaction.id) { runInAction(() => { diff --git a/web/store/issue/issue-details/root.store.ts b/web/store/issue/issue-details/root.store.ts index d78add446..4c2d6add1 100644 --- a/web/store/issue/issue-details/root.store.ts +++ b/web/store/issue/issue-details/root.store.ts @@ -15,8 +15,15 @@ import { IssueCommentReactionStore, IIssueCommentReactionStoreActions, } from "./comment_reaction.store"; - -import { TIssue, TIssueComment, TIssueCommentReaction, TIssueLink, TIssueRelationTypes } from "@plane/types"; +import { + TIssue, + TIssueAttachment, + TIssueComment, + TIssueCommentReaction, + TIssueLink, + TIssueReaction, + TIssueRelationTypes, +} from "@plane/types"; export type TPeekIssue = { workspaceSlug: string; @@ -151,6 +158,7 @@ export class IssueDetail implements IIssueDetail { this.issue.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId); // reactions + addReactions = (issueId: string, reactions: TIssueReaction[]) => this.reaction.addReactions(issueId, reactions); fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) => this.reaction.fetchReactions(workspaceSlug, projectId, issueId); createReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => @@ -164,6 +172,8 @@ export class IssueDetail implements IIssueDetail { ) => this.reaction.removeReaction(workspaceSlug, projectId, issueId, reaction, userId); // attachments + addAttachments = (issueId: string, attachments: TIssueAttachment[]) => + this.attachment.addAttachments(issueId, attachments); fetchAttachments = async (workspaceSlug: string, projectId: string, issueId: string) => this.attachment.fetchAttachments(workspaceSlug, projectId, issueId); createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, data: FormData) => @@ -172,6 +182,7 @@ export class IssueDetail implements IIssueDetail { this.attachment.removeAttachment(workspaceSlug, projectId, issueId, attachmentId); // link + addLinks = (issueId: string, links: TIssueLink[]) => this.link.addLinks(issueId, links); fetchLinks = async (workspaceSlug: string, projectId: string, issueId: string) => this.link.fetchLinks(workspaceSlug, projectId, issueId); createLink = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => @@ -206,6 +217,8 @@ export class IssueDetail implements IIssueDetail { this.subIssues.deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId); // subscription + addSubscription = (issueId: string, isSubscribed: boolean | undefined | null) => + this.subscription.addSubscription(issueId, isSubscribed); fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) => this.subscription.fetchSubscriptions(workspaceSlug, projectId, issueId); createSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => diff --git a/web/store/issue/issue-details/subscription.store.ts b/web/store/issue/issue-details/subscription.store.ts index 02f863cbe..276c952f4 100644 --- a/web/store/issue/issue-details/subscription.store.ts +++ b/web/store/issue/issue-details/subscription.store.ts @@ -6,21 +6,22 @@ import { NotificationService } from "services/notification.service"; import { IIssueDetail } from "./root.store"; export interface IIssueSubscriptionStoreActions { - fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + addSubscription: (issueId: string, isSubscribed: boolean | undefined | null) => void; + fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; } export interface IIssueSubscriptionStore extends IIssueSubscriptionStoreActions { // observables - subscriptionMap: Record>>; // Record defines subscriptionId as key and link as value + subscriptionMap: Record>; // Record defines subscriptionId as key and link as value // helper methods - getSubscriptionByIssueId: (issueId: string) => Record | undefined; + getSubscriptionByIssueId: (issueId: string) => boolean | undefined; } export class IssueSubscriptionStore implements IIssueSubscriptionStore { // observables - subscriptionMap: Record>> = {}; + subscriptionMap: Record> = {}; // root store rootIssueDetail: IIssueDetail; // services @@ -31,6 +32,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore { // observables subscriptionMap: observable, // actions + addSubscription: action.bound, fetchSubscriptions: action, createSubscription: action, removeSubscription: action, @@ -49,22 +51,26 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore { return this.subscriptionMap[issueId]?.[currentUserId] ?? undefined; }; + addSubscription = (issueId: string, isSubscribed: boolean | undefined | null) => { + const currentUserId = this.rootIssueDetail.rootIssueStore.currentUserId; + if (!currentUserId) throw new Error("user id not available"); + + runInAction(() => { + set(this.subscriptionMap, [issueId, currentUserId], isSubscribed ?? false); + }); + }; + fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) => { try { - const currentUserId = this.rootIssueDetail.rootIssueStore.currentUserId; - if (!currentUserId) throw new Error("user id not available"); - const subscription = await this.notificationService.getIssueNotificationSubscriptionStatus( workspaceSlug, projectId, issueId ); - runInAction(() => { - set(this.subscriptionMap, [issueId, currentUserId], subscription); - }); + this.addSubscription(issueId, subscription?.subscribed); - return subscription; + return subscription?.subscribed; } catch (error) { throw error; } @@ -79,9 +85,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore { set(this.subscriptionMap, [issueId, currentUserId], { subscribed: true }); }); - const response = await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId); - - return response; + await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId); } catch (error) { this.fetchSubscriptions(workspaceSlug, projectId, issueId); throw error; @@ -97,13 +101,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore { set(this.subscriptionMap, [issueId, currentUserId], { subscribed: false }); }); - const response = await this.notificationService.unsubscribeFromIssueNotifications( - workspaceSlug, - projectId, - issueId - ); - - return response; + await this.notificationService.unsubscribeFromIssueNotifications(workspaceSlug, projectId, issueId); } catch (error) { this.fetchSubscriptions(workspaceSlug, projectId, issueId); throw error; diff --git a/web/store/issue/module/issue.store.ts b/web/store/issue/module/issue.store.ts index b83519cd2..e9b96ac54 100644 --- a/web/store/issue/module/issue.store.ts +++ b/web/store/issue/module/issue.store.ts @@ -39,13 +39,13 @@ export interface IModuleIssues { issueId: string, data: Partial, moduleId?: string | undefined - ) => Promise; + ) => Promise; removeIssue: ( workspaceSlug: string, projectId: string, issueId: string, moduleId?: string | undefined - ) => Promise; + ) => Promise; quickAddIssue: ( workspaceSlug: string, projectId: string, @@ -212,9 +212,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { try { if (!moduleId) throw new Error("Module Id is required"); - const response = await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); + await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); - return response; } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); throw error; @@ -230,7 +229,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { try { if (!moduleId) throw new Error("Module Id is required"); - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId); const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId); @@ -238,8 +237,6 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues { runInAction(() => { this.issues[moduleId].splice(issueIndex, 1); }); - - return response; } catch (error) { throw error; } diff --git a/web/store/issue/profile/issue.store.ts b/web/store/issue/profile/issue.store.ts index 5cde37230..461928c64 100644 --- a/web/store/issue/profile/issue.store.ts +++ b/web/store/issue/profile/issue.store.ts @@ -41,13 +41,13 @@ export interface IProfileIssues { issueId: string, data: Partial, userId?: string | undefined - ) => Promise; + ) => Promise; removeIssue: ( workspaceSlug: string, projectId: string, issueId: string, userId?: string | undefined - ) => Promise; + ) => Promise; quickAddIssue: undefined; } @@ -221,14 +221,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { if (!userId) throw new Error("user id is required"); this.rootStore.issues.updateIssue(issueId, data); - const response = await this.rootIssueStore.projectIssues.updateIssue( - workspaceSlug, - projectId, - data.id as keyof TIssue, - data - ); - - return response; + await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, data.id as keyof TIssue, data); } catch (error) { if (this.currentView) this.fetchIssues(workspaceSlug, undefined, "mutation", userId, this.currentView); throw error; @@ -243,7 +236,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { ) => { if (!userId) return; try { - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const uniqueViewId = `${workspaceSlug}_${this.currentView}`; @@ -252,8 +245,6 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { runInAction(() => { this.issues[userId][uniqueViewId].splice(issueIndex, 1); }); - - return response; } catch (error) { throw error; } diff --git a/web/store/issue/project-views/issue.store.ts b/web/store/issue/project-views/issue.store.ts index d643999c9..8327ffcce 100644 --- a/web/store/issue/project-views/issue.store.ts +++ b/web/store/issue/project-views/issue.store.ts @@ -34,13 +34,13 @@ export interface IProjectViewIssues { issueId: string, data: Partial, viewId?: string | undefined - ) => Promise; + ) => Promise; removeIssue: ( workspaceSlug: string, projectId: string, issueId: string, viewId?: string | undefined - ) => Promise; + ) => Promise; quickAddIssue: ( workspaceSlug: string, projectId: string, @@ -181,8 +181,7 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI try { if (!viewId) throw new Error("View Id is required"); - const response = await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); - return response; + await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation"); throw error; @@ -198,15 +197,13 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI try { if (!viewId) throw new Error("View Id is required"); - const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId); if (issueIndex >= 0) runInAction(() => { this.issues[viewId].splice(issueIndex, 1); }); - - return response; } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation"); throw error; diff --git a/web/store/issue/project/issue.store.ts b/web/store/issue/project/issue.store.ts index 2000a440a..76bf7bcc2 100644 --- a/web/store/issue/project/issue.store.ts +++ b/web/store/issue/project/issue.store.ts @@ -21,8 +21,8 @@ export interface IProjectIssues { // action fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; - updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; - removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise; removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise; } @@ -144,8 +144,7 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues { try { this.rootStore.issues.updateIssue(issueId, data); - const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); - return response; + await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); } catch (error) { this.fetchIssues(workspaceSlug, projectId, "mutation"); throw error; @@ -154,14 +153,13 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues { removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { try { - const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); + await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); runInAction(() => { pull(this.issues[projectId], issueId); }); this.rootStore.issues.removeIssue(issueId); - return response; } catch (error) { throw error; } diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts index e168f85c2..e2b8418c7 100644 --- a/web/store/issue/workspace/issue.store.ts +++ b/web/store/issue/workspace/issue.store.ts @@ -30,13 +30,13 @@ export interface IWorkspaceIssues { issueId: string, data: Partial, viewId?: string | undefined - ) => Promise; + ) => Promise; removeIssue: ( workspaceSlug: string, projectId: string, issueId: string, viewId?: string | undefined - ) => Promise; + ) => Promise; } export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues { @@ -165,8 +165,7 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue if (!viewId) throw new Error("View id is required"); this.rootStore.issues.updateIssue(issueId, data); - const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); - return response; + await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); } catch (error) { if (viewId) this.fetchIssues(workspaceSlug, viewId, "mutation"); throw error; @@ -184,7 +183,7 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue const uniqueViewId = `${workspaceSlug}_${viewId}`; - const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); + await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); const issueIndex = this.issues[uniqueViewId].findIndex((_issueId) => _issueId === issueId); if (issueIndex >= 0) @@ -193,8 +192,6 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue }); this.rootStore.issues.removeIssue(issueId); - - return response; } catch (error) { throw error; }