From 16f31f7ba113702059eb84b7c232f0a6a519ac22 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 29 Aug 2023 12:41:44 +0530 Subject: [PATCH] feat: workspace views --- apiserver/plane/api/serializers/__init__.py | 2 +- apiserver/plane/api/serializers/view.py | 31 ++++++++- apiserver/plane/api/urls.py | 29 +++++++++ apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/view.py | 72 ++++++++++++++++++++- apiserver/plane/db/models/__init__.py | 2 +- apiserver/plane/db/models/view.py | 25 ++++++- 7 files changed, 157 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 5855f0413..59694d769 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -22,7 +22,7 @@ from .project import ( ProjectMemberAdminSerializer, ) from .state import StateSerializer, StateLiteSerializer -from .view import IssueViewSerializer, IssueViewFavoriteSerializer +from .view import WorkspaceViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer from .cycle import CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer, CycleWriteSerializer from .asset import FileAssetSerializer from .issue import ( diff --git a/apiserver/plane/api/serializers/view.py b/apiserver/plane/api/serializers/view.py index 076228ae0..a2502c59a 100644 --- a/apiserver/plane/api/serializers/view.py +++ b/apiserver/plane/api/serializers/view.py @@ -5,10 +5,39 @@ from rest_framework import serializers from .base import BaseSerializer from .workspace import WorkspaceLiteSerializer from .project import ProjectLiteSerializer -from plane.db.models import IssueView, IssueViewFavorite +from plane.db.models import WorkspaceView, IssueView, IssueViewFavorite from plane.utils.issue_filters import issue_filters +class WorkspaceViewSerializer(BaseSerializer): + workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True) + + class Meta: + model = WorkspaceView + fields = "__all__" + read_only_fields = [ + "workspace", + "query", + ] + + def create(self, validated_data): + query_params = validated_data.get("query_data", {}) + if bool(query_params): + validated_data["query"] = issue_filters(query_params, "POST") + else: + validated_data["query"] = dict() + return WorkspaceView.objects.create(**validated_data) + + def update(self, instance, validated_data): + query_params = validated_data.get("query_data", {}) + if bool(query_params): + validated_data["query"] = issue_filters(query_params, "POST") + else: + validated_data["query"] = dict() + validated_data["query"] = issue_filters(query_params, "PATCH") + return super().update(instance, validated_data) + + class IssueViewSerializer(BaseSerializer): is_favorite = serializers.BooleanField(read_only=True) project_detail = ProjectLiteSerializer(source="project", read_only=True) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index a6beac693..7b7f58e21 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -98,6 +98,8 @@ from plane.api.views import ( BulkEstimatePointEndpoint, ## End Estimates # Views + WorkspaceViewViewSet, + WorkspaceViewIssuesEndpoint, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet, @@ -633,6 +635,33 @@ urlpatterns = [ ViewIssuesEndpoint.as_view(), name="project-view-issues", ), + path( + "workspaces//views/", + WorkspaceViewViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="workspace-view", + ), + path( + "workspaces//views//", + WorkspaceViewViewSet.as_view( + { + "get": "retrieve", + "put": "update", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="workspace-view", + ), + path( + "workspaces//views//issues/", + WorkspaceViewIssuesEndpoint.as_view(), + name="workspace-view-issues", + ), path( "workspaces//projects//user-favorite-views/", IssueViewFavoriteViewSet.as_view( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 9572c552f..ce313ce3d 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -55,7 +55,7 @@ from .workspace import ( WorkspaceMembersEndpoint, ) from .state import StateViewSet -from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import WorkspaceViewViewSet, WorkspaceViewIssuesEndpoint, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet from .cycle import ( CycleViewSet, CycleIssueViewSet, diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index 32ba24c8b..e630a1eba 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -10,12 +10,15 @@ from sentry_sdk import capture_exception # Module imports from . import BaseViewSet, BaseAPIView from plane.api.serializers import ( + WorkspaceViewSerializer, IssueViewSerializer, IssueLiteSerializer, IssueViewFavoriteSerializer, ) -from plane.api.permissions import ProjectEntityPermission +from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission from plane.db.models import ( + Workspace, + WorkspaceView, IssueView, Issue, IssueViewFavorite, @@ -24,6 +27,73 @@ from plane.db.models import ( from plane.utils.issue_filters import issue_filters +class WorkspaceViewViewSet(BaseViewSet): + serializer_class = WorkspaceViewSerializer + model = WorkspaceView + permission_classes = [ + WorkspaceEntityPermission, + ] + + def perform_create(self, serializer): + workspace = Workspace.objects.get(slug=self.kwargs.get("slug")) + serializer.save(workspace_id=workspace.id) + + def get_queryset(self): + return self.filter_queryset( + super() + .get_queryset() + .filter(workspace__slug=self.kwargs.get("slug")) + .select_related("workspace") + .order_by("-created_at") + .distinct() + ) + + +class WorkspaceViewIssuesEndpoint(BaseAPIView): + permission_classes = [ + WorkspaceEntityPermission, + ] + + def get(self, request, slug, view_id): + try: + view = WorkspaceView.objects.get(pk=view_id) + queries = view.query + + filters = issue_filters(request.query_params, "GET") + + issues = ( + Issue.issue_objects.filter( + **queries, + workspace__slug=slug, + ) + .filter(**filters) + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), + ) + ) + ) + + serializer = IssueLiteSerializer(issues, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except WorkspaceView.DoesNotExist: + return Response( + {"error": "Workspace View does not exist"}, status=status.HTTP_404_NOT_FOUND + ) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + class IssueViewViewSet(BaseViewSet): serializer_class = IssueViewSerializer model = IssueView diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 659eea3eb..13c6d6868 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -48,7 +48,7 @@ from .state import State from .cycle import Cycle, CycleIssue, CycleFavorite -from .view import IssueView, IssueViewFavorite +from .view import WorkspaceView, IssueView, IssueViewFavorite from .module import Module, ModuleMember, ModuleIssue, ModuleLink, ModuleFavorite diff --git a/apiserver/plane/db/models/view.py b/apiserver/plane/db/models/view.py index 6a968af53..9d9f8f5cc 100644 --- a/apiserver/plane/db/models/view.py +++ b/apiserver/plane/db/models/view.py @@ -3,7 +3,30 @@ from django.db import models from django.conf import settings # Module import -from . import ProjectBaseModel +from . import ProjectBaseModel, BaseModel + + +class WorkspaceView(BaseModel): + workspace = models.ForeignKey( + "db.Workspace", on_delete=models.CASCADE, related_name="workspace_views" + ) + name = models.CharField(max_length=255, verbose_name="View Name") + description = models.TextField(verbose_name="View Description", blank=True) + query = models.JSONField(verbose_name="View Query") + access = models.PositiveSmallIntegerField( + default=1, choices=((0, "Private"), (1, "Public")) + ) + query_data = models.JSONField(default=dict) + + class Meta: + verbose_name = "Workspace View" + verbose_name_plural = "Workspace Views" + db_table = "workspace_views" + ordering = ("-created_at",) + + def __str__(self): + """Return name of the View""" + return f"{self.name} <{self.workspace.name}>" class IssueView(ProjectBaseModel):