cherry-pick: bugfixes in 1.8.2 from preview

This commit is contained in:
sriram veeraghanta
2025-03-07 16:50:14 +05:30
parent 573afc40e7
commit 3769331b09
4 changed files with 71 additions and 127 deletions

View File

@@ -116,25 +116,23 @@ class IssueCreateSerializer(BaseSerializer):
data["label_ids"] = label_ids if label_ids else []
return data
def validate(self, data):
def validate(self, attrs):
if (
data.get("start_date", None) is not None
and data.get("target_date", None) is not None
and data.get("start_date", None) > data.get("target_date", None)
attrs.get("start_date", None) is not None
and attrs.get("target_date", None) is not None
and attrs.get("start_date", None) > attrs.get("target_date", None)
):
raise serializers.ValidationError("Start date cannot exceed target date")
return data
def get_valid_assignees(self, assignees, project_id):
if not assignees:
return []
if attrs.get("assignee_ids", []):
attrs["assignee_ids"] = ProjectMember.objects.filter(
project_id=self.context["project_id"],
role__gte=15,
is_active=True,
member_id__in=attrs["assignee_ids"],
).values_list("member_id", flat=True)
return ProjectMember.objects.filter(
project_id=project_id,
role__gte=15,
is_active=True,
member_id__in=assignees
).values_list('member_id', flat=True)
return attrs
def create(self, validated_data):
assignees = validated_data.pop("assignee_ids", None)
@@ -164,20 +162,19 @@ class IssueCreateSerializer(BaseSerializer):
created_by_id = issue.created_by_id
updated_by_id = issue.updated_by_id
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
if valid_assignee_ids is not None and len(valid_assignee_ids):
if assignees is not None and len(assignees):
try:
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee_id=user_id,
assignee_id=assignee_id,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user_id in valid_assignee_ids
for assignee_id in assignees
],
batch_size=10,
)
@@ -185,12 +182,15 @@ class IssueCreateSerializer(BaseSerializer):
pass
else:
# Then assign it to default assignee, if it is a valid assignee
if default_assignee_id is not None and ProjectMember.objects.filter(
member_id=default_assignee_id,
project_id=project_id,
role__gte=15,
is_active=True
).exists():
if (
default_assignee_id is not None
and ProjectMember.objects.filter(
member_id=default_assignee_id,
project_id=project_id,
role__gte=15,
is_active=True,
).exists()
):
try:
IssueAssignee.objects.create(
assignee_id=default_assignee_id,
@@ -234,21 +234,20 @@ class IssueCreateSerializer(BaseSerializer):
created_by_id = instance.created_by_id
updated_by_id = instance.updated_by_id
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
if valid_assignee_ids is not None:
if assignees is not None:
IssueAssignee.objects.filter(issue=instance).delete()
try:
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee_id=user_id,
assignee_id=assignee_id,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user_id in valid_assignee_ids
for assignee_id in assignees
],
batch_size=10,
ignore_conflicts=True,

View File

@@ -431,7 +431,7 @@ class IntakeIssueViewSet(BaseViewSet):
}
issue_serializer = IssueCreateSerializer(
issue, data=issue_data, partial=True
issue, data=issue_data, partial=True, context={"project_id": project_id}
)
if issue_serializer.is_valid():

View File

