[WEB-3038]feat: home preferences (#6308)
* WIP * WIP * WIP * WIP * Create home preference if not exist * chore: handled the unique state name validation (#6299) * fix: changed the response structure (#6301) * [WEB-1964]chore: cycles actions restructuring (#6298) * chore: cycles quick actions restructuring * chore: added additional actions to cycle list actions * chore: cycle quick action structure * chore: added additional actions to cycle list actions * chore: added end cycle hook * fix: updated end cycle export --------- Co-authored-by: gurusinath <gurusainath007@gmail.com> * fix: active cycle graph tooltip and endpoint validation (#6306) * [WEB-2870]feat: language support (#6215) * fix: adding language support package * fix: language support implementation using mobx * fix: adding more languages for support * fix: profile settings translations * feat: added language support for sidebar and user settings * feat: added language support for deactivation modal * fix: added project sync after transfer issues (#6200) * code refactor and improvement (#6203) * chore: package code refactoring * chore: component restructuring and refactor * chore: comment create improvement * refactor: enhance workspace and project wrapper modularity (#6207) * [WEB-2678]feat: added functionality to add labels directly from dropdown (#6211) * enhancement:added functionality to add features directly from dropdown * fix: fixed import order * fix: fixed lint errors * chore: added common component for project activity (#6212) * chore: added common component for project activity * fix: added enum * fix: added enum for initiatives * - Do not clear temp files that are locked. (#6214) - Handle edge cases in sync workspace * fix: labels empty state for drop down (#6216) * refactor: remove cn helper function from the editor package (#6217) * * feat: added language support to issue create modal in sidebar * fix: project activity type * * fix: added missing translations * fix: modified translation for plurals * fix: fixed spanish translation * dev: language type error in space user profile types * fix: type fixes * chore: added alpha tag --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: gurusinath <gurusainath007@gmail.com> * feat: introduced stacked bar chart and tree map chart. (#6305) * feat: add issue attachment external endpoint (#6307) * [PE-97] chore: re-order pages options (#6303) * chore: re-order pages dropdown options * chore: re-order pages dropdown options * fix: remove localdb tracing * [WEB-2937] feat: home recent activies list endpoint (#6295) * Crud for wuick links * Validate quick link existence * Add custom method for destroy and retrieve * Add List method * Remove print statements * List all the workspace quick links * feat: endpoint to get recently active items * Resolve conflicts * Resolve conflicts * Add filter to only list required entities * Return required fields * Add filter * Add filter * fix: remove emoji edit for uneditable pages (#6304) * Removed duplicate imports * feat: patch api * Enable sort order to be updatable * Return key name only insert missing keys use serializer to return data * Remove random generation of sort_order * Remove name field Remove random generation of sort_order --------- Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Co-authored-by: gurusinath <gurusainath007@gmail.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
This commit is contained in:
parent
fbbca0c519
commit
1acaae9d5e
6 changed files with 149 additions and 33 deletions
|
|
@ -20,7 +20,8 @@ from .workspace import (
|
||||||
WorkspaceMemberMeSerializer,
|
WorkspaceMemberMeSerializer,
|
||||||
WorkspaceUserPropertiesSerializer,
|
WorkspaceUserPropertiesSerializer,
|
||||||
WorkspaceUserLinkSerializer,
|
WorkspaceUserLinkSerializer,
|
||||||
WorkspaceRecentVisitSerializer
|
WorkspaceRecentVisitSerializer,
|
||||||
|
WorkspaceHomePreferenceSerializer,
|
||||||
)
|
)
|
||||||
from .project import (
|
from .project import (
|
||||||
ProjectSerializer,
|
ProjectSerializer,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ from plane.db.models import (
|
||||||
Issue,
|
Issue,
|
||||||
Page,
|
Page,
|
||||||
Project,
|
Project,
|
||||||
ProjectMember
|
ProjectMember,
|
||||||
|
WorkspaceHomePreference,
|
||||||
)
|
)
|
||||||
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
||||||
from django.core.validators import URLValidator
|
from django.core.validators import URLValidator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceSerializer(DynamicBaseSerializer):
|
class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||||
owner = UserLiteSerializer(read_only=True)
|
owner = UserLiteSerializer(read_only=True)
|
||||||
total_members = serializers.IntegerField(read_only=True)
|
total_members = serializers.IntegerField(read_only=True)
|
||||||
|
|
@ -119,6 +121,7 @@ class WorkspaceUserPropertiesSerializer(BaseSerializer):
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = ["workspace", "user"]
|
read_only_fields = ["workspace", "user"]
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceUserLinkSerializer(BaseSerializer):
|
class WorkspaceUserLinkSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WorkspaceUserLink
|
model = WorkspaceUserLink
|
||||||
|
|
@ -141,18 +144,29 @@ class WorkspaceUserLinkSerializer(BaseSerializer):
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class IssueRecentVisitSerializer(serializers.ModelSerializer):
|
class IssueRecentVisitSerializer(serializers.ModelSerializer):
|
||||||
project_identifier = serializers.SerializerMethodField()
|
project_identifier = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
fields = ["name", "state", "priority", "assignees", "type", "sequence_id", "project_id", "project_identifier"]
|
fields = [
|
||||||
|
"name",
|
||||||
|
"state",
|
||||||
|
"priority",
|
||||||
|
"assignees",
|
||||||
|
"type",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"project_identifier",
|
||||||
|
]
|
||||||
|
|
||||||
def get_project_identifier(self, obj):
|
def get_project_identifier(self, obj):
|
||||||
project = obj.project
|
project = obj.project
|
||||||
|
|
||||||
return project.identifier if project else None
|
return project.identifier if project else None
|
||||||
|
|
||||||
|
|
||||||
class ProjectMemberSerializer(BaseSerializer):
|
class ProjectMemberSerializer(BaseSerializer):
|
||||||
member = UserLiteSerializer(read_only=True)
|
member = UserLiteSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
@ -160,6 +174,7 @@ class ProjectMemberSerializer(BaseSerializer):
|
||||||
model = ProjectMember
|
model = ProjectMember
|
||||||
fields = ["member"]
|
fields = ["member"]
|
||||||
|
|
||||||
|
|
||||||
class ProjectRecentVisitSerializer(serializers.ModelSerializer):
|
class ProjectRecentVisitSerializer(serializers.ModelSerializer):
|
||||||
project_members = serializers.SerializerMethodField()
|
project_members = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
|
@ -168,47 +183,57 @@ class ProjectRecentVisitSerializer(serializers.ModelSerializer):
|
||||||
fields = ["id", "name", "logo_props", "project_members", "identifier"]
|
fields = ["id", "name", "logo_props", "project_members", "identifier"]
|
||||||
|
|
||||||
def get_project_members(self, obj):
|
def get_project_members(self, obj):
|
||||||
members = ProjectMember.objects.filter(project_id=obj.id).select_related('member')
|
members = ProjectMember.objects.filter(project_id=obj.id).select_related(
|
||||||
|
"member"
|
||||||
|
)
|
||||||
|
|
||||||
serializer = ProjectMemberSerializer(members, many=True)
|
serializer = ProjectMemberSerializer(members, many=True)
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
|
|
||||||
class PageRecentVisitSerializer(serializers.ModelSerializer):
|
class PageRecentVisitSerializer(serializers.ModelSerializer):
|
||||||
project_id = serializers.SerializerMethodField()
|
project_id = serializers.SerializerMethodField()
|
||||||
project_identifier = serializers.SerializerMethodField()
|
project_identifier = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Page
|
model = Page
|
||||||
fields = ["id", "name", "logo_props", "project_id", "owned_by", "project_identifier"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"logo_props",
|
||||||
|
"project_id",
|
||||||
|
"owned_by",
|
||||||
|
"project_identifier",
|
||||||
|
]
|
||||||
|
|
||||||
def get_project_id(self, obj):
|
def get_project_id(self, obj):
|
||||||
return obj.project_id if hasattr(obj, 'project_id') else obj.projects.values_list('id', flat=True).first()
|
return (
|
||||||
|
obj.project_id
|
||||||
|
if hasattr(obj, "project_id")
|
||||||
|
else obj.projects.values_list("id", flat=True).first()
|
||||||
|
)
|
||||||
|
|
||||||
def get_project_identifier(self, obj):
|
def get_project_identifier(self, obj):
|
||||||
project = obj.projects.first()
|
project = obj.projects.first()
|
||||||
|
|
||||||
return project.identifier if project else None
|
return project.identifier if project else None
|
||||||
|
|
||||||
|
|
||||||
def get_entity_model_and_serializer(entity_type):
|
def get_entity_model_and_serializer(entity_type):
|
||||||
entity_map = {
|
entity_map = {
|
||||||
"issue": (Issue, IssueRecentVisitSerializer),
|
"issue": (Issue, IssueRecentVisitSerializer),
|
||||||
"page": (Page, PageRecentVisitSerializer),
|
"page": (Page, PageRecentVisitSerializer),
|
||||||
"project": (Project, ProjectRecentVisitSerializer)
|
"project": (Project, ProjectRecentVisitSerializer),
|
||||||
}
|
}
|
||||||
return entity_map.get(entity_type, (None, None))
|
return entity_map.get(entity_type, (None, None))
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceRecentVisitSerializer(BaseSerializer):
|
class WorkspaceRecentVisitSerializer(BaseSerializer):
|
||||||
entity_data = serializers.SerializerMethodField()
|
entity_data = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserRecentVisit
|
model = UserRecentVisit
|
||||||
fields = [
|
fields = ["id", "entity_name", "entity_identifier", "entity_data", "visited_at"]
|
||||||
"id",
|
|
||||||
"entity_name",
|
|
||||||
"entity_identifier",
|
|
||||||
"entity_data",
|
|
||||||
"visited_at"
|
|
||||||
]
|
|
||||||
read_only_fields = ["workspace", "owner", "created_by", "updated_by"]
|
read_only_fields = ["workspace", "owner", "created_by", "updated_by"]
|
||||||
|
|
||||||
def get_entity_data(self, obj):
|
def get_entity_data(self, obj):
|
||||||
|
|
@ -225,3 +250,10 @@ class WorkspaceRecentVisitSerializer(BaseSerializer):
|
||||||
except entity_model.DoesNotExist:
|
except entity_model.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceHomePreferenceSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = WorkspaceHomePreference
|
||||||
|
fields = ["key", "is_enabled", "sort_order"]
|
||||||
|
read_only_fields = ["worspace", "created_by", "update_by"]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ from plane.app.views import (
|
||||||
WorkspaceFavoriteGroupEndpoint,
|
WorkspaceFavoriteGroupEndpoint,
|
||||||
WorkspaceDraftIssueViewSet,
|
WorkspaceDraftIssueViewSet,
|
||||||
QuickLinkViewSet,
|
QuickLinkViewSet,
|
||||||
UserRecentVisitViewSet
|
UserRecentVisitViewSet,
|
||||||
|
WorkspacePreferenceViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -215,25 +216,33 @@ urlpatterns = [
|
||||||
WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}),
|
WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}),
|
||||||
name="workspace-drafts-issues",
|
name="workspace-drafts-issues",
|
||||||
),
|
),
|
||||||
|
|
||||||
# quick link
|
# quick link
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/quick-links/",
|
"workspaces/<str:slug>/quick-links/",
|
||||||
QuickLinkViewSet.as_view({"get": "list", "post": "create"}),
|
QuickLinkViewSet.as_view({"get": "list", "post": "create"}),
|
||||||
name="workspace-quick-links"
|
name="workspace-quick-links",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/quick-links/<uuid:pk>/",
|
"workspaces/<str:slug>/quick-links/<uuid:pk>/",
|
||||||
QuickLinkViewSet.as_view({
|
QuickLinkViewSet.as_view(
|
||||||
"get": "retrieve",
|
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||||
"patch": "partial_update",
|
),
|
||||||
"delete": "destroy"
|
name="workspace-quick-links",
|
||||||
}),
|
),
|
||||||
name="workspace-quick-links"
|
# Widgets
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/home-preferences/",
|
||||||
|
WorkspacePreferenceViewSet.as_view(),
|
||||||
|
name="workspace-home-preference",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/home-preferences/<str:key>/",
|
||||||
|
WorkspacePreferenceViewSet.as_view(),
|
||||||
|
name="workspace-home-preference",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/recent-visits/",
|
"workspaces/<str:slug>/recent-visits/",
|
||||||
UserRecentVisitViewSet.as_view({"get": "list"}),
|
UserRecentVisitViewSet.as_view({"get": "list"}),
|
||||||
name="workspace-recent-visits"
|
name="workspace-recent-visits",
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ from .workspace.base import (
|
||||||
|
|
||||||
from .workspace.draft import WorkspaceDraftIssueViewSet
|
from .workspace.draft import WorkspaceDraftIssueViewSet
|
||||||
|
|
||||||
|
from .workspace.preference import WorkspacePreferenceViewSet
|
||||||
from .workspace.favorite import (
|
from .workspace.favorite import (
|
||||||
WorkspaceFavoriteEndpoint,
|
WorkspaceFavoriteEndpoint,
|
||||||
WorkspaceFavoriteGroupEndpoint,
|
WorkspaceFavoriteGroupEndpoint,
|
||||||
|
|
|
||||||
72
apiserver/plane/app/views/workspace/preference.py
Normal file
72
apiserver/plane/app/views/workspace/preference.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Module imports
|
||||||
|
from ..base import BaseAPIView
|
||||||
|
from plane.db.models.workspace import WorkspaceHomePreference
|
||||||
|
from plane.app.permissions import allow_permission, ROLE
|
||||||
|
from plane.db.models import Workspace
|
||||||
|
from plane.app.serializers.workspace import WorkspaceHomePreferenceSerializer
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspacePreferenceViewSet(BaseAPIView):
|
||||||
|
model = WorkspaceHomePreference
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
return WorkspaceHomePreferenceSerializer
|
||||||
|
|
||||||
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||||
|
def get(self, request, slug):
|
||||||
|
workspace = Workspace.objects.get(slug=slug)
|
||||||
|
|
||||||
|
get_preference = WorkspaceHomePreference.objects.filter(
|
||||||
|
user=request.user, workspace_id=workspace.id
|
||||||
|
)
|
||||||
|
|
||||||
|
create_preference_keys = []
|
||||||
|
|
||||||
|
keys = [key for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices]
|
||||||
|
|
||||||
|
for preference in keys:
|
||||||
|
if preference not in get_preference.values_list("key", flat=True):
|
||||||
|
create_preference_keys.append(preference)
|
||||||
|
|
||||||
|
preference = WorkspaceHomePreference.objects.bulk_create(
|
||||||
|
[
|
||||||
|
WorkspaceHomePreference(
|
||||||
|
key=key, user=request.user, workspace=workspace
|
||||||
|
)
|
||||||
|
for key in create_preference_keys
|
||||||
|
],
|
||||||
|
batch_size=10,
|
||||||
|
ignore_conflicts=True,
|
||||||
|
)
|
||||||
|
preference = WorkspaceHomePreference.objects.filter(
|
||||||
|
user=request.user, workspace_id=workspace.id
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
preference.values("key", "is_enabled", "config", "sort_order"),
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||||
|
def patch(self, request, slug, key):
|
||||||
|
preference = WorkspaceHomePreference.objects.filter(
|
||||||
|
key=key, workspace__slug=slug
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if preference:
|
||||||
|
serializer = WorkspaceHomePreferenceSerializer(
|
||||||
|
preference, 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)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
@ -69,6 +69,7 @@ from .workspace import (
|
||||||
WorkspaceTheme,
|
WorkspaceTheme,
|
||||||
WorkspaceUserProperties,
|
WorkspaceUserProperties,
|
||||||
WorkspaceUserLink,
|
WorkspaceUserLink,
|
||||||
|
WorkspaceHomePreference
|
||||||
)
|
)
|
||||||
|
|
||||||
from .favorite import UserFavorite
|
from .favorite import UserFavorite
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue