Skip to content

Commit 1acaae9

Browse files
sangeethailangoNarayanBavisettivamsikrishnamathalagurusainathanmolsinghbhatia
authored
[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>
1 parent fbbca0c commit 1acaae9

File tree

6 files changed

+149
-33
lines changed

6 files changed

+149
-33
lines changed

apiserver/plane/app/serializers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
WorkspaceMemberMeSerializer,
2121
WorkspaceUserPropertiesSerializer,
2222
WorkspaceUserLinkSerializer,
23-
WorkspaceRecentVisitSerializer
23+
WorkspaceRecentVisitSerializer,
24+
WorkspaceHomePreferenceSerializer,
2425
)
2526
from .project import (
2627
ProjectSerializer,

apiserver/plane/app/serializers/workspace.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616
WorkspaceUserProperties,
1717
WorkspaceUserLink,
1818
UserRecentVisit,
19-
Issue,
20-
Page,
19+
Issue,
20+
Page,
2121
Project,
22-
ProjectMember
22+
ProjectMember,
23+
WorkspaceHomePreference,
2324
)
2425
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
2526

2627
# Django imports
2728
from django.core.validators import URLValidator
2829
from django.core.exceptions import ValidationError
2930

31+
3032
class WorkSpaceSerializer(DynamicBaseSerializer):
3133
owner = UserLiteSerializer(read_only=True)
3234
total_members = serializers.IntegerField(read_only=True)
@@ -119,6 +121,7 @@ class Meta:
119121
fields = "__all__"
120122
read_only_fields = ["workspace", "user"]
121123

124+
122125
class WorkspaceUserLinkSerializer(BaseSerializer):
123126
class Meta:
124127
model = WorkspaceUserLink
@@ -129,7 +132,7 @@ def to_internal_value(self, data):
129132
url = data.get("url", "")
130133
if url and not url.startswith(("http://", "https://")):
131134
data["url"] = "http://" + url
132-
135+
133136
return super().to_internal_value(data)
134137

135138
def validate_url(self, value):
@@ -141,74 +144,96 @@ def validate_url(self, value):
141144

142145
return value
143146

147+
144148
class IssueRecentVisitSerializer(serializers.ModelSerializer):
145149
project_identifier = serializers.SerializerMethodField()
146150

147151
class Meta:
148152
model = Issue
149-
fields = ["name", "state", "priority", "assignees", "type", "sequence_id", "project_id", "project_identifier"]
153+
fields = [
154+
"name",
155+
"state",
156+
"priority",
157+
"assignees",
158+
"type",
159+
"sequence_id",
160+
"project_id",
161+
"project_identifier",
162+
]
150163

151164
def get_project_identifier(self, obj):
152165
project = obj.project
153166

154167
return project.identifier if project else None
155168

169+
156170
class ProjectMemberSerializer(BaseSerializer):
157171
member = UserLiteSerializer(read_only=True)
158172

159173
class Meta:
160174
model = ProjectMember
161175
fields = ["member"]
162176

177+
163178
class ProjectRecentVisitSerializer(serializers.ModelSerializer):
164-
project_members = serializers.SerializerMethodField()
165-
179+
project_members = serializers.SerializerMethodField()
180+
166181
class Meta:
167182
model = Project
168183
fields = ["id", "name", "logo_props", "project_members", "identifier"]
169184

170185
def get_project_members(self, obj):
171-
members = ProjectMember.objects.filter(project_id=obj.id).select_related('member')
186+
members = ProjectMember.objects.filter(project_id=obj.id).select_related(
187+
"member"
188+
)
172189

173190
serializer = ProjectMemberSerializer(members, many=True)
174191
return serializer.data
175-
192+
193+
176194
class PageRecentVisitSerializer(serializers.ModelSerializer):
177195
project_id = serializers.SerializerMethodField()
178196
project_identifier = serializers.SerializerMethodField()
179197

180198
class Meta:
181199
model = Page
182-
fields = ["id", "name", "logo_props", "project_id", "owned_by", "project_identifier"]
200+
fields = [
201+
"id",
202+
"name",
203+
"logo_props",
204+
"project_id",
205+
"owned_by",
206+
"project_identifier",
207+
]
183208

184209
def get_project_id(self, obj):
185-
return obj.project_id if hasattr(obj, 'project_id') else obj.projects.values_list('id', flat=True).first()
186-
210+
return (
211+
obj.project_id
212+
if hasattr(obj, "project_id")
213+
else obj.projects.values_list("id", flat=True).first()
214+
)
215+
187216
def get_project_identifier(self, obj):
188217
project = obj.projects.first()
189218

190219
return project.identifier if project else None
191220

221+
192222
def get_entity_model_and_serializer(entity_type):
193223
entity_map = {
194224
"issue": (Issue, IssueRecentVisitSerializer),
195225
"page": (Page, PageRecentVisitSerializer),
196-
"project": (Project, ProjectRecentVisitSerializer)
226+
"project": (Project, ProjectRecentVisitSerializer),
197227
}
198228
return entity_map.get(entity_type, (None, None))
199229

230+
200231
class WorkspaceRecentVisitSerializer(BaseSerializer):
201232
entity_data = serializers.SerializerMethodField()
202233

203234
class Meta:
204235
model = UserRecentVisit
205-
fields = [
206-
"id",
207-
"entity_name",
208-
"entity_identifier",
209-
"entity_data",
210-
"visited_at"
211-
]
236+
fields = ["id", "entity_name", "entity_identifier", "entity_data", "visited_at"]
212237
read_only_fields = ["workspace", "owner", "created_by", "updated_by"]
213238

