[WEB - 883] fix: external apis (#3975)

* fix: external apis

* dev: remove descrypt in email password

* dev: add email as field in user serializer

* dev: fix linting errors

* dev: push commit to enable build triggers

* fix: formatting errors

* dev: remove instance value extraction
This commit is contained in:
Nikhil 2024-04-08 18:46:05 +05:30 committed by GitHub
parent 8d009187ab
commit 9b0949148f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 327 additions and 93 deletions

View File

@ -66,11 +66,11 @@ class BaseSerializer(serializers.ModelSerializer):
if expand in self.fields: if expand in self.fields:
# Import all the expandable serializers # Import all the expandable serializers
from . import ( from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer, IssueSerializer,
ProjectLiteSerializer,
StateLiteSerializer,
UserLiteSerializer,
WorkspaceLiteSerializer,
) )
# Expansion mapper # Expansion mapper

View File

@ -7,6 +7,7 @@ from plane.db.models import (
ProjectIdentifier, ProjectIdentifier,
WorkspaceMember, WorkspaceMember,
) )
from .base import BaseSerializer from .base import BaseSerializer

View File

@ -11,6 +11,7 @@ class UserLiteSerializer(BaseSerializer):
"id", "id",
"first_name", "first_name",
"last_name", "last_name",
"email",
"avatar", "avatar",
"display_name", "display_name",
"email", "email",

View File

