Skip to content

Commit d168fd4

Browse files
[WEB-2388] fix: workspace draft issues migration (#5749)
* fix: workspace draft issues * chore: changed the timezone key * chore: migration changes
1 parent 7317975 commit d168fd4

File tree

20 files changed

+2871
-461
lines changed

20 files changed

+2871
-461
lines changed

apiserver/plane/api/views/cycle.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def get(self, request, slug, project_id, pk=None):
207207
# Incomplete Cycles
208208
if cycle_view == "incomplete":
209209
queryset = queryset.filter(
210-
Q(end_date__gte=timezone.now().date())
210+
Q(end_date__gte=timezone.now())
211211
| Q(end_date__isnull=True),
212212
)
213213
return self.paginate(
@@ -311,7 +311,7 @@ def patch(self, request, slug, project_id, pk):
311311

312312
if (
313313
cycle.end_date is not None
314-
and cycle.end_date < timezone.now().date()
314+
and cycle.end_date < timezone.now()
315315
):
316316
if "sort_order" in request_data:
317317
# Can only change sort order
@@ -537,7 +537,7 @@ def post(self, request, slug, project_id, cycle_id):
537537
cycle = Cycle.objects.get(
538538
pk=cycle_id, project_id=project_id, workspace__slug=slug
539539
)
540-
if cycle.end_date >= timezone.now().date():
540+
if cycle.end_date >= timezone.now():
541541
return Response(
542542
{"error": "Only completed cycles can be archived"},
543543
status=status.HTTP_400_BAD_REQUEST,
@@ -1146,7 +1146,7 @@ def post(self, request, slug, project_id, cycle_id):
11461146

11471147
if (
11481148
new_cycle.end_date is not None
1149-
and new_cycle.end_date < timezone.now().date()
1149+
and new_cycle.end_date < timezone.now()
11501150
):
11511151
return Response(
11521152
{

apiserver/plane/app/serializers/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,9 @@
124124
from .dashboard import DashboardSerializer, WidgetSerializer
125125

126126
from .favorite import UserFavoriteSerializer
127+
128+
from .draft import (
129+
DraftIssueCreateSerializer,
130+
DraftIssueSerializer,
131+
DraftIssueDetailSerializer,
132+
)
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Django imports
2+
from django.utils import timezone
3+
4+
# Third Party imports
5+
from rest_framework import serializers
6+
7+
# Module imports
8+
from .base import BaseSerializer
9+
from plane.db.models import (
10+
User,
11+
Issue,
12+
Label,
13+
State,
14+
DraftIssue,
15+
DraftIssueAssignee,
16+
DraftIssueLabel,
17+
DraftIssueCycle,
18+
DraftIssueModule,
19+
)
20+
21+
22+
class DraftIssueCreateSerializer(BaseSerializer):
23+
# ids
24+
state_id = serializers.PrimaryKeyRelatedField(
25+
source="state",
26+
queryset=State.objects.all(),
27+
required=False,
28+
allow_null=True,
29+
)
30+
parent_id = serializers.PrimaryKeyRelatedField(
31+
source="parent",
32+
queryset=Issue.objects.all(),
33+
required=False,
34+
allow_null=True,
35+
)
36+
label_ids = serializers.ListField(
37+
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
38+
write_only=True,
39+
required=False,
40+
)
41+
assignee_ids = serializers.ListField(
42+
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
43+
write_only=True,
44+
required=False,
45+
)
46+
47+
class Meta:
48+
model = DraftIssue
49+
fields = "__all__"
50+
read_only_fields = [
51+
"workspace",
52+
"project",
53+
"created_by",
54+
"updated_by",
55+
"created_at",
56+
"updated_at",
57+
]
58+
59+
def to_representation(self, instance):
60+
data = super().to_representation(instance)
61+
assignee_ids = self.initial_data.get("assignee_ids")
62+
data["assignee_ids"] = assignee_ids if assignee_ids else []
63+
label_ids = self.initial_data.get("label_ids")
64+
data["label_ids"] = label_ids if label_ids else []
65+
return data
66+
67+
def validate(self, data):
68+
if (
69+
data.get("start_date", None) is not None
70+
and data.get("target_date", None) is not None
71+
and data.get("start_date", None) > data.get("target_date", None)
72+
):
73+
raise serializers.ValidationError(
74+
"Start date cannot exceed target date"
75+
)
76+
return data
77+
78+
def create(self, validated_data):
79+
assignees = validated_data.pop("assignee_ids", None)
80+
labels = validated_data.pop("label_ids", None)
81+
modules = validated_data.pop("module_ids", None)
82+
cycle_id = self.initial_data.get("cycle_id", None)
83+
modules = self.initial_data.get("module_ids", None)
84+
85+
workspace_id = self.context["workspace_id"]
86+
87+
# Create Issue
88+
issue = DraftIssue.objects.create(
89+
**validated_data,
90+
workspace_id=workspace_id,
91+
)
92+
93+
# Issue Audit Users
94+
created_by_id = issue.created_by_id
95+
updated_by_id = issue.updated_by_id
96+
97+
if assignees is not None and len(assignees):
98+
DraftIssueAssignee.objects.bulk_create(
99+
[
100+
DraftIssueAssignee(
101+
assignee=user,
102+
issue=issue,
103+
workspace_id=workspace_id,
104+
created_by_id=created_by_id,
105+
updated_by_id=updated_by_id,
106+
)
107+
for user in assignees
108+
],
109+
batch_size=10,
110+
)
111+
112+
if labels is not None and len(labels):
113+
DraftIssueLabel.objects.bulk_create(
114+
[
115+
DraftIssueLabel(
116+
label=label,
117+
issue=issue,
118+
workspace_id=workspace_id,
119+
created_by_id=created_by_id,
120+
updated_by_id=updated_by_id,
121+
)
122+
for label in labels
123+
],
124+
batch_size=10,
125+
)
126+
127+
if cycle_id is not None:
128+
DraftIssueCycle.objects.create(
129+
cycle_id=cycle_id,
130+
draft_issue=issue,
131+
workspace_id=workspace_id,
132+
created_by_id=created_by_id,
133+
updated_by_id=updated_by_id,
134+
)
135+
136+
if modules is not None and len(modules):
137+
DraftIssueModule.objects.bulk_create(
138+
[
139+
DraftIssueModule(
140+
module_id=module_id,
141+
draft_issue=issue,
142+
workspace_id=workspace_id,
143+
created_by_id=created_by_id,
144+
updated_by_id=updated_by_id,
145+
)
146+
for module_id in modules
147+
],
148+
batch_size=10,
149+
)
150+
151+
return issue
152+
153+
def update(self, instance, validated_data):
154+
assignees = validated_data.pop("assignee_ids", None)
155+
labels = validated_data.pop("label_ids", None)
156+
cycle_id = self.initial_data.get("cycle_id", None)
157+
modules = self.initial_data.get("module_ids", None)
158+
159+
# Related models
160+
workspace_id = instance.workspace_id
161+
created_by_id = instance.created_by_id
162+
updated_by_id = instance.updated_by_id
163+
164+
if assignees is not None:
165+
DraftIssueAssignee.objects.filter(issue=instance).delete()
166+
DraftIssueAssignee.objects.bulk_create(
167+
[
168+
DraftIssueAssignee(
169+
assignee=user,
170+
issue=instance,
171+
workspace_id=workspace_id,
172+
created_by_id=created_by_id,
173+
updated_by_id=updated_by_id,
174+
)
175+
for user in assignees
176+
],
177+
batch_size=10,
178+
)
179+
180+
if labels is not None:
181+
DraftIssueLabel.objects.filter(issue=instance).delete()
182+
DraftIssueLabel.objects.bulk_create(
183+
[
184+
DraftIssueLabel(
185+
label=label,
186+
issue=instance,
187+
workspace_id=workspace_id,
188+
created_by_id=created_by_id,
189+
updated_by_id=updated_by_id,
190+
)
191+
for label in labels
192+
],
193+
batch_size=10,
194+
)
195+
196+
if cycle_id is not None:
197+
DraftIssueCycle.objects.filter(draft_issue=instance).delete()
198+
DraftIssueCycle.objects.create(
199+
cycle_id=cycle_id,
200+
draft_issue=instance,
201+
workspace_id=workspace_id,
202+
created_by_id=created_by_id,
203+
updated_by_id=updated_by_id,
204+
)
205+
206+
if modules is not None:
207+
DraftIssueModule.objects.filter(draft_issue=instance).delete()
208+
DraftIssueModule.objects.bulk_create(
209+
[
210+
DraftIssueModule(
211+
module=module,
212+
draft_issue=instance,
213+
workspace_id=workspace_id,
214+
created_by_id=created_by_id,
215+
updated_by_id=updated_by_id,
216+
)
217+
for module in modules
218+
],
219+
batch_size=10,
220+
)
221+
222+
# Time updation occurs even when other related models are updated
223+
instance.updated_at = timezone.now()
224+
return super().update(instance, validated_data)
225+
226+
227+
class DraftIssueSerializer(BaseSerializer):
228+
# ids
229+
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
230+
module_ids = serializers.ListField(
231+
child=serializers.UUIDField(),
232+
required=False,
233+
)
234+
235+
# Many to many
236+
label_ids = serializers.ListField(
237+
child=serializers.UUIDField(),
238+
required=False,
239+
)
240+
assignee_ids = serializers.ListField(
241+
child=serializers.UUIDField(),
242+
required=False,
243+
)
244+
245+
class Meta:
246+
model = DraftIssue
247+
fields = [
248+
"id",
249+
"name",
250+
"state_id",
251+
"sort_order",
252+
"completed_at",
253+
"estimate_point",
254+
"priority",
255+
"start_date",
256+
"target_date",
257+
"project_id",
258+
"parent_id",
259+
"cycle_id",
260+
"module_ids",
261+
"label_ids",
262+
"assignee_ids",
263+
"created_at",
264+
"updated_at",
265+
"created_by",
266+
"updated_by",
267+
]
268+
read_only_fields = fields
269+
270+
271+
class DraftIssueDetailSerializer(DraftIssueSerializer):
272+
description_html = serializers.CharField()
273+
274+
class Meta(DraftIssueSerializer.Meta):
275+
fields = DraftIssueSerializer.Meta.fields + [
276+
"description_html",
277+
]
278+
read_only_fields = fields

apiserver/plane/app/urls/issue.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
IssueActivityEndpoint,
1212
IssueArchiveViewSet,
1313
IssueCommentViewSet,
14-
IssueDraftViewSet,
1514
IssueListEndpoint,
1615
IssueReactionViewSet,
1716
IssueRelationViewSet,
@@ -290,28 +289,6 @@
290289
name="issue-relation",
291290
),
292291
## End Issue Relation
293-
## Issue Drafts
294-
path(
295-
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/",
296-
IssueDraftViewSet.as_view(
297-
{
298-
"get": "list",
299-
"post": "create",
300-
}
301-
),
302-
name="project-issue-draft",
303-
),
304-
path(
305-
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/<uuid:pk>/",
306-
IssueDraftViewSet.as_view(
307-
{
308-
"get": "retrieve",
309-
"patch": "partial_update",
310-
"delete": "destroy",
311-
}
312-
),
313-
name="project-issue-draft",
314-
),
315292
path(
316293
"workspaces/<str:slug>/projects/<uuid:project_id>/deleted-issues/",
317294
DeletedIssuesListViewSet.as_view(),

apiserver/plane/app/urls/workspace.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
WorkspaceCyclesEndpoint,
2828
WorkspaceFavoriteEndpoint,
2929
WorkspaceFavoriteGroupEndpoint,
30+
WorkspaceDraftIssueViewSet,
3031
)
3132

3233

@@ -254,4 +255,25 @@
254255
WorkspaceFavoriteGroupEndpoint.as_view(),
255256
name="workspace-user-favorites-groups",
256257
),
258+
path(
259+
"workspaces/<str:slug>/draft-issues/",
260+
WorkspaceDraftIssueViewSet.as_view(
261+
{
262+
"get": "list",
263+
"post": "create",
264+
}
265+
),
266+
name="workspace-draft-issues",
267+
),
268+
path(
269+
"workspaces/<str:slug>/draft-issues/<uuid:pk>/",
270+
WorkspaceDraftIssueViewSet.as_view(
271+
{
272+
"get": "retrieve",
273+
"patch": "partial_update",
274+
"delete": "destroy",
275+
}
276+
),
277+
name="workspace-drafts-issues",
278+
),
257279
]

0 commit comments

Comments
 (0)