diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index 479f08d5a3..623071573c 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -121,8 +121,6 @@ from .exporter import ExporterHistorySerializer from .webhook import WebhookSerializer, WebhookLogSerializer -from .dashboard import DashboardSerializer, WidgetSerializer - from .favorite import UserFavoriteSerializer from .draft import ( diff --git a/apiserver/plane/app/serializers/dashboard.py b/apiserver/plane/app/serializers/dashboard.py deleted file mode 100644 index d09fc3e354..0000000000 --- a/apiserver/plane/app/serializers/dashboard.py +++ /dev/null @@ -1,21 +0,0 @@ -# Module imports -from .base import BaseSerializer -from plane.db.models import DeprecatedDashboard, DeprecatedWidget - -# Third party frameworks -from rest_framework import serializers - - -class DashboardSerializer(BaseSerializer): - class Meta: - model = DeprecatedDashboard - fields = "__all__" - - -class WidgetSerializer(BaseSerializer): - is_visible = serializers.BooleanField(read_only=True) - widget_filters = serializers.JSONField(read_only=True) - - class Meta: - model = DeprecatedWidget - fields = ["id", "key", "is_visible", "widget_filters"] diff --git a/apiserver/plane/app/urls/__init__.py b/apiserver/plane/app/urls/__init__.py index 3be75536b2..8cfc18dbb6 100644 --- a/apiserver/plane/app/urls/__init__.py +++ b/apiserver/plane/app/urls/__init__.py @@ -2,7 +2,6 @@ from .analytic import urlpatterns as analytic_urls from .api import urlpatterns as api_urls from .asset import urlpatterns as asset_urls from .cycle import urlpatterns as cycle_urls -from .dashboard import urlpatterns as dashboard_urls from .estimate import urlpatterns as estimate_urls from .external import urlpatterns as external_urls from .intake import urlpatterns as intake_urls @@ -23,7 +22,6 @@ urlpatterns = [ *analytic_urls, *asset_urls, *cycle_urls, - *dashboard_urls, *estimate_urls, *external_urls, *intake_urls, diff --git a/apiserver/plane/app/urls/dashboard.py b/apiserver/plane/app/urls/dashboard.py deleted file mode 100644 index 0dc24a8081..0000000000 --- a/apiserver/plane/app/urls/dashboard.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.urls import path - - -from plane.app.views import DashboardEndpoint, WidgetsEndpoint - - -urlpatterns = [ - path( - "workspaces//dashboard/", - DashboardEndpoint.as_view(), - name="dashboard", - ), - path( - "workspaces//dashboard//", - DashboardEndpoint.as_view(), - name="dashboard", - ), - path( - "dashboard//widgets//", - WidgetsEndpoint.as_view(), - name="widgets", - ), -] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 684179d904..ba63920f6c 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -210,8 +210,6 @@ from .webhook.base import ( WebhookSecretRegenerateEndpoint, ) -from .dashboard.base import DashboardEndpoint, WidgetsEndpoint - from .error_404 import custom_404_view from .notification.base import MarkAllReadNotificationViewSet diff --git a/apiserver/plane/app/views/dashboard/base.py b/apiserver/plane/app/views/dashboard/base.py deleted file mode 100644 index ff67bb73b9..0000000000 --- a/apiserver/plane/app/views/dashboard/base.py +++ /dev/null @@ -1,812 +0,0 @@ -# Django imports -from django.contrib.postgres.aggregates import ArrayAgg -from django.contrib.postgres.fields import ArrayField -from django.db.models import ( - Case, - CharField, - Count, - Exists, - F, - Func, - IntegerField, - JSONField, - OuterRef, - Prefetch, - Q, - Subquery, - UUIDField, - Value, - When, -) -from django.db.models.functions import Coalesce -from django.utils import timezone -from rest_framework import status - -# Third Party imports -from rest_framework.response import Response - -from plane.app.serializers import ( - DashboardSerializer, - IssueActivitySerializer, - IssueSerializer, - WidgetSerializer, -) -from plane.db.models import ( - DeprecatedDashboard, - DeprecatedDashboardWidget, - Issue, - IssueActivity, - FileAsset, - IssueLink, - IssueRelation, - Project, - DeprecatedWidget, - WorkspaceMember, - CycleIssue, -) -from plane.utils.issue_filters import issue_filters - -# Module imports -from .. import BaseAPIView - - -def dashboard_overview_stats(self, request, slug): - assigned_issues = ( - Issue.issue_objects.filter( - (Q(assignees__in=[request.user]) & Q(issue_assignee__deleted_at__isnull=True)), - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - workspace__slug=slug, - ) - .filter( - Q( - project__project_projectmember__role=5, - project__guest_view_all_features=True, - ) - | Q( - project__project_projectmember__role=5, - project__guest_view_all_features=False, - created_by=self.request.user, - ) - | - # For other roles (role < 5), show all issues - Q(project__project_projectmember__role__gt=5), - project__project_projectmember__member=self.request.user, - project__project_projectmember__is_active=True, - ) - .count() - ) - - pending_issues_count = ( - Issue.issue_objects.filter( - ~Q(state__group__in=["completed", "cancelled"]), - target_date__lt=timezone.now().date(), - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - workspace__slug=slug, - assignees__in=[request.user], - ) - .filter( - Q( - project__project_projectmember__role=5, - project__guest_view_all_features=True, - ) - | Q( - project__project_projectmember__role=5, - project__guest_view_all_features=False, - created_by=self.request.user, - ) - | - # For other roles (role < 5), show all issues - Q(project__project_projectmember__role__gt=5), - project__project_projectmember__member=self.request.user, - project__project_projectmember__is_active=True, - ) - .count() - ) - - created_issues_count = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - created_by_id=request.user.id, - ) - .filter( - Q( - project__project_projectmember__role=5, - project__guest_view_all_features=True, - ) - | Q( - project__project_projectmember__role=5, - project__guest_view_all_features=False, - created_by=self.request.user, - ) - | - # For other roles (role < 5), show all issues - Q(project__project_projectmember__role__gt=5), - project__project_projectmember__member=self.request.user, - project__project_projectmember__is_active=True, - ) - .count() - ) - - completed_issues_count = ( - Issue.issue_objects.filter( - ( - Q(assignees__in=[request.user]) - & Q(issue_assignee__deleted_at__isnull=True) - ), - workspace__slug=slug, - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - state__group="completed", - ) - .filter( - Q( - project__project_projectmember__role=5, - project__guest_view_all_features=True, - ) - | Q( - project__project_projectmember__role=5, - project__guest_view_all_features=False, - created_by=self.request.user, - ) - | - # For other roles (role < 5), show all issues - Q(project__project_projectmember__role__gt=5), - project__project_projectmember__member=self.request.user, - project__project_projectmember__is_active=True, - ) - .count() - ) - - return Response( - { - "assigned_issues_count": assigned_issues, - "pending_issues_count": pending_issues_count, - "completed_issues_count": completed_issues_count, - "created_issues_count": created_issues_count, - }, - status=status.HTTP_200_OK, - ) - - -def dashboard_assigned_issues(self, request, slug): - filters = issue_filters(request.query_params, "GET") - issue_type = request.GET.get("issue_type", None) - - # get all the assigned issues - assigned_issues = ( - Issue.issue_objects.filter( - ( - Q(assignees__in=[request.user]) - & Q(issue_assignee__deleted_at__isnull=True) - ), - workspace__slug=slug, - project__project_projectmember__member=request.user, - project__project_projectmember__is_active=True, - ) - .filter(**filters) - .select_related("workspace", "project", "state", "parent") - .prefetch_related("assignees", "labels", "issue_module__module") - .prefetch_related( - Prefetch( - "issue_relation", - queryset=IssueRelation.objects.select_related( - "related_issue" - ).select_related("issue"), - ) - ) - .annotate( - cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] - ) - ) - .annotate( - link_count=IssueLink.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - attachment_count=FileAsset.objects.filter( - issue_id=OuterRef("id"), - entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, - ) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - label_ids=Coalesce( - ArrayAgg( - "labels__id", - distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - assignee_ids=Coalesce( - ArrayAgg( - "assignees__id", - distinct=True, - filter=Q( - ~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True) - & Q(issue_assignee__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - module_ids=Coalesce( - ArrayAgg( - "issue_module__module_id", - distinct=True, - filter=Q( - ~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - ) - ) - - if WorkspaceMember.objects.filter( - workspace__slug=slug, member=request.user, role=5, is_active=True - ).exists(): - assigned_issues = assigned_issues.filter(created_by=request.user) - - # Priority Ordering - priority_order = ["urgent", "high", "medium", "low", "none"] - assigned_issues = assigned_issues.annotate( - priority_order=Case( - *[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)], - output_field=CharField(), - ) - ).order_by("priority_order") - - if issue_type == "pending": - pending_issues_count = assigned_issues.filter( - state__group__in=["backlog", "started", "unstarted"] - ).count() - pending_issues = assigned_issues.filter( - state__group__in=["backlog", "started", "unstarted"] - )[:5] - return Response( - { - "issues": IssueSerializer( - pending_issues, many=True, expand=self.expand - ).data, - "count": pending_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "completed": - completed_issues_count = assigned_issues.filter( - state__group__in=["completed"] - ).count() - completed_issues = assigned_issues.filter(state__group__in=["completed"])[:5] - return Response( - { - "issues": IssueSerializer( - completed_issues, many=True, expand=self.expand - ).data, - "count": completed_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "overdue": - overdue_issues_count = assigned_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__lt=timezone.now(), - ).count() - overdue_issues = assigned_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__lt=timezone.now(), - )[:5] - return Response( - { - "issues": IssueSerializer( - overdue_issues, many=True, expand=self.expand - ).data, - "count": overdue_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "upcoming": - upcoming_issues_count = assigned_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__gte=timezone.now(), - ).count() - upcoming_issues = assigned_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__gte=timezone.now(), - )[:5] - return Response( - { - "issues": IssueSerializer( - upcoming_issues, many=True, expand=self.expand - ).data, - "count": upcoming_issues_count, - }, - status=status.HTTP_200_OK, - ) - - return Response( - {"error": "Please specify a valid issue type"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - -def dashboard_created_issues(self, request, slug): - filters = issue_filters(request.query_params, "GET") - issue_type = request.GET.get("issue_type", None) - - # get all the assigned issues - created_issues = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project__project_projectmember__member=request.user, - project__project_projectmember__is_active=True, - created_by=request.user, - ) - .filter(**filters) - .select_related("workspace", "project", "state", "parent") - .prefetch_related("assignees", "labels", "issue_module__module") - .annotate( - cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] - ) - ) - .annotate( - link_count=IssueLink.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - attachment_count=FileAsset.objects.filter( - issue_id=OuterRef("id"), - entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, - ) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - label_ids=Coalesce( - ArrayAgg( - "labels__id", - distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - assignee_ids=Coalesce( - ArrayAgg( - "assignees__id", - distinct=True, - filter=Q( - ~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True) - & Q(issue_assignee__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - module_ids=Coalesce( - ArrayAgg( - "issue_module__module_id", - distinct=True, - filter=Q( - ~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__deleted_at__isnull=True) - ), - ), - Value([], output_field=ArrayField(UUIDField())), - ), - ) - .order_by("created_at") - ) - - # Priority Ordering - priority_order = ["urgent", "high", "medium", "low", "none"] - created_issues = created_issues.annotate( - priority_order=Case( - *[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)], - output_field=CharField(), - ) - ).order_by("priority_order") - - if issue_type == "pending": - pending_issues_count = created_issues.filter( - state__group__in=["backlog", "started", "unstarted"] - ).count() - pending_issues = created_issues.filter( - state__group__in=["backlog", "started", "unstarted"] - )[:5] - return Response( - { - "issues": IssueSerializer( - pending_issues, many=True, expand=self.expand - ).data, - "count": pending_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "completed": - completed_issues_count = created_issues.filter( - state__group__in=["completed"] - ).count() - completed_issues = created_issues.filter(state__group__in=["completed"])[:5] - return Response( - { - "issues": IssueSerializer(completed_issues, many=True).data, - "count": completed_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "overdue": - overdue_issues_count = created_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__lt=timezone.now(), - ).count() - overdue_issues = created_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__lt=timezone.now(), - )[:5] - return Response( - { - "issues": IssueSerializer(overdue_issues, many=True).data, - "count": overdue_issues_count, - }, - status=status.HTTP_200_OK, - ) - - if issue_type == "upcoming": - upcoming_issues_count = created_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__gte=timezone.now(), - ).count() - upcoming_issues = created_issues.filter( - state__group__in=["backlog", "unstarted", "started"], - target_date__gte=timezone.now(), - )[:5] - return Response( - { - "issues": IssueSerializer(upcoming_issues, many=True).data, - "count": upcoming_issues_count, - }, - status=status.HTTP_200_OK, - ) - - return Response( - {"error": "Please specify a valid issue type"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - -def dashboard_issues_by_state_groups(self, request, slug): - filters = issue_filters(request.query_params, "GET") - state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] - extra_filters = {} - - if WorkspaceMember.objects.filter( - workspace__slug=slug, member=request.user, role=5, is_active=True - ).exists(): - extra_filters = {"created_by": request.user} - - issues_by_state_groups = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - assignees__in=[request.user], - ) - .filter(**filters, **extra_filters) - .values("state__group") - .annotate(count=Count("id")) - ) - - # default state - all_groups = {state: 0 for state in state_order} - - # Update counts for existing groups - for entry in issues_by_state_groups: - all_groups[entry["state__group"]] = entry["count"] - - # Prepare output including all groups with their counts - output_data = [ - {"state": group, "count": count} for group, count in all_groups.items() - ] - - return Response(output_data, status=status.HTTP_200_OK) - - -def dashboard_issues_by_priority(self, request, slug): - filters = issue_filters(request.query_params, "GET") - priority_order = ["urgent", "high", "medium", "low", "none"] - extra_filters = {} - - if WorkspaceMember.objects.filter( - workspace__slug=slug, member=request.user, role=5, is_active=True - ).exists(): - extra_filters = {"created_by": request.user} - - issues_by_priority = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project__project_projectmember__is_active=True, - project__project_projectmember__member=request.user, - assignees__in=[request.user], - ) - .filter(**filters, **extra_filters) - .values("priority") - .annotate(count=Count("id")) - ) - - # default priority - all_groups = {priority: 0 for priority in priority_order} - - # Update counts for existing groups - for entry in issues_by_priority: - all_groups[entry["priority"]] = entry["count"] - - # Prepare output including all groups with their counts - output_data = [ - {"priority": group, "count": count} for group, count in all_groups.items() - ] - - return Response(output_data, status=status.HTTP_200_OK) - - -def dashboard_recent_activity(self, request, slug): - queryset = IssueActivity.objects.filter( - ~Q(field__in=["comment", "vote", "reaction", "draft"]), - workspace__slug=slug, - project__project_projectmember__member=request.user, - project__project_projectmember__is_active=True, - project__archived_at__isnull=True, - actor=request.user, - ).select_related("actor", "workspace", "issue", "project")[:8] - - return Response( - IssueActivitySerializer(queryset, many=True).data, status=status.HTTP_200_OK - ) - - -def dashboard_recent_projects(self, request, slug): - project_ids = ( - IssueActivity.objects.filter( - workspace__slug=slug, - project__project_projectmember__member=request.user, - project__project_projectmember__is_active=True, - project__archived_at__isnull=True, - actor=request.user, - ) - .values_list("project_id", flat=True) - .distinct() - ) - - # Extract project IDs from the recent projects - unique_project_ids = set(project_id for project_id in project_ids) - - # Fetch additional projects only if needed - if len(unique_project_ids) < 4: - additional_projects = Project.objects.filter( - project_projectmember__member=request.user, - project_projectmember__is_active=True, - archived_at__isnull=True, - workspace__slug=slug, - ).exclude(id__in=unique_project_ids) - - # Append additional project IDs to the existing list - unique_project_ids.update(additional_projects.values_list("id", flat=True)) - - return Response(list(unique_project_ids)[:4], status=status.HTTP_200_OK) - - -def dashboard_recent_collaborators(self, request, slug): - project_members_with_activities = ( - WorkspaceMember.objects.filter(workspace__slug=slug, is_active=True) - .annotate( - active_issue_count=Count( - Case( - When( - member__issue_assignee__issue__state__group__in=[ - "unstarted", - "started", - ], - member__issue_assignee__issue__workspace__slug=slug, - member__issue_assignee__issue__project__project_projectmember__member=request.user, - member__issue_assignee__issue__project__project_projectmember__is_active=True, - then=F("member__issue_assignee__issue__id"), - ), - distinct=True, - output_field=IntegerField(), - ), - distinct=True, - ), - user_id=F("member_id"), - ) - .values("user_id", "active_issue_count") - .order_by("-active_issue_count") - .distinct() - ) - return Response((project_members_with_activities), status=status.HTTP_200_OK) - - -class DashboardEndpoint(BaseAPIView): - def create(self, request, slug): - serializer = DashboardSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def patch(self, request, slug, pk): - serializer = DashboardSerializer(data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, slug, pk): - serializer = DashboardSerializer(data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_204_NO_CONTENT) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, slug, dashboard_id=None): - if not dashboard_id: - dashboard_type = request.GET.get("dashboard_type", None) - if dashboard_type == "home": - dashboard, created = DeprecatedDashboard.objects.get_or_create( - type_identifier=dashboard_type, - owned_by=request.user, - is_default=True, - ) - - if created: - widgets_to_fetch = [ - "overview_stats", - "assigned_issues", - "created_issues", - "issues_by_state_groups", - "issues_by_priority", - "recent_activity", - "recent_projects", - "recent_collaborators", - ] - - updated_dashboard_widgets = [] - for widget_key in widgets_to_fetch: - widget = DeprecatedWidget.objects.filter( - key=widget_key - ).values_list("id", flat=True) - if widget: - updated_dashboard_widgets.append( - DeprecatedDashboardWidget( - widget_id=widget, dashboard_id=dashboard.id - ) - ) - - DeprecatedDashboardWidget.objects.bulk_create( - updated_dashboard_widgets, batch_size=100 - ) - - widgets = ( - DeprecatedWidget.objects.annotate( - is_visible=Exists( - DeprecatedDashboardWidget.objects.filter( - widget_id=OuterRef("pk"), - dashboard_id=dashboard.id, - is_visible=True, - ) - ) - ) - .annotate( - dashboard_filters=Subquery( - DeprecatedDashboardWidget.objects.filter( - widget_id=OuterRef("pk"), - dashboard_id=dashboard.id, - filters__isnull=False, - ) - .exclude(filters={}) - .values("filters")[:1] - ) - ) - .annotate( - widget_filters=Case( - When( - dashboard_filters__isnull=False, - then=F("dashboard_filters"), - ), - default=F("filters"), - output_field=JSONField(), - ) - ) - ) - return Response( - { - "dashboard": DashboardSerializer(dashboard).data, - "widgets": WidgetSerializer(widgets, many=True).data, - }, - status=status.HTTP_200_OK, - ) - return Response( - {"error": "Please specify a valid dashboard type"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - widget_key = request.GET.get("widget_key", "overview_stats") - - WIDGETS_MAPPER = { - "overview_stats": dashboard_overview_stats, - "assigned_issues": dashboard_assigned_issues, - "created_issues": dashboard_created_issues, - "issues_by_state_groups": dashboard_issues_by_state_groups, - "issues_by_priority": dashboard_issues_by_priority, - "recent_activity": dashboard_recent_activity, - "recent_projects": dashboard_recent_projects, - "recent_collaborators": dashboard_recent_collaborators, - } - - func = WIDGETS_MAPPER.get(widget_key) - if func is not None: - response = func(self, request=request, slug=slug) - if isinstance(response, Response): - return response - - return Response( - {"error": "Please specify a valid widget key"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - -class WidgetsEndpoint(BaseAPIView): - def patch(self, request, dashboard_id, widget_id): - dashboard_widget = DeprecatedDashboardWidget.objects.filter( - widget_id=widget_id, dashboard_id=dashboard_id - ).first() - dashboard_widget.is_visible = request.data.get( - "is_visible", dashboard_widget.is_visible - ) - dashboard_widget.sort_order = request.data.get( - "sort_order", dashboard_widget.sort_order - ) - dashboard_widget.filters = request.data.get("filters", dashboard_widget.filters) - dashboard_widget.save() - return Response({"message": "successfully updated"}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/db/migrations/0092_alter_deprecateddashboardwidget_unique_together_and_more.py b/apiserver/plane/db/migrations/0092_alter_deprecateddashboardwidget_unique_together_and_more.py new file mode 100644 index 0000000000..86369bbdc3 --- /dev/null +++ b/apiserver/plane/db/migrations/0092_alter_deprecateddashboardwidget_unique_together_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.18 on 2025-02-25 15:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0091_issuecomment_edited_at_and_more"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="deprecateddashboardwidget", + unique_together=None, + ), + migrations.RemoveField( + model_name="deprecateddashboardwidget", + name="created_by", + ), + migrations.RemoveField( + model_name="deprecateddashboardwidget", + name="dashboard", + ), + migrations.RemoveField( + model_name="deprecateddashboardwidget", + name="updated_by", + ), + migrations.RemoveField( + model_name="deprecateddashboardwidget", + name="widget", + ), + migrations.DeleteModel( + name="DeprecatedDashboard", + ), + migrations.DeleteModel( + name="DeprecatedDashboardWidget", + ), + migrations.DeleteModel( + name="DeprecatedWidget", + ), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 8e20a8d671..3cf46c9194 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -3,7 +3,6 @@ from .api import APIActivityLog, APIToken from .asset import FileAsset from .base import BaseModel from .cycle import Cycle, CycleIssue, CycleUserProperties -from .dashboard import DeprecatedDashboard, DeprecatedDashboardWidget, DeprecatedWidget from .deploy_board import DeployBoard from .draft import ( DraftIssue, diff --git a/apiserver/plane/db/models/dashboard.py b/apiserver/plane/db/models/dashboard.py deleted file mode 100644 index d1101e3529..0000000000 --- a/apiserver/plane/db/models/dashboard.py +++ /dev/null @@ -1,92 +0,0 @@ -import uuid - -# Django imports -from django.db import models - -# Module imports -from ..mixins import TimeAuditModel -from .base import BaseModel - - -class DeprecatedDashboard(BaseModel): - DASHBOARD_CHOICES = ( - ("workspace", "Workspace"), - ("project", "Project"), - ("home", "Home"), - ("team", "Team"), - ("user", "User"), - ) - name = models.CharField(max_length=255) - description_html = models.TextField(blank=True, default="

") - identifier = models.UUIDField(null=True) - owned_by = models.ForeignKey( - "db.User", on_delete=models.CASCADE, related_name="dashboards" - ) - is_default = models.BooleanField(default=False) - type_identifier = models.CharField( - max_length=30, - choices=DASHBOARD_CHOICES, - verbose_name="Dashboard Type", - default="home", - ) - logo_props = models.JSONField(default=dict) - - def __str__(self): - """Return name of the dashboard""" - return f"{self.name}" - - class Meta: - verbose_name = "DeprecatedDashboard" - verbose_name_plural = "DeprecatedDashboards" - db_table = "deprecated_dashboards" - ordering = ("-created_at",) - - -class DeprecatedWidget(TimeAuditModel): - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) - key = models.CharField(max_length=255) - filters = models.JSONField(default=dict) - logo_props = models.JSONField(default=dict) - - def __str__(self): - """Return name of the widget""" - return f"{self.key}" - - class Meta: - verbose_name = "DeprecatedWidget" - verbose_name_plural = "DeprecatedWidgets" - db_table = "deprecated_widgets" - ordering = ("-created_at",) - - -class DeprecatedDashboardWidget(BaseModel): - widget = models.ForeignKey( - DeprecatedWidget, on_delete=models.CASCADE, related_name="dashboard_widgets" - ) - dashboard = models.ForeignKey( - DeprecatedDashboard, on_delete=models.CASCADE, related_name="dashboard_widgets" - ) - is_visible = models.BooleanField(default=True) - sort_order = models.FloatField(default=65535) - filters = models.JSONField(default=dict) - properties = models.JSONField(default=dict) - - def __str__(self): - """Return name of the dashboard""" - return f"{self.dashboard.name} {self.widget.key}" - - class Meta: - unique_together = ("widget", "dashboard", "deleted_at") - constraints = [ - models.UniqueConstraint( - fields=["widget", "dashboard"], - condition=models.Q(deleted_at__isnull=True), - name="dashboard_widget_unique_widget_dashboard_when_deleted_at_null", - ) - ] - verbose_name = "Deprecated Dashboard Widget" - verbose_name_plural = "Deprecated Dashboard Widgets" - db_table = "deprecated_dashboard_widgets" - ordering = ("-created_at",)