fix: create cycle function (#5775)

* fix: create cycle function

* chore: draft and cycle version changes

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
Akshita Goyal
2024-10-08 20:01:15 +05:30
committed by GitHub
parent 6e52f1b434
commit 6d78418e79
6 changed files with 233 additions and 40 deletions

View File

@@ -207,8 +207,7 @@ class CycleAPIEndpoint(BaseAPIView):
# Incomplete Cycles
if cycle_view == "incomplete":
queryset = queryset.filter(
Q(end_date__gte=timezone.now())
| Q(end_date__isnull=True),
Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True),
)
return self.paginate(
request=request,
@@ -309,10 +308,7 @@ class CycleAPIEndpoint(BaseAPIView):
request_data = request.data
if (
cycle.end_date is not None
and cycle.end_date < timezone.now()
):
if cycle.end_date is not None and cycle.end_date < timezone.now():
if "sort_order" in request_data:
# Can only change sort order
request_data = {

View File

@@ -49,7 +49,6 @@ class DraftIssueCreateSerializer(BaseSerializer):
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
@@ -83,11 +82,13 @@ class DraftIssueCreateSerializer(BaseSerializer):
modules = self.initial_data.get("module_ids", None)
workspace_id = self.context["workspace_id"]
project_id = self.context["project_id"]
# Create Issue
issue = DraftIssue.objects.create(
**validated_data,
workspace_id=workspace_id,
project_id=project_id,
)
# Issue Audit Users
@@ -99,8 +100,9 @@ class DraftIssueCreateSerializer(BaseSerializer):
[
DraftIssueAssignee(
assignee=user,
issue=issue,
draft_issue=issue,
workspace_id=workspace_id,
project_id=project_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
@@ -114,7 +116,8 @@ class DraftIssueCreateSerializer(BaseSerializer):
[
DraftIssueLabel(
label=label,
issue=issue,
draft_issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
@@ -128,6 +131,7 @@ class DraftIssueCreateSerializer(BaseSerializer):
DraftIssueCycle.objects.create(
cycle_id=cycle_id,
draft_issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
@@ -139,6 +143,7 @@ class DraftIssueCreateSerializer(BaseSerializer):
DraftIssueModule(
module_id=module_id,
draft_issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
@@ -153,22 +158,25 @@ class DraftIssueCreateSerializer(BaseSerializer):
def update(self, instance, validated_data):
assignees = validated_data.pop("assignee_ids", None)
labels = validated_data.pop("label_ids", None)
cycle_id = self.initial_data.get("cycle_id", None)
cycle_id = self.context.get("cycle_id", None)
modules = self.initial_data.get("module_ids", None)
# Related models
workspace_id = instance.workspace_id
project_id = instance.project_id
created_by_id = instance.created_by_id
updated_by_id = instance.updated_by_id
if assignees is not None:
DraftIssueAssignee.objects.filter(issue=instance).delete()
DraftIssueAssignee.objects.filter(draft_issue=instance).delete()
DraftIssueAssignee.objects.bulk_create(
[
DraftIssueAssignee(
assignee=user,
issue=instance,
draft_issue=instance,
workspace_id=workspace_id,
project_id=project_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
@@ -178,13 +186,14 @@ class DraftIssueCreateSerializer(BaseSerializer):
)
if labels is not None:
DraftIssueLabel.objects.filter(issue=instance).delete()
DraftIssueLabel.objects.filter(draft_issue=instance).delete()
DraftIssueLabel.objects.bulk_create(
[
DraftIssueLabel(
label=label,
issue=instance,
draft_issue=instance,
workspace_id=workspace_id,
project_id=project_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
@@ -193,28 +202,31 @@ class DraftIssueCreateSerializer(BaseSerializer):
batch_size=10,
)
if cycle_id is not None:
if cycle_id != "not_provided":
DraftIssueCycle.objects.filter(draft_issue=instance).delete()
DraftIssueCycle.objects.create(
cycle_id=cycle_id,
draft_issue=instance,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
if cycle_id is not None:
DraftIssueCycle.objects.create(
cycle_id=cycle_id,
draft_issue=instance,
workspace_id=workspace_id,
project_id=project_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
if modules is not None:
DraftIssueModule.objects.filter(draft_issue=instance).delete()
DraftIssueModule.objects.bulk_create(
[
DraftIssueModule(
module=module,
module_id=module_id,
draft_issue=instance,
workspace_id=workspace_id,
project_id=project_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for module in modules
for module_id in modules
],
batch_size=10,
)

View File

@@ -276,4 +276,9 @@ urlpatterns = [
),
name="workspace-drafts-issues",
),
path(
"workspaces/<str:slug>/draft-to-issue/<uuid:draft_id>/",
WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}),
name="workspace-drafts-issues",
),
]

View File

@@ -187,6 +187,7 @@ class CycleViewSet(BaseViewSet):
"completed_issues",
"assignee_ids",
"status",
"version",
"created_by",
)
@@ -216,6 +217,7 @@ class CycleViewSet(BaseViewSet):
"completed_issues",
"assignee_ids",
"status",
"version",
"created_by",
)
return Response(data, status=status.HTTP_200_OK)
@@ -255,6 +257,7 @@ class CycleViewSet(BaseViewSet):
"external_id",
"progress_snapshot",
"logo_props",
"version",
# meta fields
"is_favorite",
"total_issues",
@@ -306,10 +309,7 @@ class CycleViewSet(BaseViewSet):
request_data = request.data
if (
cycle.end_date is not None
and cycle.end_date < timezone.now()
):
if cycle.end_date is not None and cycle.end_date < timezone.now():
if "sort_order" in request_data:
# Can only change sort order for a completed cycle``
request_data = {
@@ -347,6 +347,7 @@ class CycleViewSet(BaseViewSet):
"external_id",
"progress_snapshot",
"logo_props",
"version",
# meta fields
"is_favorite",
"total_issues",
@@ -412,6 +413,7 @@ class CycleViewSet(BaseViewSet):
"progress_snapshot",
"sub_issues",
"logo_props",
"version",
# meta fields
"is_favorite",
"total_issues",
@@ -1148,6 +1150,7 @@ class CycleProgressEndpoint(BaseAPIView):
status=status.HTTP_200_OK,
)
class CycleAnalyticsEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])

View File

@@ -1,4 +1,10 @@
# Python imports
import json
# Django imports
from django.utils import timezone
from django.core import serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import (
@@ -26,9 +32,15 @@ from plane.app.serializers import (
from plane.db.models import (
Issue,
DraftIssue,
CycleIssue,
ModuleIssue,
DraftIssueModule,
DraftIssueCycle,
Workspace,
)
from .. import BaseViewSet
from plane.bgtasks.issue_activities_task import issue_activity
from plane.utils.issue_filters import issue_filters
class WorkspaceDraftIssueViewSet(BaseViewSet):
@@ -40,6 +52,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
)
def list(self, request, slug):
filters = issue_filters(request.query_params, "GET")
issues = (
DraftIssue.objects.filter(workspace__slug=slug)
.filter(created_by=request.user)
@@ -81,8 +94,16 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
.order_by("-created_at")
)
serializer = DraftIssueSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
issues = issues.filter(**filters)
# List Paginate
return self.paginate(
request=request,
queryset=(issues),
on_results=lambda issues: DraftIssueSerializer(
issues,
many=True,
).data,
)
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
@@ -94,6 +115,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
data=request.data,
context={
"workspace_id": workspace.id,
"project_id": request.data.get("project_id", None),
},
)
if serializer.is_valid():
@@ -156,8 +178,14 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
status=status.HTTP_404_NOT_FOUND,
)
serializer = IssueCreateSerializer(
issue, data=request.data, partial=True
serializer = DraftIssueCreateSerializer(
issue,
data=request.data,
partial=True,
context={
"project_id": request.data.get("project_id", None),
"cycle_id": request.data.get("cycle_id", "not_provided"),
},
)
if serializer.is_valid():
@@ -234,3 +262,151 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
draft_issue = DraftIssue.objects.get(workspace__slug=slug, pk=pk)
draft_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER],
level="WORKSPACE",
)
def create_draft_to_issue(self, request, slug, draft_id):
draft_issue = (
DraftIssue.objects.filter(workspace__slug=slug, pk=draft_id)
.annotate(cycle_id=F("draft_issue_cycle__cycle_id"))
.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
assignee_ids=Coalesce(
ArrayAgg(
"assignees__id",
distinct=True,
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
module_ids=Coalesce(
ArrayAgg(
"draft_issue_module__module_id",
distinct=True,
filter=~Q(draft_issue_module__module_id__isnull=True)
& Q(
draft_issue_module__module__archived_at__isnull=True
),
),
Value([], output_field=ArrayField(UUIDField())),
),
)
.select_related("project", "workspace")
.first()
)
if not draft_issue.project_id:
return Response(
{"error": "Project is required to create an issue."},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IssueCreateSerializer(
data=request.data,
context={
"project_id": draft_issue.project_id,
"workspace_id": draft_issue.project.workspace_id,
"default_assignee_id": draft_issue.project.default_assignee_id,
},
)
if serializer.is_valid():
serializer.save()
issue_activity.delay(
type="issue.activity.created",
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(draft_issue.project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
if draft_issue.cycle_id:
created_records = CycleIssue.objects.create(
cycle_id=draft_issue.cycle_id,
issue_id=serializer.data.get("id", None),
project_id=draft_issue.project_id,
workspace_id=draft_issue.workspace_id,
created_by_id=draft_issue.created_by_id,
updated_by_id=draft_issue.updated_by_id,
)
# Capture Issue Activity
issue_activity.delay(
type="cycle.activity.created",
requested_data=None,
actor_id=str(self.request.user.id),
issue_id=None,
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
{
"updated_cycle_issues": None,
"created_cycle_issues": serializers.serialize(
"json", created_records
),
}
),
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
if draft_issue.module_ids:
# bulk create the module
ModuleIssue.objects.bulk_create(
[
ModuleIssue(
module_id=module,
issue_id=serializer.data.get("id", None),
workspace_id=draft_issue.workspace_id,
project_id=draft_issue.project_id,
created_by_id=draft_issue.created_by_id,
updated_by_id=draft_issue.updated_by_id,
)
for module in draft_issue.module_ids
],
batch_size=10,
)
# Bulk Update the activity
_ = [
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"module_id": str(module)}),
actor_id=str(request.user.id),
issue_id=serializer.data.get("id", None),
project_id=draft_issue.project_id,
current_instance=None,
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
for module in draft_issue.module_ids
]
# delete the draft issue
draft_issue.delete()
# delete the draft issue module
DraftIssueModule.objects.filter(draft_issue=draft_issue).delete()
# delete the draft issue cycle
DraftIssueCycle.objects.filter(draft_issue=draft_issue).delete()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@@ -144,7 +144,6 @@ export class CycleStore implements ICycleStore {
fetchActiveCycleProgress: action,
fetchActiveCycleAnalytics: action,
fetchCycleDetails: action,
createCycle: action,
updateCycleDetails: action,
deleteCycle: action,
addCycleToFavorites: action,
@@ -617,13 +616,15 @@ export class CycleStore implements ICycleStore {
* @param data
* @returns
*/
createCycle = async (workspaceSlug: string, projectId: string, data: Partial<ICycle>) =>
await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => {
runInAction(() => {
set(this.cycleMap, [response.id], response);
});
return response;
});
createCycle = action(
async (workspaceSlug: string, projectId: string, data: Partial<ICycle>) =>
await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => {
runInAction(() => {
set(this.cycleMap, [response.id], response);
});
return response;
})
);
/**
* @description updates cycle details