@@ -55,10 +55,7 @@ from plane.utils.grouper import (
)
from plane.utils.issue_filters import issue_filters
from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import (
GroupedOffsetPaginator,
SubGroupedOffsetPaginator,
)
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
from .. import BaseAPIView, BaseViewSet
from plane.utils.timezone_converter import user_timezone_converter
from plane.bgtasks.recent_visited_task import recent_visited_task
@@ -75,13 +72,10 @@ class IssueListEndpoint(BaseAPIView):
if not issue_ids:
return Response(
{"error": "Issues are required"},
status=status.HTTP_400_BAD_REQUEST,
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
)
issue_ids = [
issue_id for issue_id in issue_ids.split(",") if issue_id != ""
]
issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""]
queryset = (
Issue.issue_objects.filter(
@@ -113,9 +107,7 @@ class IssueListEndpoint(BaseAPIView):
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -137,9 +129,7 @@ class IssueListEndpoint(BaseAPIView):
# issue queryset
issue_queryset = issue_queryset_grouper(
queryset=issue_queryset,
group_by=group_by,
sub_group_by=sub_group_by,
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
)
recent_visited_task.delay(
@@ -208,9 +198,7 @@ class IssueViewSet(BaseViewSet):
def get_queryset(self):
return (
Issue.issue_objects.filter(
project_id=self.kwargs.get("project_id")
)
Issue.issue_objects.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
@@ -237,9 +225,7 @@ class IssueViewSet(BaseViewSet):
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -251,9 +237,7 @@ class IssueViewSet(BaseViewSet):
def list(self, request, slug, project_id):
extra_filters = {}
if request.GET.get("updated_at__gt", None) is not None:
extra_filters = {
"updated_at__gt": request.GET.get("updated_at__gt")
}
extra_filters = {"updated_at__gt": request.GET.get("updated_at__gt")}
project = Project.objects.get(pk=project_id, workspace__slug=slug)
filters = issue_filters(request.query_params, "GET")
@@ -273,9 +257,7 @@ class IssueViewSet(BaseViewSet):
# issue queryset
issue_queryset = issue_queryset_grouper(
queryset=issue_queryset,
group_by=group_by,
sub_group_by=sub_group_by,
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
)
recent_visited_task.delay(
@@ -312,9 +294,7 @@ class IssueViewSet(BaseViewSet):
order_by=order_by_param,
queryset=issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by,
issues=issues,
sub_group_by=sub_group_by,
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
paginator_cls=SubGroupedOffsetPaginator,
group_by_fields=issue_group_values(
@@ -347,9 +327,7 @@ class IssueViewSet(BaseViewSet):
order_by=order_by_param,
queryset=issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by,
issues=issues,
sub_group_by=sub_group_by,
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
paginator_cls=GroupedOffsetPaginator,
group_by_fields=issue_group_values(
@@ -395,7 +373,7 @@ class IssueViewSet(BaseViewSet):
project_id=project_id, slug=slug
)
if workflow_state_manager._validate_issue_creation(
state_id=request.data.get("state_id"),
state_id=request.data.get("state_id")
):
return Response(
{"error": "You cannot create a issue in this state"},
@@ -408,9 +386,7 @@ class IssueViewSet(BaseViewSet):
# Track the issue
issue_activity.delay(
type="issue.activity.created",
requested_data=json.dumps(
self.request.data, cls=DjangoJSONEncoder
),
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
actor_id=str(request.user.id),
issue_id=str(serializer.data.get("id", None)),
project_id=str(project_id),
@@ -421,9 +397,7 @@ class IssueViewSet(BaseViewSet):
)
issue = (
issue_queryset_grouper(
queryset=self.get_queryset().filter(
pk=serializer.data["id"]
),
queryset=self.get_queryset().filter(pk=serializer.data["id"]),
group_by=None,
sub_group_by=None,
)
@@ -483,9 +457,7 @@ class IssueViewSet(BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
creator=True,
model=Issue,
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue
)
def retrieve(self, request, slug, project_id, pk=None):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
@@ -519,9 +491,7 @@ class IssueViewSet(BaseViewSet):
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -567,9 +537,7 @@ class IssueViewSet(BaseViewSet):
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related(
"issue", "actor"
),
queryset=IssueReaction.objects.select_related("issue", "actor"),
)
)
.prefetch_related(
@@ -705,7 +673,7 @@ class IssueViewSet(BaseViewSet):
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
serializer = IssueCreateSerializer(
issue, data=request.data, partial=True
issue, data=request.data, partial=True, context={"project_id": project_id}
)
if serializer.is_valid():
serializer.save()
@@ -800,9 +768,7 @@ class IssueUserDisplayPropertyEndpoint(BaseAPIView):
user=request.user, project_id=project_id
)
issue_property.filters = request.data.get(
"filters", issue_property.filters
)
issue_property.filters = request.data.get("filters", issue_property.filters)
issue_property.display_filters = request.data.get(
"display_filters", issue_property.display_filters
)
@@ -829,8 +795,7 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
if not len(issue_ids):
return Response(
{"error": "Issue IDs are required"},
status=status.HTTP_400_BAD_REQUEST,
{"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST
)
issues = Issue.issue_objects.filter(
@@ -854,9 +819,7 @@ class DeletedIssuesListViewSet(BaseAPIView):
if request.GET.get("updated_at__gt", None) is not None:
filters = {"updated_at__gt": request.GET.get("updated_at__gt")}
deleted_issues = (
Issue.all_objects.filter(
workspace__slug=slug, project_id=project_id
)
Issue.all_objects.filter(workspace__slug=slug, project_id=project_id)
.filter(Q(archived_at__isnull=False) | Q(deleted_at__isnull=False))
.filter(**filters)
.values_list("id", flat=True)
@@ -875,9 +838,7 @@ class IssuePaginatedViewSet(BaseViewSet):
)
return (
issue_queryset.select_related(
"workspace", "project", "state", "parent"
)
issue_queryset.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
@@ -902,9 +863,7 @@ class IssuePaginatedViewSet(BaseViewSet):
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -1043,9 +1002,7 @@ class IssueDetailEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET")
issue = (
Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id
)
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
@@ -1108,9 +1065,7 @@ class IssueDetailEndpoint(BaseAPIView):
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -1133,9 +1088,7 @@ class IssueDetailEndpoint(BaseAPIView):
class IssueBulkUpdateDateEndpoint(BaseAPIView):
def validate_dates(
self, current_start, current_target, new_start, new_target
):
def validate_dates(self, current_start, current_target, new_start, new_target):
"""
Validate that start date is before target date.
"""
@@ -1179,12 +1132,8 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
if start_date:
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps(
{"start_date": update.get("start_date")}
),
current_instance=json.dumps(
{"start_date": str(issue.start_date)}
),
requested_data=json.dumps({"start_date": update.get("start_date")}),
current_instance=json.dumps({"start_date": str(issue.start_date)}),
issue_id=str(issue_id),
actor_id=str(request.user.id),
project_id=str(project_id),
@@ -1211,18 +1160,14 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
issues_to_update.append(issue)
# Bulk update issues
Issue.objects.bulk_update(
issues_to_update, ["start_date", "target_date"]
)
Issue.objects.bulk_update(issues_to_update, ["start_date", "target_date"])
return Response(
{"message": "Issues updated successfully"},
status=status.HTTP_200_OK,
{"message": "Issues updated successfully"}, status=status.HTTP_200_OK
)
class IssueMetaEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="PROJECT")
def get(self, request, slug, project_id, issue_id):
issue = Issue.issue_objects.only("sequence_id", "project__identifier").get(
@@ -1238,14 +1183,12 @@ class IssueMetaEndpoint(BaseAPIView):
class IssueDetailIdentifierEndpoint(BaseAPIView):
def strict_str_to_int(self, s):
if not s.isdigit() and not (s.startswith('-') and s[1:].isdigit()):
if not s.isdigit() and not (s.startswith("-") and s[1:].isdigit()):
raise ValueError("Invalid integer string")
return int(s)
def get(self, request, slug, project_identifier, issue_identifier):
# Check if the issue identifier is a valid integer
try:
issue_identifier = self.strict_str_to_int(issue_identifier)
@@ -1257,10 +1200,8 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
# Fetch the project
project = Project.objects.get(
identifier__iexact=project_identifier,
workspace__slug=slug,
identifier__iexact=project_identifier, workspace__slug=slug
)
# Fetch the issue
issue = (
@@ -1349,8 +1290,8 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
.annotate(
is_subscribed=Exists(
IssueSubscriber.objects.filter(
workspace__slug=slug,
project_id=project.id,
workspace__slug=slug,
project_id=project.id,
issue__sequence_id=issue_identifier,
subscriber=request.user,
)
@@ -1364,7 +1305,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
{"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND,
)
# Check if the user is a member of the project
if not ProjectMember.objects.filter(
workspace__slug=slug,
@@ -1374,8 +1315,8 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
).exists():
return Response(
{
"error": "You are not allowed to view this issue",
"type": project.network
"error": "You are not allowed to view this issue",
"type": project.network,
},
status=status.HTTP_403_FORBIDDEN,
)

View File

@@ -15,7 +15,6 @@ from .base import BaseViewSet
from plane.db.models import (
IntakeIssue,
Issue,
State,
IssueLink,
FileAsset,
DeployBoard,
@@ -216,7 +215,12 @@ class IntakeIssuePublicViewSet(BaseViewSet):
"description": issue_data.get("description", issue.description),
}
issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True)
issue_serializer = IssueCreateSerializer(
issue,
data=issue_data,
partial=True,
context={"project_id": project_deploy_board.project_id},
)
if issue_serializer.is_valid():
current_instance = issue