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,
|
RelatedIssueSerializer,
|
||||||
IssuePublicSerializer,
|
IssuePublicSerializer,
|
||||||
IssueDetailSerializer,
|
IssueDetailSerializer,
|
||||||
|
IssueReactionLiteSerializer,
|
||||||
|
IssueAttachmentLiteSerializer,
|
||||||
|
IssueLinkLiteSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .module import (
|
from .module import (
|
||||||
|
@ -58,9 +58,12 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
LabelSerializer,
|
LabelSerializer,
|
||||||
CycleIssueSerializer,
|
CycleIssueSerializer,
|
||||||
IssueFlatSerializer,
|
IssueLiteSerializer,
|
||||||
IssueRelationSerializer,
|
IssueRelationSerializer,
|
||||||
InboxIssueLiteSerializer
|
InboxIssueLiteSerializer,
|
||||||
|
IssueReactionLiteSerializer,
|
||||||
|
IssueAttachmentLiteSerializer,
|
||||||
|
IssueLinkLiteSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Expansion mapper
|
# Expansion mapper
|
||||||
@ -79,12 +82,34 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
"assignees": UserLiteSerializer,
|
"assignees": UserLiteSerializer,
|
||||||
"labels": LabelSerializer,
|
"labels": LabelSerializer,
|
||||||
"issue_cycle": CycleIssueSerializer,
|
"issue_cycle": CycleIssueSerializer,
|
||||||
"parent": IssueSerializer,
|
"parent": IssueLiteSerializer,
|
||||||
"issue_relation": IssueRelationSerializer,
|
"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
|
return self.fields
|
||||||
|
|
||||||
@ -105,7 +130,11 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
LabelSerializer,
|
LabelSerializer,
|
||||||
CycleIssueSerializer,
|
CycleIssueSerializer,
|
||||||
IssueRelationSerializer,
|
IssueRelationSerializer,
|
||||||
InboxIssueLiteSerializer
|
InboxIssueLiteSerializer,
|
||||||
|
IssueLiteSerializer,
|
||||||
|
IssueReactionLiteSerializer,
|
||||||
|
IssueAttachmentLiteSerializer,
|
||||||
|
IssueLinkLiteSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Expansion mapper
|
# Expansion mapper
|
||||||
@ -124,9 +153,13 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
"assignees": UserLiteSerializer,
|
"assignees": UserLiteSerializer,
|
||||||
"labels": LabelSerializer,
|
"labels": LabelSerializer,
|
||||||
"issue_cycle": CycleIssueSerializer,
|
"issue_cycle": CycleIssueSerializer,
|
||||||
"parent": IssueSerializer,
|
"parent": IssueLiteSerializer,
|
||||||
"issue_relation": IssueRelationSerializer,
|
"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
|
# Check if field in expansion then expand the field
|
||||||
if expand in expansion:
|
if expand in expansion:
|
||||||
|
@ -444,6 +444,22 @@ class IssueLinkSerializer(BaseSerializer):
|
|||||||
return IssueLink.objects.create(**validated_data)
|
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 IssueAttachmentSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueAttachment
|
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):
|
class IssueReactionSerializer(BaseSerializer):
|
||||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
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 CommentReactionSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CommentReaction
|
model = CommentReaction
|
||||||
@ -606,48 +649,39 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IssueDetailSerializer(IssueSerializer):
|
class IssueDetailSerializer(IssueSerializer):
|
||||||
description_html = serializers.CharField()
|
description_html = serializers.CharField()
|
||||||
is_subscribed = serializers.BooleanField(read_only=True)
|
is_subscribed = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
class Meta(IssueSerializer.Meta):
|
class Meta(IssueSerializer.Meta):
|
||||||
fields = IssueSerializer.Meta.fields + ["description_html", "is_subscribed"]
|
fields = IssueSerializer.Meta.fields + [
|
||||||
|
"description_html",
|
||||||
|
"is_subscribed",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class IssueLiteSerializer(DynamicBaseSerializer):
|
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:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
fields = "__all__"
|
fields = [
|
||||||
read_only_fields = [
|
"id",
|
||||||
"start_date",
|
"sequence_id",
|
||||||
"target_date",
|
"project_id",
|
||||||
"completed_at",
|
|
||||||
"workspace",
|
|
||||||
"project",
|
|
||||||
"created_by",
|
|
||||||
"updated_by",
|
|
||||||
"created_at",
|
|
||||||
"updated_at",
|
|
||||||
]
|
]
|
||||||
|
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):
|
class IssuePublicSerializer(BaseSerializer):
|
||||||
|
@ -3,7 +3,7 @@ import json
|
|||||||
|
|
||||||
# Django import
|
# Django import
|
||||||
from django.utils import timezone
|
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.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
@ -25,13 +25,14 @@ from plane.db.models import (
|
|||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
|
IssueReaction,
|
||||||
|
IssueSubscriber,
|
||||||
)
|
)
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
|
IssueCreateSerializer,
|
||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
InboxSerializer,
|
InboxSerializer,
|
||||||
InboxIssueSerializer,
|
InboxIssueSerializer,
|
||||||
IssueCreateSerializer,
|
|
||||||
IssueDetailSerializer,
|
|
||||||
)
|
)
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.bgtasks.issue_activites_task import issue_activity
|
from plane.bgtasks.issue_activites_task import issue_activity
|
||||||
@ -385,9 +386,7 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
if state is not None:
|
if state is not None:
|
||||||
issue.state = state
|
issue.state = state
|
||||||
issue.save()
|
issue.save()
|
||||||
issue = self.get_queryset().filter(pk=issue_id).first()
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
serializer = IssueSerializer(issue, expand=self.expand)
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
return Response(
|
return Response(
|
||||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||||
)
|
)
|
||||||
@ -397,11 +396,45 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, inbox_id, issue_id):
|
def retrieve(self, request, slug, project_id, inbox_id, issue_id):
|
||||||
issue = self.get_queryset().filter(pk=issue_id).first()
|
issue = (
|
||||||
serializer = IssueDetailSerializer(
|
self.get_queryset()
|
||||||
issue,
|
.filter(pk=issue_id)
|
||||||
expand=self.expand,
|
.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)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, inbox_id, issue_id):
|
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)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
issue = self.get_queryset().filter(pk=pk).first()
|
issue = (
|
||||||
return Response(
|
self.get_queryset()
|
||||||
IssueDetailSerializer(
|
.filter(pk=pk)
|
||||||
issue, fields=self.fields, expand=self.expand
|
.prefetch_related(
|
||||||
).data,
|
Prefetch(
|
||||||
status=status.HTTP_200_OK,
|
"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):
|
def partial_update(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
@ -560,39 +595,8 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
)
|
)
|
||||||
issue = (
|
issue = self.get_queryset().filter(pk=pk).first()
|
||||||
self.get_queryset()
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
.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)
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, pk=None):
|
def destroy(self, request, slug, project_id, pk=None):
|
||||||
@ -1581,13 +1585,47 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
issue = self.get_queryset().filter(pk=pk).first()
|
issue = (
|
||||||
return Response(
|
self.get_queryset()
|
||||||
IssueDetailSerializer(
|
.filter(pk=pk)
|
||||||
issue, fields=self.fields, expand=self.expand
|
.prefetch_related(
|
||||||
).data,
|
Prefetch(
|
||||||
status=status.HTTP_200_OK,
|
"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):
|
def unarchive(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
@ -2286,17 +2324,52 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
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)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
issue = self.get_queryset().filter(pk=pk).first()
|
issue = (
|
||||||
return Response(
|
self.get_queryset()
|
||||||
IssueSerializer(
|
.filter(pk=pk)
|
||||||
issue, fields=self.fields, expand=self.expand
|
.prefetch_related(
|
||||||
).data,
|
Prefetch(
|
||||||
status=status.HTTP_200_OK,
|
"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):
|
def destroy(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(
|
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 { TIssuePriorities } from "../issues";
|
||||||
|
import { TIssueAttachment } from "./issue_attachment";
|
||||||
|
import { TIssueLink } from "./issue_link";
|
||||||
|
import { TIssueReaction } from "./issue_reaction";
|
||||||
|
|
||||||
// new issue structure types
|
// new issue structure types
|
||||||
export type TIssue = {
|
export type TIssue = {
|
||||||
@ -34,7 +37,12 @@ export type TIssue = {
|
|||||||
updated_by: string;
|
updated_by: string;
|
||||||
|
|
||||||
is_draft: boolean;
|
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 is used for optimistic updates. It is not a part of the API response.
|
||||||
tempId?: string;
|
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 = {
|
export type TIssueAttachment = {
|
||||||
id: string;
|
id: string;
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
attributes: {
|
attributes: {
|
||||||
name: string;
|
name: string;
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
asset: string;
|
asset: string;
|
||||||
created_by: string;
|
issue_id: string;
|
||||||
|
|
||||||
|
//need
|
||||||
|
updated_at: string;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
project: string;
|
|
||||||
workspace: string;
|
|
||||||
issue: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueAttachmentMap = {
|
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 & {
|
export type TIssueLink = TIssueLinkEditableFields & {
|
||||||
created_at: Date;
|
created_by_id: string;
|
||||||
created_by: string;
|
|
||||||
created_by_detail: IUserLite;
|
|
||||||
id: string;
|
id: string;
|
||||||
metadata: any;
|
metadata: any;
|
||||||
|
issue_id: string;
|
||||||
|
|
||||||
|
//need
|
||||||
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueLinkMap = {
|
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 = {
|
export type TIssueReaction = {
|
||||||
actor: string;
|
actor_id: string;
|
||||||
actor_detail: IUserLite;
|
|
||||||
created_at: Date;
|
|
||||||
created_by: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
issue: string;
|
issue_id: string;
|
||||||
project: string;
|
|
||||||
reaction: string;
|
reaction: string;
|
||||||
updated_at: Date;
|
|
||||||
updated_by: string;
|
|
||||||
workspace: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueReactionMap = {
|
export type TIssueReactionMap = {
|
||||||
|
@ -196,9 +196,9 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const updateDraftIssue = async (payload: Partial<TIssue>) => {
|
const updateDraftIssue = async (payload: Partial<TIssue>) => {
|
||||||
await draftIssues
|
await draftIssues
|
||||||
.updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
.updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
if (isUpdatingSingleIssue) {
|
if (isUpdatingSingleIssue) {
|
||||||
mutate<TIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
mutate<TIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...payload } as TIssue), false);
|
||||||
} else {
|
} else {
|
||||||
if (payload.parent_id) mutate(SUB_ISSUES(payload.parent_id.toString()));
|
if (payload.parent_id) mutate(SUB_ISSUES(payload.parent_id.toString()));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { useIssueDetail } from "hooks/store";
|
import { useIssueDetail, useMember } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { ExternalLinkIcon, Tooltip } from "@plane/ui";
|
import { ExternalLinkIcon, Tooltip } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -26,6 +26,7 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
|||||||
toggleIssueLinkModal: toggleIssueLinkModalStore,
|
toggleIssueLinkModal: toggleIssueLinkModalStore,
|
||||||
link: { getLinkById },
|
link: { getLinkById },
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
|
const { getUserDetails } = useMember();
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
// state
|
// state
|
||||||
@ -38,6 +39,8 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
|||||||
const linkDetail = getLinkById(linkId);
|
const linkDetail = getLinkById(linkId);
|
||||||
if (!linkDetail) return <></>;
|
if (!linkDetail) return <></>;
|
||||||
|
|
||||||
|
const createdByDetails = getUserDetails(linkDetail.created_by_id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={linkId}>
|
<div key={linkId}>
|
||||||
<IssueLinkCreateUpdateModal
|
<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">
|
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||||
Added {calculateTimeAgo(linkDetail.created_at)}
|
Added {calculateTimeAgo(linkDetail.created_at)}
|
||||||
<br />
|
<br />
|
||||||
by{" "}
|
{createdByDetails && (
|
||||||
{linkDetail.created_by_detail.is_bot
|
<>
|
||||||
? linkDetail.created_by_detail.first_name + " Bot"
|
by {createdByDetails?.is_bot ? createdByDetails?.first_name + " Bot" : createdByDetails?.display_name}
|
||||||
: linkDetail.created_by_detail.display_name}
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,7 +96,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||||||
showToast: boolean = true
|
showToast: boolean = true
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await updateIssue(workspaceSlug, projectId, issueId, data);
|
await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
if (showToast) {
|
if (showToast) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Issue updated successfully",
|
title: "Issue updated successfully",
|
||||||
@ -106,7 +106,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||||||
}
|
}
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: ISSUE_UPDATED,
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: Object.keys(data).join(","),
|
changed_property: Object.keys(data).join(","),
|
||||||
change_details: Object.values(data).join(","),
|
change_details: Object.values(data).join(","),
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { FC, useState } from "react";
|
|
||||||
import { Bell, BellOff } from "lucide-react";
|
import { Bell, BellOff } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { FC, useState } from "react";
|
||||||
// UI
|
// UI
|
||||||
import { Button, Loader } from "@plane/ui";
|
import { Button, Loader } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "hooks/store";
|
import { useIssueDetail } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import isNil from "lodash/isNil";
|
||||||
|
|
||||||
export type TIssueSubscription = {
|
export type TIssueSubscription = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -25,17 +26,17 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
|
|||||||
// state
|
// state
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const subscription = getSubscriptionByIssueId(issueId);
|
const isSubscribed = getSubscriptionByIssueId(issueId);
|
||||||
|
|
||||||
const handleSubscription = async () => {
|
const handleSubscription = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (subscription?.subscribed) await removeSubscription(workspaceSlug, projectId, issueId);
|
if (isSubscribed) await removeSubscription(workspaceSlug, projectId, issueId);
|
||||||
else await createSubscription(workspaceSlug, projectId, issueId);
|
else await createSubscription(workspaceSlug, projectId, issueId);
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: `Issue ${subscription?.subscribed ? `unsubscribed` : `subscribed`} successfully.!`,
|
title: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`,
|
||||||
message: `Issue ${subscription?.subscribed ? `unsubscribed` : `subscribed`} successfully.!`,
|
message: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -48,42 +49,32 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!subscription)
|
if (isNil(isSubscribed))
|
||||||
return (
|
return (
|
||||||
<Loader>
|
<Loader>
|
||||||
<Loader.Item width="92px" height="27px" />
|
<Loader.Item width="106px" height="28px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{subscription ? (
|
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
prependIcon={subscription?.subscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
prependIcon={isSubscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
className="hover:!bg-custom-primary-100/20"
|
className="hover:!bg-custom-primary-100/20"
|
||||||
onClick={handleSubscription}
|
onClick={handleSubscription}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<span>
|
<span>
|
||||||
<span className="hidden sm:block">Loading...</span>
|
<span className="hidden sm:block">Loading</span>...
|
||||||
</span>
|
</span>
|
||||||
) : subscription?.subscribed ? (
|
) : isSubscribed ? (
|
||||||
<div className="hidden sm:block">Unsubscribe</div>
|
<div className="hidden sm:block">Unsubscribe</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="hidden sm:block">Subscribe</div>
|
<div className="hidden sm:block">Subscribe</div>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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;
|
if (!workspaceSlug || !payload.project_id || !data?.id) return;
|
||||||
|
|
||||||
try {
|
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({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -191,11 +191,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: ISSUE_UPDATED,
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS" },
|
payload: { ...payload, issueId: data.id, state: "SUCCESS" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -15,7 +15,6 @@ import { ISSUE_UPDATED, ISSUE_DELETED } from "constants/event-tracker";
|
|||||||
|
|
||||||
interface IIssuePeekOverview {
|
interface IIssuePeekOverview {
|
||||||
is_archived?: boolean;
|
is_archived?: boolean;
|
||||||
onIssueUpdate?: (issue: Partial<TIssue>) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TIssuePeekOperations = {
|
export type TIssuePeekOperations = {
|
||||||
@ -46,7 +45,7 @@ export type TIssuePeekOperations = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||||
const { is_archived = false, onIssueUpdate } = props;
|
const { is_archived = false } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// router
|
// router
|
||||||
@ -87,7 +86,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await updateIssue(workspaceSlug, projectId, issueId, data);
|
const response = await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
if (onIssueUpdate) await onIssueUpdate(response);
|
|
||||||
if (showToast)
|
if (showToast)
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Issue updated successfully",
|
title: "Issue updated successfully",
|
||||||
@ -96,7 +94,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: ISSUE_UPDATED,
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: Object.keys(data).join(","),
|
changed_property: Object.keys(data).join(","),
|
||||||
change_details: Object.values(data).join(","),
|
change_details: Object.values(data).join(","),
|
||||||
@ -314,7 +312,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
removeIssueFromModule,
|
removeIssueFromModule,
|
||||||
removeModulesFromIssue,
|
removeModulesFromIssue,
|
||||||
setToastAlert,
|
setToastAlert,
|
||||||
onIssueUpdate,
|
|
||||||
captureIssueEvent,
|
captureIssueEvent,
|
||||||
router.asPath,
|
router.asPath,
|
||||||
]
|
]
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
// services
|
// services
|
||||||
import { APIService } from "services/api.service";
|
import { APIService } from "services/api.service";
|
||||||
// type
|
// type
|
||||||
import type {
|
import type { TIssue, IIssueDisplayProperties, TIssueLink, TIssueSubIssues, TIssueActivity } from "@plane/types";
|
||||||
TIssue,
|
|
||||||
IIssueDisplayProperties,
|
|
||||||
ILinkDetails,
|
|
||||||
TIssueLink,
|
|
||||||
TIssueSubIssues,
|
|
||||||
TIssueActivity,
|
|
||||||
} from "@plane/types";
|
|
||||||
// helper
|
// helper
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
|
||||||
@ -211,7 +204,7 @@ export class IssueService extends APIService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssueLink>
|
data: Partial<TIssueLink>
|
||||||
): Promise<ILinkDetails> {
|
): Promise<TIssueLink> {
|
||||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`, data)
|
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`, data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -225,7 +218,7 @@ export class IssueService extends APIService {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
linkId: string,
|
linkId: string,
|
||||||
data: Partial<TIssueLink>
|
data: Partial<TIssueLink>
|
||||||
): Promise<ILinkDetails> {
|
): Promise<TIssueLink> {
|
||||||
return this.patch(
|
return this.patch(
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`,
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`,
|
||||||
data
|
data
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { APIService } from "services/api.service";
|
import { APIService } from "services/api.service";
|
||||||
// type
|
// type
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
|
||||||
export class IssueArchiveService extends APIService {
|
export class IssueArchiveService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -25,8 +26,15 @@ export class IssueArchiveService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async retrieveArchivedIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async retrieveArchivedIssue(
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`)
|
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)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
@ -17,7 +17,7 @@ export interface IArchivedIssues {
|
|||||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined;
|
||||||
// actions
|
// actions
|
||||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue>;
|
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>;
|
removeIssueFromArchived: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
quickAddIssue: undefined;
|
quickAddIssue: undefined;
|
||||||
}
|
}
|
||||||
@ -111,15 +111,13 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
|
|||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
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);
|
const issueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === issueId);
|
||||||
if (issueIndex >= 0)
|
if (issueIndex >= 0)
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues[projectId].splice(issueIndex, 1);
|
this.issues[projectId].splice(issueIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ export interface ICycleIssues {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssue>,
|
data: Partial<TIssue>,
|
||||||
cycleId?: string | undefined
|
cycleId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
removeIssue: (
|
removeIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
cycleId?: string | undefined
|
cycleId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
quickAddIssue: (
|
quickAddIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -207,9 +207,8 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
|||||||
try {
|
try {
|
||||||
if (!cycleId) throw new Error("Cycle Id is required");
|
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);
|
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
throw error;
|
throw error;
|
||||||
@ -225,7 +224,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
|||||||
try {
|
try {
|
||||||
if (!cycleId) throw new Error("Cycle Id is required");
|
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);
|
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||||
|
|
||||||
const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId);
|
const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId);
|
||||||
@ -233,8 +232,6 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues[cycleId].splice(issueIndex, 1);
|
this.issues[cycleId].splice(issueIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ export interface IDraftIssues {
|
|||||||
// actions
|
// actions
|
||||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
quickAddIssue: undefined;
|
quickAddIssue: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues {
|
|||||||
|
|
||||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||||
try {
|
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) {
|
if (data.hasOwnProperty("is_draft") && data?.is_draft === false) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -151,8 +151,6 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
throw error;
|
throw error;
|
||||||
@ -161,7 +159,7 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues {
|
|||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
|
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
update(this.issues, [projectId], (issueIds = []) => {
|
update(this.issues, [projectId], (issueIds = []) => {
|
||||||
@ -169,8 +167,6 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues {
|
|||||||
return issueIds;
|
return issueIds;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { IIssueDetail } from "./root.store";
|
|||||||
import { TIssueAttachment, TIssueAttachmentMap, TIssueAttachmentIdMap } from "@plane/types";
|
import { TIssueAttachment, TIssueAttachmentMap, TIssueAttachmentIdMap } from "@plane/types";
|
||||||
|
|
||||||
export interface IIssueAttachmentStoreActions {
|
export interface IIssueAttachmentStoreActions {
|
||||||
|
addAttachments: (issueId: string, attachments: TIssueAttachment[]) => void;
|
||||||
fetchAttachments: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueAttachment[]>;
|
fetchAttachments: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueAttachment[]>;
|
||||||
createAttachment: (
|
createAttachment: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -54,6 +55,7 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
|
|||||||
// computed
|
// computed
|
||||||
issueAttachments: computed,
|
issueAttachments: computed,
|
||||||
// actions
|
// actions
|
||||||
|
addAttachments: action.bound,
|
||||||
fetchAttachments: action,
|
fetchAttachments: action,
|
||||||
createAttachment: action,
|
createAttachment: action,
|
||||||
removeAttachment: action,
|
removeAttachment: action,
|
||||||
@ -83,17 +85,21 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// actions
|
// 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) => {
|
fetchAttachments = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.issueAttachmentService.getIssueAttachment(workspaceSlug, projectId, issueId);
|
const response = await this.issueAttachmentService.getIssueAttachment(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
if (response && response.length > 0) {
|
this.addAttachments(issueId, response);
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -2,15 +2,15 @@ import { makeObservable } from "mobx";
|
|||||||
// services
|
// services
|
||||||
import { IssueArchiveService, IssueService } from "services/issue";
|
import { IssueArchiveService, IssueService } from "services/issue";
|
||||||
// types
|
// types
|
||||||
import { IIssueDetail } from "./root.store";
|
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { computedFn } from "mobx-utils";
|
import { computedFn } from "mobx-utils";
|
||||||
|
import { IIssueDetail } from "./root.store";
|
||||||
|
|
||||||
export interface IIssueStoreActions {
|
export interface IIssueStoreActions {
|
||||||
// actions
|
// actions
|
||||||
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string, isArchived?: boolean) => Promise<TIssue>;
|
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string, isArchived?: boolean) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: 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>;
|
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<TIssue>;
|
||||||
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<any>;
|
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) => {
|
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) => {
|
||||||
try {
|
try {
|
||||||
const query = {
|
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);
|
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
|
||||||
|
|
||||||
if (!issue) throw new Error("Issue not found");
|
if (!issue) throw new Error("Issue not found");
|
||||||
@ -75,13 +76,15 @@ export class IssueStore implements IIssueStore {
|
|||||||
// state
|
// state
|
||||||
|
|
||||||
// issue reactions
|
// issue reactions
|
||||||
this.rootIssueDetailStore.reaction.fetchReactions(workspaceSlug, projectId, issueId);
|
if (issue.issue_reactions) this.rootIssueDetailStore.addReactions(issueId, issue.issue_reactions);
|
||||||
|
|
||||||
// fetch issue links
|
// fetch issue links
|
||||||
this.rootIssueDetailStore.link.fetchLinks(workspaceSlug, projectId, issueId);
|
if (issue.issue_link) this.rootIssueDetailStore.addLinks(issueId, issue.issue_link);
|
||||||
|
|
||||||
// fetch issue attachments
|
// 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
|
// fetch issue activity
|
||||||
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
@ -89,9 +92,6 @@ export class IssueStore implements IIssueStore {
|
|||||||
// fetch issue comments
|
// fetch issue comments
|
||||||
this.rootIssueDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);
|
this.rootIssueDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
// fetch issue subscription
|
|
||||||
this.rootIssueDetailStore.subscription.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
|
||||||
|
|
||||||
// fetch sub issues
|
// fetch sub issues
|
||||||
this.rootIssueDetailStore.subIssues.fetchSubIssues(workspaceSlug, projectId, issueId);
|
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>) => {
|
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||||
const issue = await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(
|
await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
workspaceSlug,
|
|
||||||
projectId,
|
|
||||||
issueId,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
return issue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
|
@ -7,16 +7,22 @@ import { IIssueDetail } from "./root.store";
|
|||||||
import { TIssueLink, TIssueLinkMap, TIssueLinkIdMap } from "@plane/types";
|
import { TIssueLink, TIssueLinkMap, TIssueLinkIdMap } from "@plane/types";
|
||||||
|
|
||||||
export interface IIssueLinkStoreActions {
|
export interface IIssueLinkStoreActions {
|
||||||
|
addLinks: (issueId: string, links: TIssueLink[]) => void;
|
||||||
fetchLinks: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueLink[]>;
|
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: (
|
updateLink: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
linkId: string,
|
linkId: string,
|
||||||
data: Partial<TIssueLink>
|
data: Partial<TIssueLink>
|
||||||
) => Promise<any>;
|
) => Promise<TIssueLink>;
|
||||||
removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise<any>;
|
removeLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueLinkStore extends IIssueLinkStoreActions {
|
export interface IIssueLinkStore extends IIssueLinkStoreActions {
|
||||||
@ -47,6 +53,7 @@ export class IssueLinkStore implements IIssueLinkStore {
|
|||||||
// computed
|
// computed
|
||||||
issueLinks: computed,
|
issueLinks: computed,
|
||||||
// actions
|
// actions
|
||||||
|
addLinks: action.bound,
|
||||||
fetchLinks: action,
|
fetchLinks: action,
|
||||||
createLink: action,
|
createLink: action,
|
||||||
updateLink: action,
|
updateLink: action,
|
||||||
@ -77,15 +84,17 @@ export class IssueLinkStore implements IIssueLinkStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// actions
|
// 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) => {
|
fetchLinks = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.issueService.fetchIssueLinks(workspaceSlug, projectId, issueId);
|
const response = await this.issueService.fetchIssueLinks(workspaceSlug, projectId, issueId);
|
||||||
|
this.addLinks(issueId, response);
|
||||||
runInAction(() => {
|
|
||||||
this.links[issueId] = response.map((link) => link.id);
|
|
||||||
response.forEach((link) => set(this.linkMap, link.id, link));
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@ -136,7 +145,7 @@ export class IssueLinkStore implements IIssueLinkStore {
|
|||||||
|
|
||||||
removeLink = async (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => {
|
removeLink = async (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => {
|
||||||
try {
|
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);
|
const linkIndex = this.links[issueId].findIndex((_comment) => _comment === linkId);
|
||||||
if (linkIndex >= 0)
|
if (linkIndex >= 0)
|
||||||
@ -147,7 +156,6 @@ export class IssueLinkStore implements IIssueLinkStore {
|
|||||||
|
|
||||||
// fetching activity
|
// fetching activity
|
||||||
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { groupReactions } from "helpers/emoji.helper";
|
|||||||
|
|
||||||
export interface IIssueReactionStoreActions {
|
export interface IIssueReactionStoreActions {
|
||||||
// actions
|
// actions
|
||||||
|
addReactions: (issueId: string, reactions: TIssueReaction[]) => void;
|
||||||
fetchReactions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueReaction[]>;
|
fetchReactions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueReaction[]>;
|
||||||
createReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<any>;
|
createReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise<any>;
|
||||||
removeReaction: (
|
removeReaction: (
|
||||||
@ -50,6 +51,7 @@ export class IssueReactionStore implements IIssueReactionStore {
|
|||||||
reactions: observable,
|
reactions: observable,
|
||||||
reactionMap: observable,
|
reactionMap: observable,
|
||||||
// actions
|
// actions
|
||||||
|
addReactions: action.bound,
|
||||||
fetchReactions: action,
|
fetchReactions: action,
|
||||||
createReaction: action,
|
createReaction: action,
|
||||||
removeReaction: action,
|
removeReaction: action,
|
||||||
@ -82,18 +84,15 @@ export class IssueReactionStore implements IIssueReactionStore {
|
|||||||
if (reactions?.[reaction])
|
if (reactions?.[reaction])
|
||||||
reactions?.[reaction].map((reactionId) => {
|
reactions?.[reaction].map((reactionId) => {
|
||||||
const currentReaction = this.getReactionById(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;
|
return _userReactions;
|
||||||
};
|
};
|
||||||
|
|
||||||
// actions
|
addReactions = (issueId: string, reactions: TIssueReaction[]) => {
|
||||||
fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
const groupedReactions = groupReactions(reactions || [], "reaction");
|
||||||
try {
|
|
||||||
const response = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId);
|
|
||||||
const groupedReactions = groupReactions(response || [], "reaction");
|
|
||||||
|
|
||||||
const issueReactionIdsMap: { [reaction: string]: string[] } = {};
|
const issueReactionIdsMap: { [reaction: string]: string[] } = {};
|
||||||
|
|
||||||
@ -104,8 +103,16 @@ export class IssueReactionStore implements IIssueReactionStore {
|
|||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.reactions, issueId, issueReactionIdsMap);
|
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;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -144,7 +151,7 @@ export class IssueReactionStore implements IIssueReactionStore {
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const userReactions = this.reactionsByUser(issueId, userId);
|
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) {
|
if (currentReaction && currentReaction.id) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
@ -15,8 +15,15 @@ import {
|
|||||||
IssueCommentReactionStore,
|
IssueCommentReactionStore,
|
||||||
IIssueCommentReactionStoreActions,
|
IIssueCommentReactionStoreActions,
|
||||||
} from "./comment_reaction.store";
|
} from "./comment_reaction.store";
|
||||||
|
import {
|
||||||
import { TIssue, TIssueComment, TIssueCommentReaction, TIssueLink, TIssueRelationTypes } from "@plane/types";
|
TIssue,
|
||||||
|
TIssueAttachment,
|
||||||
|
TIssueComment,
|
||||||
|
TIssueCommentReaction,
|
||||||
|
TIssueLink,
|
||||||
|
TIssueReaction,
|
||||||
|
TIssueRelationTypes,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
export type TPeekIssue = {
|
export type TPeekIssue = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -151,6 +158,7 @@ export class IssueDetail implements IIssueDetail {
|
|||||||
this.issue.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
|
this.issue.removeIssueFromModule(workspaceSlug, projectId, moduleId, issueId);
|
||||||
|
|
||||||
// reactions
|
// reactions
|
||||||
|
addReactions = (issueId: string, reactions: TIssueReaction[]) => this.reaction.addReactions(issueId, reactions);
|
||||||
fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
fetchReactions = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
this.reaction.fetchReactions(workspaceSlug, projectId, issueId);
|
this.reaction.fetchReactions(workspaceSlug, projectId, issueId);
|
||||||
createReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) =>
|
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);
|
) => this.reaction.removeReaction(workspaceSlug, projectId, issueId, reaction, userId);
|
||||||
|
|
||||||
// attachments
|
// attachments
|
||||||
|
addAttachments = (issueId: string, attachments: TIssueAttachment[]) =>
|
||||||
|
this.attachment.addAttachments(issueId, attachments);
|
||||||
fetchAttachments = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
fetchAttachments = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
this.attachment.fetchAttachments(workspaceSlug, projectId, issueId);
|
this.attachment.fetchAttachments(workspaceSlug, projectId, issueId);
|
||||||
createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, data: FormData) =>
|
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);
|
this.attachment.removeAttachment(workspaceSlug, projectId, issueId, attachmentId);
|
||||||
|
|
||||||
// link
|
// link
|
||||||
|
addLinks = (issueId: string, links: TIssueLink[]) => this.link.addLinks(issueId, links);
|
||||||
fetchLinks = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
fetchLinks = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
this.link.fetchLinks(workspaceSlug, projectId, issueId);
|
this.link.fetchLinks(workspaceSlug, projectId, issueId);
|
||||||
createLink = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssueLink>) =>
|
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);
|
this.subIssues.deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
|
||||||
|
|
||||||
// subscription
|
// subscription
|
||||||
|
addSubscription = (issueId: string, isSubscribed: boolean | undefined | null) =>
|
||||||
|
this.subscription.addSubscription(issueId, isSubscribed);
|
||||||
fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
this.subscription.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
this.subscription.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
||||||
createSubscription = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
createSubscription = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
|
@ -6,21 +6,22 @@ import { NotificationService } from "services/notification.service";
|
|||||||
import { IIssueDetail } from "./root.store";
|
import { IIssueDetail } from "./root.store";
|
||||||
|
|
||||||
export interface IIssueSubscriptionStoreActions {
|
export interface IIssueSubscriptionStoreActions {
|
||||||
fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
addSubscription: (issueId: string, isSubscribed: boolean | undefined | null) => void;
|
||||||
createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
fetchSubscriptions: (workspaceSlug: string, projectId: string, issueId: string) => Promise<boolean>;
|
||||||
removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
createSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
|
removeSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueSubscriptionStore extends IIssueSubscriptionStoreActions {
|
export interface IIssueSubscriptionStore extends IIssueSubscriptionStoreActions {
|
||||||
// observables
|
// 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
|
// helper methods
|
||||||
getSubscriptionByIssueId: (issueId: string) => Record<string, boolean> | undefined;
|
getSubscriptionByIssueId: (issueId: string) => boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
||||||
// observables
|
// observables
|
||||||
subscriptionMap: Record<string, Record<string, Record<string, boolean>>> = {};
|
subscriptionMap: Record<string, Record<string, boolean>> = {};
|
||||||
// root store
|
// root store
|
||||||
rootIssueDetail: IIssueDetail;
|
rootIssueDetail: IIssueDetail;
|
||||||
// services
|
// services
|
||||||
@ -31,6 +32,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
|||||||
// observables
|
// observables
|
||||||
subscriptionMap: observable,
|
subscriptionMap: observable,
|
||||||
// actions
|
// actions
|
||||||
|
addSubscription: action.bound,
|
||||||
fetchSubscriptions: action,
|
fetchSubscriptions: action,
|
||||||
createSubscription: action,
|
createSubscription: action,
|
||||||
removeSubscription: action,
|
removeSubscription: action,
|
||||||
@ -49,22 +51,26 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
|||||||
return this.subscriptionMap[issueId]?.[currentUserId] ?? undefined;
|
return this.subscriptionMap[issueId]?.[currentUserId] ?? undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
addSubscription = (issueId: string, isSubscribed: boolean | undefined | null) => {
|
||||||
try {
|
|
||||||
const currentUserId = this.rootIssueDetail.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueDetail.rootIssueStore.currentUserId;
|
||||||
if (!currentUserId) throw new Error("user id not available");
|
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(
|
const subscription = await this.notificationService.getIssueNotificationSubscriptionStatus(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
issueId
|
issueId
|
||||||
);
|
);
|
||||||
|
|
||||||
runInAction(() => {
|
this.addSubscription(issueId, subscription?.subscribed);
|
||||||
set(this.subscriptionMap, [issueId, currentUserId], subscription);
|
|
||||||
});
|
|
||||||
|
|
||||||
return subscription;
|
return subscription?.subscribed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -79,9 +85,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
|||||||
set(this.subscriptionMap, [issueId, currentUserId], { subscribed: true });
|
set(this.subscriptionMap, [issueId, currentUserId], { subscribed: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId);
|
await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
this.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
||||||
throw error;
|
throw error;
|
||||||
@ -97,13 +101,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore {
|
|||||||
set(this.subscriptionMap, [issueId, currentUserId], { subscribed: false });
|
set(this.subscriptionMap, [issueId, currentUserId], { subscribed: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await this.notificationService.unsubscribeFromIssueNotifications(
|
await this.notificationService.unsubscribeFromIssueNotifications(workspaceSlug, projectId, issueId);
|
||||||
workspaceSlug,
|
|
||||||
projectId,
|
|
||||||
issueId
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
this.fetchSubscriptions(workspaceSlug, projectId, issueId);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -39,13 +39,13 @@ export interface IModuleIssues {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssue>,
|
data: Partial<TIssue>,
|
||||||
moduleId?: string | undefined
|
moduleId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
removeIssue: (
|
removeIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
moduleId?: string | undefined
|
moduleId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
quickAddIssue: (
|
quickAddIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -212,9 +212,8 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
|||||||
try {
|
try {
|
||||||
if (!moduleId) throw new Error("Module Id is required");
|
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);
|
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
|
this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
|
||||||
throw error;
|
throw error;
|
||||||
@ -230,7 +229,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
|||||||
try {
|
try {
|
||||||
if (!moduleId) throw new Error("Module Id is required");
|
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);
|
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||||
|
|
||||||
const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId);
|
const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId);
|
||||||
@ -238,8 +237,6 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues[moduleId].splice(issueIndex, 1);
|
this.issues[moduleId].splice(issueIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ export interface IProfileIssues {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssue>,
|
data: Partial<TIssue>,
|
||||||
userId?: string | undefined
|
userId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
removeIssue: (
|
removeIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
userId?: string | undefined
|
userId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
quickAddIssue: undefined;
|
quickAddIssue: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,14 +221,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
|
|||||||
if (!userId) throw new Error("user id is required");
|
if (!userId) throw new Error("user id is required");
|
||||||
|
|
||||||
this.rootStore.issues.updateIssue(issueId, data);
|
this.rootStore.issues.updateIssue(issueId, data);
|
||||||
const response = await this.rootIssueStore.projectIssues.updateIssue(
|
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, data.id as keyof TIssue, data);
|
||||||
workspaceSlug,
|
|
||||||
projectId,
|
|
||||||
data.id as keyof TIssue,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.currentView) this.fetchIssues(workspaceSlug, undefined, "mutation", userId, this.currentView);
|
if (this.currentView) this.fetchIssues(workspaceSlug, undefined, "mutation", userId, this.currentView);
|
||||||
throw error;
|
throw error;
|
||||||
@ -243,7 +236,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
|
|||||||
) => {
|
) => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
try {
|
try {
|
||||||
const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
|
await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
const uniqueViewId = `${workspaceSlug}_${this.currentView}`;
|
const uniqueViewId = `${workspaceSlug}_${this.currentView}`;
|
||||||
|
|
||||||
@ -252,8 +245,6 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues[userId][uniqueViewId].splice(issueIndex, 1);
|
this.issues[userId][uniqueViewId].splice(issueIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,13 @@ export interface IProjectViewIssues {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssue>,
|
data: Partial<TIssue>,
|
||||||
viewId?: string | undefined
|
viewId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
removeIssue: (
|
removeIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
viewId?: string | undefined
|
viewId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
quickAddIssue: (
|
quickAddIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -181,8 +181,7 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
|
|||||||
try {
|
try {
|
||||||
if (!viewId) throw new Error("View Id is required");
|
if (!viewId) throw new Error("View Id is required");
|
||||||
|
|
||||||
const response = await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
|
await this.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
throw error;
|
throw error;
|
||||||
@ -198,15 +197,13 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
|
|||||||
try {
|
try {
|
||||||
if (!viewId) throw new Error("View Id is required");
|
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);
|
const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId);
|
||||||
if (issueIndex >= 0)
|
if (issueIndex >= 0)
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues[viewId].splice(issueIndex, 1);
|
this.issues[viewId].splice(issueIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -21,8 +21,8 @@ export interface IProjectIssues {
|
|||||||
// action
|
// action
|
||||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue>;
|
quickAddIssue: (workspaceSlug: string, projectId: string, data: TIssue) => Promise<TIssue>;
|
||||||
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
|
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
|
||||||
}
|
}
|
||||||
@ -144,8 +144,7 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
|
|||||||
try {
|
try {
|
||||||
this.rootStore.issues.updateIssue(issueId, data);
|
this.rootStore.issues.updateIssue(issueId, data);
|
||||||
|
|
||||||
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
|
await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
throw error;
|
throw error;
|
||||||
@ -154,14 +153,13 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
|
|||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
|
await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
pull(this.issues[projectId], issueId);
|
pull(this.issues[projectId], issueId);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore.issues.removeIssue(issueId);
|
this.rootStore.issues.removeIssue(issueId);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,13 @@ export interface IWorkspaceIssues {
|
|||||||
issueId: string,
|
issueId: string,
|
||||||
data: Partial<TIssue>,
|
data: Partial<TIssue>,
|
||||||
viewId?: string | undefined
|
viewId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
removeIssue: (
|
removeIssue: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
viewId?: string | undefined
|
viewId?: string | undefined
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssues {
|
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");
|
if (!viewId) throw new Error("View id is required");
|
||||||
|
|
||||||
this.rootStore.issues.updateIssue(issueId, data);
|
this.rootStore.issues.updateIssue(issueId, data);
|
||||||
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
|
await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (viewId) this.fetchIssues(workspaceSlug, viewId, "mutation");
|
if (viewId) this.fetchIssues(workspaceSlug, viewId, "mutation");
|
||||||
throw error;
|
throw error;
|
||||||
@ -184,7 +183,7 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
|
|||||||
|
|
||||||
const uniqueViewId = `${workspaceSlug}_${viewId}`;
|
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);
|
const issueIndex = this.issues[uniqueViewId].findIndex((_issueId) => _issueId === issueId);
|
||||||
if (issueIndex >= 0)
|
if (issueIndex >= 0)
|
||||||
@ -193,8 +192,6 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore.issues.removeIssue(issueId);
|
this.rootStore.issues.removeIssue(issueId);
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user