From 16f31f7ba113702059eb84b7c232f0a6a519ac22 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 29 Aug 2023 12:41:44 +0530 Subject: [PATCH 1/6] 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): From 3a1d945f30b9542b4898f110c965178f6ee6f250 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 29 Aug 2023 12:51:25 +0530 Subject: [PATCH 2/6] fix: added project member filter --- apiserver/plane/api/views/view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index e630a1eba..1543e7bee 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -65,6 +65,7 @@ class WorkspaceViewIssuesEndpoint(BaseAPIView): Issue.issue_objects.filter( **queries, workspace__slug=slug, + project__project_projectmember__member=self.request.user ) .filter(**filters) .select_related("workspace") From 74b764d8d40cc797b85de590e15d74737c6d14d7 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Thu, 14 Sep 2023 19:23:33 +0530 Subject: [PATCH 3/6] fix: added pagination in workspace views --- apiserver/plane/api/views/view.py | 17 +++++++-- ..._alter_analyticview_created_by_and_more.py | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index 1543e7bee..95a483dd4 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -80,9 +80,20 @@ class WorkspaceViewIssuesEndpoint(BaseAPIView): ) ) ) - - serializer = IssueLiteSerializer(issues, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) + if request.GET.get("per_page", False) and request.GET.get("cursor", False): + return self.paginate( + request=request, + queryset=issues, + on_results=lambda issues: IssueLiteSerializer( + issues, many=True + ).data, + ) + else: + return Response( + {"error": "per_page and cursor are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + except WorkspaceView.DoesNotExist: return Response( {"error": "Workspace View does not exist"}, status=status.HTTP_404_NOT_FOUND diff --git a/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py b/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py new file mode 100644 index 000000000..b3f56a3de --- /dev/null +++ b/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.3 on 2023-09-14 13:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0044_auto_20230913_0709'), + ] + + operations = [ + migrations.CreateModel( + name='WorkspaceView', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=255, verbose_name='View Name')), + ('description', models.TextField(blank=True, verbose_name='View Description')), + ('query', models.JSONField(verbose_name='View Query')), + ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)), + ('query_data', models.JSONField(default=dict)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_views', to='db.workspace')), + ], + options={ + 'verbose_name': 'Workspace View', + 'verbose_name_plural': 'Workspace Views', + 'db_table': 'workspace_views', + 'ordering': ('-created_at',), + }, + ), + ] From e6e2b4e096b830a00610cfc8c438675870415bd8 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 19 Sep 2023 13:47:29 +0530 Subject: [PATCH 4/6] fix: filters and group up by for workspace issues --- apiserver/plane/api/urls.py | 8 +- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/view.py | 179 ++++++++++++++++++++------ 3 files changed, 149 insertions(+), 40 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 9d5a5b548..b9c5cc6b3 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -103,7 +103,7 @@ from plane.api.views import ( ## End Estimates # Views WorkspaceViewViewSet, - WorkspaceViewIssuesEndpoint, + WorkspaceViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet, @@ -675,7 +675,11 @@ urlpatterns = [ ), path( "workspaces//views//issues/", - WorkspaceViewIssuesEndpoint.as_view(), + WorkspaceViewIssuesViewSet.as_view( + { + "get": "list", + } + ), name="workspace-view-issues", ), path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 219c5367f..f35ae4e76 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -56,7 +56,7 @@ from .workspace import ( LeaveWorkspaceEndpoint, ) from .state import StateViewSet -from .view import WorkspaceViewViewSet, WorkspaceViewIssuesEndpoint, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import WorkspaceViewViewSet, WorkspaceViewIssuesViewSet, 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 95a483dd4..726d0fcf1 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -1,4 +1,18 @@ # Django imports +from django.db.models import ( + Prefetch, + OuterRef, + Func, + F, + Case, + Value, + CharField, + When, + Exists, + Max, +) +from django.utils.decorators import method_decorator +from django.views.decorators.gzip import gzip_page from django.db import IntegrityError from django.db.models import Prefetch, OuterRef, Exists @@ -23,8 +37,11 @@ from plane.db.models import ( Issue, IssueViewFavorite, IssueReaction, + IssueLink, + IssueAttachment, ) from plane.utils.issue_filters import issue_filters +from plane.utils.grouper import group_results class WorkspaceViewViewSet(BaseViewSet): @@ -49,55 +66,143 @@ class WorkspaceViewViewSet(BaseViewSet): ) -class WorkspaceViewIssuesEndpoint(BaseAPIView): +class WorkspaceViewIssuesViewSet(BaseViewSet): 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, - project__project_projectmember__member=self.request.user - ) - .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"), - ) + def get_queryset(self): + return ( + Issue.issue_objects.annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .filter(workspace__slug=self.kwargs.get("slug")) + .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"), ) ) - if request.GET.get("per_page", False) and request.GET.get("cursor", False): - return self.paginate( - request=request, - queryset=issues, - on_results=lambda issues: IssueLiteSerializer( - issues, many=True - ).data, + ) + + + @method_decorator(gzip_page) + def list(self, request, slug, view_id): + try: + filters = issue_filters(request.query_params, "GET") + + # Custom ordering for priority and state + priority_order = ["urgent", "high", "medium", "low", "none"] + state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] + + order_by_param = request.GET.get("order_by", "-created_at") + + issue_queryset = ( + self.get_queryset() + .filter(**filters) + .filter(project__project_projectmember__member=self.request.user) + .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(module_id=F("issue_module__module_id")) + .annotate( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter( + issue=OuterRef("id") + ) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + ) + + # Priority Ordering + if order_by_param == "priority" or order_by_param == "-priority": + priority_order = ( + priority_order + if order_by_param == "priority" + else priority_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + priority_order=Case( + *[ + When(priority=p, then=Value(i)) + for i, p in enumerate(priority_order) + ], + output_field=CharField(), + ) + ).order_by("priority_order") + + # State Ordering + elif order_by_param in [ + "state__name", + "state__group", + "-state__name", + "-state__group", + ]: + state_order = ( + state_order + if order_by_param in ["state__name", "state__group"] + else state_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + state_order=Case( + *[ + When(state__group=state_group, then=Value(i)) + for i, state_group in enumerate(state_order) + ], + default=Value(len(state_order)), + output_field=CharField(), + ) + ).order_by("state_order") + # assignee and label ordering + elif order_by_param in [ + "labels__name", + "-labels__name", + "assignees__first_name", + "-assignees__first_name", + ]: + issue_queryset = issue_queryset.annotate( + max_values=Max( + order_by_param[1::] + if order_by_param.startswith("-") + else order_by_param + ) + ).order_by( + "-max_values" if order_by_param.startswith("-") else "max_values" ) else: + issue_queryset = issue_queryset.order_by(order_by_param) + + issues = IssueLiteSerializer(issue_queryset, many=True).data + + ## Grouping the results + group_by = request.GET.get("group_by", False) + sub_group_by = request.GET.get("sub_group_by", False) + if sub_group_by and sub_group_by == group_by: return Response( - {"error": "per_page and cursor are required"}, + {"error": "Group by and sub group by cannot be same"}, status=status.HTTP_400_BAD_REQUEST, ) - except WorkspaceView.DoesNotExist: - return Response( - {"error": "Workspace View does not exist"}, status=status.HTTP_404_NOT_FOUND - ) + if group_by: + return Response( + group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK + ) + + return Response(issues, status=status.HTTP_200_OK) + except Exception as e: capture_exception(e) return Response( From 8ef9df28c4ecc0183d27ad627f2588a63efbb2ab Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 19 Sep 2023 18:51:08 +0530 Subject: [PATCH 5/6] fix: changed name workspace view to global view --- apiserver/plane/api/serializers/__init__.py | 2 +- apiserver/plane/api/serializers/view.py | 8 ++-- apiserver/plane/api/urls.py | 36 +++++++++--------- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/view.py | 15 ++++---- ..._alter_analyticview_created_by_and_more.py | 38 ------------------- .../db/migrations/0045_auto_20230915_0655.py | 28 +++++++++++++- apiserver/plane/db/models/__init__.py | 2 +- apiserver/plane/db/models/view.py | 10 ++--- 9 files changed, 64 insertions(+), 77 deletions(-) delete mode 100644 apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index b8b7c6539..dbf7ca049 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -23,7 +23,7 @@ from .project import ( ProjectPublicMemberSerializer ) from .state import StateSerializer, StateLiteSerializer -from .view import WorkspaceViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer +from .view import GlobalViewSerializer, 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 a2502c59a..a3b6f48be 100644 --- a/apiserver/plane/api/serializers/view.py +++ b/apiserver/plane/api/serializers/view.py @@ -5,15 +5,15 @@ from rest_framework import serializers from .base import BaseSerializer from .workspace import WorkspaceLiteSerializer from .project import ProjectLiteSerializer -from plane.db.models import WorkspaceView, IssueView, IssueViewFavorite +from plane.db.models import GlobalView, IssueView, IssueViewFavorite from plane.utils.issue_filters import issue_filters -class WorkspaceViewSerializer(BaseSerializer): +class GlobalViewSerializer(BaseSerializer): workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True) class Meta: - model = WorkspaceView + model = GlobalView fields = "__all__" read_only_fields = [ "workspace", @@ -26,7 +26,7 @@ class WorkspaceViewSerializer(BaseSerializer): validated_data["query"] = issue_filters(query_params, "POST") else: validated_data["query"] = dict() - return WorkspaceView.objects.create(**validated_data) + return GlobalView.objects.create(**validated_data) def update(self, instance, validated_data): query_params = validated_data.get("query_data", {}) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index b9c5cc6b3..c206c57c0 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -102,8 +102,8 @@ from plane.api.views import ( BulkEstimatePointEndpoint, ## End Estimates # Views - WorkspaceViewViewSet, - WorkspaceViewIssuesViewSet, + GlobalViewViewSet, + GlobalViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet, @@ -186,7 +186,6 @@ from plane.api.views import ( ## Exporter ExportIssuesEndpoint, ## End Exporter - ) @@ -243,7 +242,11 @@ urlpatterns = [ UpdateUserTourCompletedEndpoint.as_view(), name="user-tour", ), - path("users/workspaces//activities/", UserActivityEndpoint.as_view(), name="user-activities"), + path( + "users/workspaces//activities/", + UserActivityEndpoint.as_view(), + name="user-activities", + ), # user workspaces path( "users/me/workspaces/", @@ -653,17 +656,17 @@ urlpatterns = [ ), path( "workspaces//views/", - WorkspaceViewViewSet.as_view( + GlobalViewViewSet.as_view( { "get": "list", "post": "create", } ), - name="workspace-view", + name="global-view", ), path( "workspaces//views//", - WorkspaceViewViewSet.as_view( + GlobalViewViewSet.as_view( { "get": "retrieve", "put": "update", @@ -671,16 +674,7 @@ urlpatterns = [ "delete": "destroy", } ), - name="workspace-view", - ), - path( - "workspaces//views//issues/", - WorkspaceViewIssuesViewSet.as_view( - { - "get": "list", - } - ), - name="workspace-view-issues", + name="global-view", ), path( "workspaces//projects//user-favorite-views/", @@ -802,8 +796,12 @@ urlpatterns = [ ), path( "workspaces//issues/", - WorkSpaceIssuesEndpoint.as_view(), - name="workspace-issue", + GlobalViewIssuesViewSet.as_view( + { + "get": "list", + } + ), + name="global-view-issues", ), path( "workspaces//projects//issue-labels/", diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index f35ae4e76..c03d6d5b7 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -56,7 +56,7 @@ from .workspace import ( LeaveWorkspaceEndpoint, ) from .state import StateViewSet -from .view import WorkspaceViewViewSet, WorkspaceViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, 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 726d0fcf1..b6f1d7c4b 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -24,7 +24,7 @@ from sentry_sdk import capture_exception # Module imports from . import BaseViewSet, BaseAPIView from plane.api.serializers import ( - WorkspaceViewSerializer, + GlobalViewSerializer, IssueViewSerializer, IssueLiteSerializer, IssueViewFavoriteSerializer, @@ -32,7 +32,7 @@ from plane.api.serializers import ( from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission from plane.db.models import ( Workspace, - WorkspaceView, + GlobalView, IssueView, Issue, IssueViewFavorite, @@ -44,9 +44,9 @@ from plane.utils.issue_filters import issue_filters from plane.utils.grouper import group_results -class WorkspaceViewViewSet(BaseViewSet): - serializer_class = WorkspaceViewSerializer - model = WorkspaceView +class GlobalViewViewSet(BaseViewSet): + serializer_class = GlobalViewSerializer + model = GlobalView permission_classes = [ WorkspaceEntityPermission, ] @@ -66,7 +66,7 @@ class WorkspaceViewViewSet(BaseViewSet): ) -class WorkspaceViewIssuesViewSet(BaseViewSet): +class GlobalViewIssuesViewSet(BaseViewSet): permission_classes = [ WorkspaceEntityPermission, ] @@ -80,6 +80,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): .values("count") ) .filter(workspace__slug=self.kwargs.get("slug")) + .select_related("project") .select_related("workspace") .select_related("state") .select_related("parent") @@ -95,7 +96,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): @method_decorator(gzip_page) - def list(self, request, slug, view_id): + def list(self, request, slug): try: filters = issue_filters(request.query_params, "GET") diff --git a/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py b/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py deleted file mode 100644 index b3f56a3de..000000000 --- a/apiserver/plane/db/migrations/0045_alter_analyticview_created_by_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-14 13:17 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('db', '0044_auto_20230913_0709'), - ] - - operations = [ - migrations.CreateModel( - name='WorkspaceView', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(max_length=255, verbose_name='View Name')), - ('description', models.TextField(blank=True, verbose_name='View Description')), - ('query', models.JSONField(verbose_name='View Query')), - ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)), - ('query_data', models.JSONField(default=dict)), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), - ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), - ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_views', to='db.workspace')), - ], - options={ - 'verbose_name': 'Workspace View', - 'verbose_name_plural': 'Workspace Views', - 'db_table': 'workspace_views', - 'ordering': ('-created_at',), - }, - ), - ] diff --git a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py index 7bd907e29..a757def07 100644 --- a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py +++ b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py @@ -1,6 +1,10 @@ # Generated by Django 4.2.3 on 2023-09-15 06:55 -from django.db import migrations +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + def update_issue_activity(apps, schema_editor): IssueActivityModel = apps.get_model("db", "IssueActivity") @@ -19,5 +23,27 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='GlobalView', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=255, verbose_name='View Name')), + ('description', models.TextField(blank=True, verbose_name='View Description')), + ('query', models.JSONField(verbose_name='View Query')), + ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)), + ('query_data', models.JSONField(default=dict)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')), + ], + options={ + 'verbose_name': 'Global View', + 'verbose_name_plural': 'Global Views', + 'db_table': 'global_views', + 'ordering': ('-created_at',), + }, + ), migrations.RunPython(update_issue_activity), ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 3742f36f9..9496b5906 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -50,7 +50,7 @@ from .state import State from .cycle import Cycle, CycleIssue, CycleFavorite -from .view import WorkspaceView, IssueView, IssueViewFavorite +from .view import GlobalView, 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 9d9f8f5cc..6e0a47105 100644 --- a/apiserver/plane/db/models/view.py +++ b/apiserver/plane/db/models/view.py @@ -6,9 +6,9 @@ from django.conf import settings from . import ProjectBaseModel, BaseModel -class WorkspaceView(BaseModel): +class GlobalView(BaseModel): workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_views" + "db.Workspace", on_delete=models.CASCADE, related_name="global_views" ) name = models.CharField(max_length=255, verbose_name="View Name") description = models.TextField(verbose_name="View Description", blank=True) @@ -19,9 +19,9 @@ class WorkspaceView(BaseModel): query_data = models.JSONField(default=dict) class Meta: - verbose_name = "Workspace View" - verbose_name_plural = "Workspace Views" - db_table = "workspace_views" + verbose_name = "Global View" + verbose_name_plural = "Global Views" + db_table = "global_views" ordering = ("-created_at",) def __str__(self): From 7cd5112e8ff0b9926f1b500b0a6ae3930571e98f Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 19 Sep 2023 19:44:43 +0530 Subject: [PATCH 6/6] fix: reordered the urls --- apiserver/plane/api/urls.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index c206c57c0..c10c4a745 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -676,6 +676,15 @@ urlpatterns = [ ), name="global-view", ), + path( + "workspaces//issues/", + GlobalViewIssuesViewSet.as_view( + { + "get": "list", + } + ), + name="global-view-issues", + ), path( "workspaces//projects//user-favorite-views/", IssueViewFavoriteViewSet.as_view( @@ -794,15 +803,6 @@ urlpatterns = [ ), name="project-issue", ), - path( - "workspaces//issues/", - GlobalViewIssuesViewSet.as_view( - { - "get": "list", - } - ), - name="global-view-issues", - ), path( "workspaces//projects//issue-labels/", LabelViewSet.as_view(