214239
def get_entity_data(self, obj):
@@ -225,3 +250,10 @@ def get_entity_data(self, obj):
225250
except entity_model.DoesNotExist:
226251
return None
227252
return None
253+
254+
255+
class WorkspaceHomePreferenceSerializer(BaseSerializer):
256+
class Meta:
257+
model = WorkspaceHomePreference
258+
fields = ["key", "is_enabled", "sort_order"]
259+
read_only_fields = ["worspace", "created_by", "update_by"]

apiserver/plane/app/urls/workspace.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
WorkspaceFavoriteGroupEndpoint,
2929
WorkspaceDraftIssueViewSet,
3030
QuickLinkViewSet,
31-
UserRecentVisitViewSet
31+
UserRecentVisitViewSet,
32+
WorkspacePreferenceViewSet,
3233
)
3334

3435

@@ -215,25 +216,33 @@
215216
WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}),
216217
name="workspace-drafts-issues",
217218
),
218-
219219
# quick link
220220
path(
221221
"workspaces/<str:slug>/quick-links/",
222222
QuickLinkViewSet.as_view({"get": "list", "post": "create"}),
223-
name="workspace-quick-links"
223+
name="workspace-quick-links",
224+
),
225+
path(
226+
"workspaces/<str:slug>/quick-links/<uuid:pk>/",
227+
QuickLinkViewSet.as_view(
228+
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
229+
),
230+
name="workspace-quick-links",
231+
),
232+
# Widgets
233+
path(
234+
"workspaces/<str:slug>/home-preferences/",
235+
WorkspacePreferenceViewSet.as_view(),
236+
name="workspace-home-preference",
224237
),
225238
path(
226-
"workspaces/<str:slug>/quick-links/<uuid:pk>/",
227-
QuickLinkViewSet.as_view({
228-
"get": "retrieve",
229-
"patch": "partial_update",
230-
"delete": "destroy"
231-
}),
232-
name="workspace-quick-links"
239+
"workspaces/<str:slug>/home-preferences/<str:key>/",
240+
WorkspacePreferenceViewSet.as_view(),
241+
name="workspace-home-preference",
233242
),
234243
path(
235244
"workspaces/<str:slug>/recent-visits/",
236245
UserRecentVisitViewSet.as_view({"get": "list"}),
237-
name="workspace-recent-visits"
238-
)
246+
name="workspace-recent-visits",
247+
),
239248
]

apiserver/plane/app/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
from .workspace.draft import WorkspaceDraftIssueViewSet
4343

44+
from .workspace.preference import WorkspacePreferenceViewSet
4445
from .workspace.favorite import (
4546
WorkspaceFavoriteEndpoint,
4647
WorkspaceFavoriteGroupEndpoint,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Module imports
2+
from ..base import BaseAPIView
3+
from plane.db.models.workspace import WorkspaceHomePreference
4+
from plane.app.permissions import allow_permission, ROLE
5+
from plane.db.models import Workspace
6+
from plane.app.serializers.workspace import WorkspaceHomePreferenceSerializer
7+
8+
# Third party imports
9+
from rest_framework.response import Response
10+
from rest_framework import status
11+
12+
13+
class WorkspacePreferenceViewSet(BaseAPIView):
14+
model = WorkspaceHomePreference
15+
16+
def get_serializer_class(self):
17+
return WorkspaceHomePreferenceSerializer
18+
19+
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
20+
def get(self, request, slug):
21+
workspace = Workspace.objects.get(slug=slug)
22+
23+
get_preference = WorkspaceHomePreference.objects.filter(
24+
user=request.user, workspace_id=workspace.id
25+
)
26+
27+
create_preference_keys = []
28+
29+
keys = [key for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices]
30+
31+
for preference in keys:
32+
if preference not in get_preference.values_list("key", flat=True):
33+
create_preference_keys.append(preference)
34+
35+
preference = WorkspaceHomePreference.objects.bulk_create(
36+
[
37+
WorkspaceHomePreference(
38+
key=key, user=request.user, workspace=workspace
39+
)
40+
for key in create_preference_keys
41+
],
42+
batch_size=10,
43+
ignore_conflicts=True,
44+
)
45+
preference = WorkspaceHomePreference.objects.filter(
46+
user=request.user, workspace_id=workspace.id
47+
)
48+
49+
return Response(
50+
preference.values("key", "is_enabled", "config", "sort_order"),
51+
status=status.HTTP_200_OK,
52+
)
53+
54+
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
55+
def patch(self, request, slug, key):
56+
preference = WorkspaceHomePreference.objects.filter(
57+
key=key, workspace__slug=slug
58+
).first()
59+
60+
if preference:
61+
serializer = WorkspaceHomePreferenceSerializer(
62+
preference, data=request.data, partial=True
63+
)
64+
65+
if serializer.is_valid():
66+
serializer.save()
67+
return Response(serializer.data, status=status.HTTP_200_OK)
68+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
69+
70+
return Response(
71+
{"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST
72+
)

apiserver/plane/db/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
WorkspaceTheme,
7070
WorkspaceUserProperties,
7171
WorkspaceUserLink,
72+
WorkspaceHomePreference
7273
)
7374

7475
from .favorite import UserFavorite

0 commit comments

Comments
 (0)