From 3056727190711b0a0acc6153edae551724d6e7f9 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 24 Mar 2023 00:13:26 +0530 Subject: [PATCH] dev: endpoints for my, other, recent and favorite pages, add sort order and color for pages (#499) * dev: endpoints for my, other, recent and favorite pages, add sort order and color for pages * dev: fix state attribute error while saving page blocks --- apiserver/plane/api/urls.py | 22 +++- apiserver/plane/api/views/__init__.py | 3 + apiserver/plane/api/views/page.py | 140 +++++++++++++++++++++++++- apiserver/plane/db/models/page.py | 31 ++++++ 4 files changed, 190 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index f82d3160d..8848c2822 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -106,7 +106,10 @@ from plane.api.views import ( PageBlockViewSet, PageFavoriteViewSet, CreateIssueFromPageBlockEndpoint, + RecentPagesEndpoint, + FavoritePagesEndpoint, MyPagesEndpoint, + CreatedbyOtherPagesEndpoint, ## End Pages # Api Tokens ApiTokenEndpoint, @@ -980,9 +983,24 @@ urlpatterns = [ name="page-block-issues", ), path( - "workspaces//projects//user/pages/", + "workspaces//projects//pages/recent-pages/", + RecentPagesEndpoint.as_view(), + name="recent-pages", + ), + path( + "workspaces//projects//pages/favorite-pages/", + FavoritePagesEndpoint.as_view(), + name="recent-pages", + ), + path( + "workspaces//projects//pages/my-pages/", MyPagesEndpoint.as_view(), - name="my-pages", + name="user-pages", + ), + path( + "workspaces//projects//pages/created-by-other-pages/", + CreatedbyOtherPagesEndpoint.as_view(), + name="created-by-other-pages", ), ## End Pages # API Tokens diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 37293e087..25c76f380 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -116,5 +116,8 @@ from .page import ( PageBlockViewSet, PageFavoriteViewSet, CreateIssueFromPageBlockEndpoint, + RecentPagesEndpoint, + FavoritePagesEndpoint, MyPagesEndpoint, + CreatedbyOtherPagesEndpoint, ) diff --git a/apiserver/plane/api/views/page.py b/apiserver/plane/api/views/page.py index 473ac6f80..9d7431451 100644 --- a/apiserver/plane/api/views/page.py +++ b/apiserver/plane/api/views/page.py @@ -1,6 +1,10 @@ +# Python imports +from datetime import timedelta + # Django imports from django.db import IntegrityError from django.db.models import Exists, OuterRef, Q +from django.utils import timezone # Third party imports from rest_framework import status @@ -31,6 +35,9 @@ class PageViewSet(BaseViewSet): permission_classes = [ ProjectEntityPermission, ] + search_fields = [ + "name", + ] def get_queryset(self): subquery = PageFavorite.objects.filter( @@ -50,6 +57,7 @@ class PageViewSet(BaseViewSet): .select_related("workspace") .select_related("owned_by") .annotate(is_favorite=Exists(subquery)) + .order_by(self.request.GET.get("order_by", "-created_at")) .prefetch_related("labels") .distinct() ) @@ -175,6 +183,10 @@ class PageFavoriteViewSet(BaseViewSet): class CreateIssueFromPageBlockEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + def post(self, request, slug, project_id, page_id, page_block_id): try: page_block = PageBlock.objects.get( @@ -183,7 +195,13 @@ class CreateIssueFromPageBlockEndpoint(BaseAPIView): project_id=project_id, page_id=page_id, ) - issue = Issue.objects.create(name=page_block.name, project_id=project_id) + issue = Issue.objects.create( + name=page_block.name, + project_id=project_id, + description=page_block.description, + description_html=page_block.description_html, + description_stripped=page_block.description_stripped, + ) _ = IssueAssignee.objects.create( issue=issue, assignee=request.user, project_id=project_id ) @@ -203,11 +221,125 @@ class CreateIssueFromPageBlockEndpoint(BaseAPIView): ) -class MyPagesEndpoint(BaseAPIView): +class RecentPagesEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + def get(self, request, slug, project_id): try: - pages = Page.objects.filter( - workspace__slug=slug, project_id=project_id, owned_by=request.user + subquery = PageFavorite.objects.filter( + user=request.user, + page_id=OuterRef("pk"), + project_id=project_id, + workspace__slug=slug, + ) + pages = ( + ( + Page.objects.filter( + updated_at__gte=(timezone.now() - timedelta(days=7)), + workspace__slug=slug, + project_id=project_id, + ) + .filter(project__project_projectmember__member=request.user) + .annotate(is_favorite=Exists(subquery)) + .order_by("-updated_by") + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .prefetch_related("labels") + ) + + serializer = PageSerializer(pages, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + print(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class FavoritePagesEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): + try: + subquery = PageFavorite.objects.filter( + user=request.user, + page_id=OuterRef("pk"), + project_id=project_id, + workspace__slug=slug, + ) + pages = ( + Page.objects.filter( + workspace__slug=slug, + project_id=project_id, + ) + .annotate(is_favorite=Exists(subquery)) + .filter(is_favorite=True) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .prefetch_related("labels") + ) + + serializer = PageSerializer(pages, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class MyPagesEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): + try: + pages = ( + Page.objects.filter( + workspace__slug=slug, project_id=project_id, owned_by=request.user + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .prefetch_related("labels") + ) + serializer = PageSerializer(pages, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class CreatedbyOtherPagesEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): + try: + pages = ( + Page.objects.filter( + ~Q(owned_by=request.user), + workspace__slug=slug, + project_id=project_id, + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .prefetch_related("labels") ) serializer = PageSerializer(pages, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/db/models/page.py b/apiserver/plane/db/models/page.py index 7eee40171..618c33f07 100644 --- a/apiserver/plane/db/models/page.py +++ b/apiserver/plane/db/models/page.py @@ -4,6 +4,7 @@ from django.conf import settings # Module imports from . import ProjectBaseModel +from plane.utils.html_processor import strip_tags class Page(ProjectBaseModel): @@ -17,6 +18,7 @@ class Page(ProjectBaseModel): access = models.PositiveSmallIntegerField( choices=((0, "Public"), (1, "Private")), default=0 ) + color = models.CharField(max_length=255, blank=True) labels = models.ManyToManyField( "db.Label", blank=True, related_name="pages", through="db.PageLabel" ) @@ -42,6 +44,35 @@ class PageBlock(ProjectBaseModel): "db.Issue", on_delete=models.SET_NULL, related_name="blocks", null=True ) completed_at = models.DateTimeField(null=True) + sort_order = models.FloatField(default=65535) + + def save(self, *args, **kwargs): + if self._state.adding: + largest_sort_order = PageBlock.objects.filter( + project=self.project, page=self.page + ).aggregate(largest=models.Max("sort_order"))["largest"] + if largest_sort_order is not None: + self.sort_order = largest_sort_order + 10000 + + # Strip the html tags using html parser + self.description_stripped = ( + None + if (self.description_html == "" or self.description_html is None) + else strip_tags(self.description_html) + ) + + if self.completed_at and self.issue: + try: + from plane.db.models import State, Issue + + completed_state = State.objects.filter( + group="completed", project=self.project + ).first() + if completed_state is not None: + Issue.objects.update(pk=self.issue_id, state=completed_state) + except ImportError: + pass + super(PageBlock, self).save(*args, **kwargs) class Meta: verbose_name = "Page Block"