@ -2,29 +2,31 @@
import json import json
# Django imports # Django imports
from django.db.models import Q, Count, Sum, F, OuterRef, Func
from django.utils import timezone
from django.core import serializers from django.core import serializers
from django.db.models import Count, F, Func, OuterRef, Q, Sum
from django.utils import timezone
# Third party imports # Third party imports
from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseAPIView, WebhookMixin from plane.api.serializers import (
from plane.db.models import ( CycleIssueSerializer,
Cycle, CycleSerializer,
Issue,
CycleIssue,
IssueLink,
IssueAttachment,
) )
from plane.app.permissions import ProjectEntityPermission from plane.app.permissions import ProjectEntityPermission
from plane.api.serializers import (
CycleSerializer,
CycleIssueSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Cycle,
CycleIssue,
Issue,
IssueAttachment,
IssueLink,
)
from plane.utils.analytics_plot import burndown_plot
from .base import BaseAPIView, WebhookMixin
class CycleAPIEndpoint(WebhookMixin, BaseAPIView): class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
@ -551,7 +553,21 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
.distinct() .distinct()
) )
def get(self, request, slug, project_id, cycle_id): def get(self, request, slug, project_id, cycle_id, issue_id=None):
# Get
if issue_id:
cycle_issue = CycleIssue.objects.get(
workspace__slug=slug,
project_id=project_id,
cycle_id=cycle_id,
issue_id=issue_id,
)
serializer = CycleIssueSerializer(
cycle_issue, fields=self.fields, expand=self.expand
)
return Response(serializer.data, status=status.HTTP_200_OK)
# List
order_by = request.GET.get("order_by", "created_at") order_by = request.GET.get("order_by", "created_at")
issues = ( issues = (
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id) Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
@ -748,6 +764,209 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
workspace__slug=slug, project_id=project_id, pk=new_cycle_id workspace__slug=slug, project_id=project_id, pk=new_cycle_id
) )
old_cycle = (
Cycle.objects.filter(
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
.annotate(
total_issues=Count(
"issue_cycle",
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
completed_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
cancelled_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
started_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
unstarted_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
backlog_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
)
# Pass the new_cycle queryset to burndown_plot
completion_chart = burndown_plot(
queryset=old_cycle.first(),
slug=slug,
project_id=project_id,
cycle_id=cycle_id,
)
# Get the assignee distribution
assignee_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=cycle_id,
workspace__slug=slug,
project_id=project_id,
)
.annotate(display_name=F("assignees__display_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(avatar=F("assignees__avatar"))
.values("display_name", "assignee_id", "avatar")
.annotate(
total_issues=Count(
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("display_name")
)
# assignee distribution serialized
assignee_distribution_data = [
{
"display_name": item["display_name"],
"assignee_id": (
str(item["assignee_id"]) if item["assignee_id"] else None
),
"avatar": item["avatar"],
"total_issues": item["total_issues"],
"completed_issues": item["completed_issues"],
"pending_issues": item["pending_issues"],
}
for item in assignee_distribution
]
# Get the label distribution
label_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=cycle_id,
workspace__slug=slug,
project_id=project_id,
)
.annotate(label_name=F("labels__name"))
.annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
)
)
.annotate(
completed_issues=Count(
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("label_name")
)
# Label distribution serilization
label_distribution_data = [
{
"label_name": item["label_name"],
"color": item["color"],
"label_id": (
str(item["label_id"]) if item["label_id"] else None
),
"total_issues": item["total_issues"],
"completed_issues": item["completed_issues"],
"pending_issues": item["pending_issues"],
}
for item in label_distribution
]
current_cycle = Cycle.objects.filter(
workspace__slug=slug, project_id=project_id, pk=cycle_id
).first()
if current_cycle:
current_cycle.progress_snapshot = {
"total_issues": old_cycle.first().total_issues,
"completed_issues": old_cycle.first().completed_issues,
"cancelled_issues": old_cycle.first().cancelled_issues,
"started_issues": old_cycle.first().started_issues,
"unstarted_issues": old_cycle.first().unstarted_issues,
"backlog_issues": old_cycle.first().backlog_issues,
"distribution": {
"labels": label_distribution_data,
"assignees": assignee_distribution_data,
"completion_chart": completion_chart,
},
}
# Save the snapshot of the current cycle
current_cycle.save(update_fields=["progress_snapshot"])
if ( if (
new_cycle.end_date is not None new_cycle.end_date is not None
and new_cycle.end_date < timezone.now().date() and new_cycle.end_date < timezone.now().date()

View File

@ -2,27 +2,28 @@
import json import json
# Django improts # Django improts
from django.utils import timezone
from django.db.models import Q
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Q
from django.utils import timezone
# Third party imports # Third party imports
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseAPIView
from plane.app.permissions import ProjectLitePermission
from plane.api.serializers import InboxIssueSerializer, IssueSerializer from plane.api.serializers import InboxIssueSerializer, IssueSerializer
from plane.app.permissions import ProjectLitePermission
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import ( from plane.db.models import (
Inbox,
InboxIssue, InboxIssue,
Issue, Issue,
State,
ProjectMember,
Project, Project,
Inbox, ProjectMember,
State,
) )
from plane.bgtasks.issue_activites_task import issue_activity
from .base import BaseAPIView
class InboxIssueAPIEndpoint(BaseAPIView): class InboxIssueAPIEndpoint(BaseAPIView):

View File

@ -2,32 +2,33 @@
import json import json
# Django imports # Django imports
from django.db.models import Count, Prefetch, Q, F, Func, OuterRef
from django.utils import timezone
from django.core import serializers from django.core import serializers
from django.db.models import Count, F, Func, OuterRef, Prefetch, Q
from django.utils import timezone
# Third party imports # Third party imports
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseAPIView, WebhookMixin from plane.api.serializers import (
IssueSerializer,
ModuleIssueSerializer,
ModuleSerializer,
)
from plane.app.permissions import ProjectEntityPermission from plane.app.permissions import ProjectEntityPermission
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import ( from plane.db.models import (
Project,
Module,
ModuleLink,
Issue, Issue,
ModuleIssue,
IssueAttachment, IssueAttachment,
IssueLink, IssueLink,
Module,
ModuleIssue,
ModuleLink,
Project,
) )
from plane.api.serializers import (
ModuleSerializer, from .base import BaseAPIView, WebhookMixin
ModuleIssueSerializer,
IssueSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):

View File

@ -1,16 +1,17 @@
# Django imports # Django imports
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import Q from django.db.models import Q
from rest_framework import status
# Third party imports # Third party imports
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status
from plane.api.serializers import StateSerializer
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import Issue, State
# Module imports # Module imports
from .base import BaseAPIView from .base import BaseAPIView
from plane.api.serializers import StateSerializer
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import State, Issue
class StateAPIEndpoint(BaseAPIView): class StateAPIEndpoint(BaseAPIView):
@ -86,7 +87,11 @@ class StateAPIEndpoint(BaseAPIView):
def get(self, request, slug, project_id, state_id=None): def get(self, request, slug, project_id, state_id=None):
if state_id: if state_id:
serializer = StateSerializer(self.get_queryset().get(pk=state_id)) serializer = StateSerializer(
self.get_queryset().get(pk=state_id),
fields=self.fields,
expand=self.expand,
)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return self.paginate( return self.paginate(
request=request, request=request,

View File

@ -21,9 +21,9 @@ from django.db.models import (
) )
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils import timezone from django.utils import timezone
from rest_framework import status
# Third party imports # Third party imports
from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from plane.app.permissions import ( from plane.app.permissions import (

View File

@ -1,57 +1,59 @@
# Python imports # Python imports
import json import json
# Django imports
from django.utils import timezone
from django.db.models import (
Prefetch,
OuterRef,
Func,
F,
Q,
Case,
Value,
CharField,
When,
Exists,
Max,
)
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
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
from django.db.models import UUIDField from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import (
Case,
CharField,
Exists,
F,
Func,
Max,
OuterRef,
Prefetch,
Q,
UUIDField,
Value,
When,
)
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
# Django imports
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from rest_framework import status
# Third Party imports # Third Party imports
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.serializers import (
IssuePropertySerializer,
IssueSerializer,
IssueCreateSerializer,
IssueDetailSerializer,
)
from plane.app.permissions import ( from plane.app.permissions import (
ProjectEntityPermission, ProjectEntityPermission,
ProjectLitePermission, ProjectLitePermission,
) )
from plane.db.models import ( from plane.app.serializers import (
Project, IssueCreateSerializer,
Issue, IssueDetailSerializer,
IssueProperty, IssuePropertySerializer,
IssueLink, IssueSerializer,
IssueAttachment,
IssueSubscriber,
IssueReaction,
) )
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Issue,
IssueAttachment,
IssueLink,
IssueProperty,
IssueReaction,
IssueSubscriber,
Project,
)
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
class IssueListEndpoint(BaseAPIView): class IssueListEndpoint(BaseAPIView):

View File

@ -120,7 +120,6 @@ class IssueDraftViewSet(BaseViewSet):
@method_decorator(gzip_page) @method_decorator(gzip_page)
def list(self, request, slug, project_id): def list(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = [ state_order = [

View File

@ -1,18 +1,18 @@
# Python imports # Python imports
from itertools import groupby
from datetime import timedelta from datetime import timedelta
from itertools import groupby
# Django import # Django import
from django.db import models from django.db import models
from django.utils import timezone from django.db.models import Case, CharField, Count, F, Sum, Value, When
from django.db.models.functions import TruncDate
from django.db.models import Count, F, Sum, Value, Case, When, CharField
from django.db.models.functions import ( from django.db.models.functions import (
Coalesce, Coalesce,
Concat,
ExtractMonth, ExtractMonth,
ExtractYear, ExtractYear,
Concat, TruncDate,
) )
from django.utils import timezone
# Module imports # Module imports
from plane.db.models import Issue from plane.db.models import Issue
@ -115,11 +115,16 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None):
total_issues = queryset.total_issues total_issues = queryset.total_issues
if cycle_id: if cycle_id:
# Get all dates between the two dates if queryset.end_date and queryset.start_date:
date_range = [ # Get all dates between the two dates
queryset.start_date + timedelta(days=x) date_range = [
for x in range((queryset.end_date - queryset.start_date).days + 1) queryset.start_date + timedelta(days=x)
] for x in range(
(queryset.end_date - queryset.start_date).days + 1
)
]
else:
date_range = []
chart_data = {str(date): 0 for date in date_range} chart_data = {str(date): 0 for date in date_range}