forked from github/plane
[WEB-468] fix: issue detail endpoints (#3722)
* dev: add is_subscriber to issue details endpoint * dev: remove is_subscribed annotation from detail serializers * dev: update issue details endpoint * dev: inbox issue create * dev: issue detail serializer * dev: optimize and add extra fields for issue details * dev: remove data from issue updates * dev: add fields for issue link and attachment * remove expecting a issue response while updating and deleting an issue * change link, attachment and reaction types and modify store to recieve their data from within the issue detail API call * make changes for subscription store to recieve data from issue detail API call * dev: add issue reaction id * add query prarms for archived issue --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
7927b7678d
commit
03e5f4a5bd
@ -69,6 +69,9 @@ from .issue import (
|
||||
RelatedIssueSerializer,
|
||||
IssuePublicSerializer,
|
||||
IssueDetailSerializer,
|
||||
IssueReactionLiteSerializer,
|
||||
IssueAttachmentLiteSerializer,
|
||||
IssueLinkLiteSerializer,
|
||||
)
|
||||
|
||||
from .module import (
|
||||
|
@ -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_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_reactions": IssueReactionLiteSerializer,
|
||||
"issue_attachment": IssueAttachmentLiteSerializer,
|
||||
"issue_link": IssueLinkLiteSerializer,
|
||||
"sub_issues": IssueLiteSerializer,
|
||||
}
|
||||
# Check if field in expansion then expand the field
|
||||
if expand in expansion:
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
10
packages/types/src/issues/issue.d.ts
vendored
10
packages/types/src/issues/issue.d.ts
vendored
@ -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<TIssue>;
|
||||
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;
|
||||
|
10
packages/types/src/issues/issue_attachment.d.ts
vendored
10
packages/types/src/issues/issue_attachment.d.ts
vendored
@ -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 = {
|
||||
|
8
packages/types/src/issues/issue_link.d.ts
vendored
8
packages/types/src/issues/issue_link.d.ts
vendored
@ -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 = {
|
||||
|
11
packages/types/src/issues/issue_reaction.d.ts
vendored
11
packages/types/src/issues/issue_reaction.d.ts
vendored
@ -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 = {
|
||||
|
@ -196,9 +196,9 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
||||
const updateDraftIssue = async (payload: Partial<TIssue>) => {
|
||||
await draftIssues
|
||||
.updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
if (isUpdatingSingleIssue) {
|
||||
mutate<TIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||
mutate<TIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...payload } as TIssue), false);
|
||||
} else {
|
||||
if (payload.parent_id) mutate(SUB_ISSUES(payload.parent_id.toString()));
|
||||
}
|
||||
|
@ -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<TIssueLinkDetail> = (props) => {
|
||||
toggleIssueLinkModal: toggleIssueLinkModalStore,
|
||||
link: { getLinkById },
|
||||
} = useIssueDetail();
|
||||
const { getUserDetails } = useMember();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// state
|
||||
@ -38,6 +39,8 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
const createdByDetails = getUserDetails(linkDetail.created_by_id);
|
||||
|
||||
return (
|
||||
<div key={linkId}>
|
||||
<IssueLinkCreateUpdateModal
|
||||
@ -110,10 +113,11 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||
Added {calculateTimeAgo(linkDetail.created_at)}
|
||||
<br />
|
||||
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}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,7 +96,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = 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<TIssueDetailRoot> = 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(","),
|
||||
|
@ -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<TIssueSubscription> = 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<TIssueSubscription> = observer((props) => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!subscription)
|
||||
if (isNil(isSubscribed))
|
||||
return (
|
||||
<Loader>
|
||||
<Loader.Item width="92px" height="27px" />
|
||||
<Loader.Item width="106px" height="28px" />
|
||||
</Loader>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{subscription ? (
|
||||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
prependIcon={subscription?.subscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
||||
prependIcon={isSubscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
||||
variant="outline-primary"
|
||||
className="hover:!bg-custom-primary-100/20"
|
||||
onClick={handleSubscription}
|
||||
>
|
||||
{loading ? (
|
||||
<span>
|
||||
<span className="hidden sm:block">Loading...</span>
|
||||
<span className="hidden sm:block">Loading</span>...
|
||||
</span>
|
||||
) : subscription?.subscribed ? (
|
||||
) : isSubscribed ? (
|
||||
<div className="hidden sm:block">Unsubscribe</div>
|
||||
) : (
|
||||
<div className="hidden sm:block">Subscribe</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Loader>
|
||||
<Loader.Item height="28px" width="106px" />
|
||||
</Loader>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -183,7 +183,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = 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<IssuesModalProps> = 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",
|
||||
|
@ -15,7 +15,6 @@ import { ISSUE_UPDATED, ISSUE_DELETED } from "constants/event-tracker";
|
||||
|
||||
interface IIssuePeekOverview {
|
||||
is_archived?: boolean;
|
||||
onIssueUpdate?: (issue: Partial<TIssue>) => Promise<void>;
|
||||
}
|
||||
|
||||
export type TIssuePeekOperations = {
|
||||
@ -46,7 +45,7 @@ export type TIssuePeekOperations = {
|
||||
};
|
||||
|
||||
export const IssuePeekOverview: FC<IIssuePeekOverview> = 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<IIssuePeekOverview> = 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<IIssuePeekOverview> = 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<IIssuePeekOverview> = observer((props) => {
|
||||
removeIssueFromModule,
|
||||
removeModulesFromIssue,
|
||||
setToastAlert,
|
||||
onIssueUpdate,
|
||||
captureIssueEvent,
|
||||
router.asPath,
|
||||
]
|
||||
|
@ -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<TIssueLink>
|
||||
): Promise<ILinkDetails> {
|
||||
): Promise<TIssueLink> {
|
||||
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<TIssueLink>
|
||||
): Promise<ILinkDetails> {
|
||||
): Promise<TIssueLink> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`,
|
||||
data
|
||||
|
@ -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<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`)
|
||||
async retrieveArchivedIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
queries?: any
|
||||
): Promise<TIssue> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`, {
|
||||
params: queries,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
|
@ -17,7 +17,7 @@ export interface IArchivedIssues {
|
||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
|
||||
// actions
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
removeIssueFromArchived: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
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;
|
||||
}
|
||||
|
@ -41,13 +41,13 @@ export interface ICycleIssues {
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
cycleId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
cycleId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
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;
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ export interface IDraftIssues {
|
||||
// actions
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
quickAddIssue: undefined;
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues {
|
||||
|
||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||
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;
|
||||
}
|
||||
|
@ -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<TIssueAttachment[]>;
|
||||
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) {
|
||||
|
@ -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<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>;
|
||||
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<TIssue>;
|
||||
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<any>;
|
||||
@ -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<TIssue>) => {
|
||||
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) =>
|
||||
|
@ -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<TIssueLink[]>;
|
||||
createLink: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssueLink>) => Promise<any>;
|
||||
createLink: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
data: Partial<TIssueLink>
|
||||
) => Promise<TIssueLink>;
|
||||
updateLink: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
linkId: string,
|
||||
data: Partial<TIssueLink>
|
||||
) => Promise<any>;
|
||||
removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise<any>;
|
||||
) => Promise<TIssueLink>;
|
||||
removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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<TIssueReaction[]>;
|
||||
createReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<any>;
|
||||
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,18 +84,15 @@ 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;
|
||||
};
|
||||
|
||||
// actions
|
||||
fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
const response = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId);
|
||||
const groupedReactions = groupReactions(response || [], "reaction");
|
||||
addReactions = (issueId: string, reactions: TIssueReaction[]) => {
|
||||
const groupedReactions = groupReactions(reactions || [], "reaction");
|
||||
|
||||
const issueReactionIdsMap: { [reaction: string]: string[] } = {};
|
||||
|
||||
@ -104,8 +103,16 @@ export class IssueReactionStore implements IIssueReactionStore {
|
||||
|
||||
runInAction(() => {
|
||||
set(this.reactions, issueId, issueReactionIdsMap);
|
||||
response.forEach((reaction) => set(this.reactionMap, reaction.id, reaction));
|
||||
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);
|
||||
|
||||
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(() => {
|
||||
|
@ -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<TIssueLink>) =>
|
||||
@ -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) =>
|
||||
|
@ -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<any>;
|
||||
createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
||||
removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
||||
addSubscription: (issueId: string, isSubscribed: boolean | undefined | null) => void;
|
||||
fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<boolean>;
|
||||
createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IIssueSubscriptionStore extends IIssueSubscriptionStoreActions {
|
||||
// observables
|
||||
subscriptionMap: Record<string, Record<string, Record<string, boolean>>>; // Record defines subscriptionId as key and link as value
|
||||
subscriptionMap: Record<string, Record<string, boolean>>; // Record defines subscriptionId as key and link as value
|
||||
// helper methods
|
||||
getSubscriptionByIssueId: (issueId: string) => Record<string, boolean> | undefined;
|
||||
getSubscriptionByIssueId: (issueId: string) => boolean | undefined;
|
||||
}
|
||||
|
||||
export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
||||
// observables
|
||||
subscriptionMap: Record<string, Record<string, Record<string, boolean>>> = {};
|
||||
subscriptionMap: Record<string, Record<string, boolean>> = {};
|
||||
// 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;
|
||||
};
|
||||
|
||||
fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
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 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;
|
||||
|
@ -39,13 +39,13 @@ export interface IModuleIssues {
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
moduleId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
moduleId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
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;
|
||||
}
|
||||
|
@ -41,13 +41,13 @@ export interface IProfileIssues {
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
userId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
userId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
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;
|
||||
}
|
||||
|
@ -34,13 +34,13 @@ export interface IProjectViewIssues {
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
viewId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
viewId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
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;
|
||||
|
@ -21,8 +21,8 @@ export interface IProjectIssues {
|
||||
// action
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue>;
|
||||
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
@ -30,13 +30,13 @@ export interface IWorkspaceIssues {
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
viewId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
viewId?: string | undefined
|
||||
) => Promise<TIssue | undefined>;
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user