From 018c111ee06d3b4c6824bbaa2eaae241087bd86b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 2 Jun 2023 12:32:34 +0530 Subject: [PATCH 01/16] feat: user display name for the entire system --- apiserver/plane/api/serializers/__init__.py | 7 +-- apiserver/plane/api/serializers/people.py | 57 ------------------- apiserver/plane/api/serializers/user.py | 25 +++++++- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/auth_extended.py | 2 +- apiserver/plane/api/views/importer.py | 2 +- apiserver/plane/api/views/page.py | 2 +- .../plane/api/views/{people.py => user.py} | 0 apiserver/plane/db/models/user.py | 10 +++- 9 files changed, 38 insertions(+), 69 deletions(-) delete mode 100644 apiserver/plane/api/serializers/people.py rename apiserver/plane/api/views/{people.py => user.py} (100%) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 505a9978d5b..00335528e99 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -1,10 +1,5 @@ from .base import BaseSerializer -from .people import ( - ChangePasswordSerializer, - ResetPasswordSerializer, - TokenSerializer, -) -from .user import UserSerializer, UserLiteSerializer +from .user import UserSerializer, UserLiteSerializer, ChangePasswordSerializer, ResetPasswordSerializer from .workspace import ( WorkSpaceSerializer, WorkSpaceMemberSerializer, diff --git a/apiserver/plane/api/serializers/people.py b/apiserver/plane/api/serializers/people.py deleted file mode 100644 index b8b59416cc7..00000000000 --- a/apiserver/plane/api/serializers/people.py +++ /dev/null @@ -1,57 +0,0 @@ -from rest_framework.serializers import ( - ModelSerializer, - Serializer, - CharField, - SerializerMethodField, -) -from rest_framework.authtoken.models import Token -from rest_framework_simplejwt.tokens import RefreshToken - - -from plane.db.models import User - - -class UserSerializer(ModelSerializer): - class Meta: - model = User - fields = "__all__" - extra_kwargs = {"password": {"write_only": True}} - - -class ChangePasswordSerializer(Serializer): - model = User - - """ - Serializer for password change endpoint. - """ - old_password = CharField(required=True) - new_password = CharField(required=True) - - -class ResetPasswordSerializer(Serializer): - model = User - - """ - Serializer for password change endpoint. - """ - new_password = CharField(required=True) - confirm_password = CharField(required=True) - - -class TokenSerializer(ModelSerializer): - - user = UserSerializer() - access_token = SerializerMethodField() - refresh_token = SerializerMethodField() - - def get_access_token(self, obj): - refresh_token = RefreshToken.for_user(obj.user) - return str(refresh_token.access_token) - - def get_refresh_token(self, obj): - refresh_token = RefreshToken.for_user(obj.user) - return str(refresh_token) - - class Meta: - model = Token - fields = "__all__" diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index d8978479e84..dbcb028db06 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -1,3 +1,6 @@ +# Third party imports +from rest_framework import serializers + # Module import from .base import BaseSerializer from plane.db.models import User @@ -37,11 +40,31 @@ class Meta: "id", "first_name", "last_name", - "email", "avatar", "is_bot", + "display_name", ] read_only_fields = [ "id", "is_bot", ] + + +class ChangePasswordSerializer(serializers.Serializer): + model = User + + """ + Serializer for password change endpoint. + """ + old_password = serializers.CharField(required=True) + new_password = serializers.CharField(required=True) + + +class ResetPasswordSerializer(serializers.Serializer): + model = User + + """ + Serializer for password change endpoint. + """ + new_password = serializers.CharField(required=True) + confirm_password = serializers.CharField(required=True) diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 4177b137162..66695f7b1d4 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -13,7 +13,7 @@ ProjectMemberUserEndpoint, ProjectFavoritesViewSet, ) -from .people import ( +from .user import ( UserEndpoint, UpdateUserOnBoardedEndpoint, UserActivityEndpoint, diff --git a/apiserver/plane/api/views/auth_extended.py b/apiserver/plane/api/views/auth_extended.py index 56dc091f489..df3f3aaca83 100644 --- a/apiserver/plane/api/views/auth_extended.py +++ b/apiserver/plane/api/views/auth_extended.py @@ -22,7 +22,7 @@ ## Module imports from . import BaseAPIView -from plane.api.serializers.people import ( +from plane.api.serializers import ( ChangePasswordSerializer, ResetPasswordSerializer, ) diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index 2e0f1cec00e..e5f5a4ab01f 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -436,7 +436,7 @@ def post(self, request, slug, project_id, service): actor=request.user, project_id=project_id, workspace_id=project.workspace_id, - comment=f"{request.user.email} importer the issue from {service}", + comment=f"imported the issue from {service}", verb="created", created_by=request.user, ) diff --git a/apiserver/plane/api/views/page.py b/apiserver/plane/api/views/page.py index edca47ffe39..d9fad9eaa95 100644 --- a/apiserver/plane/api/views/page.py +++ b/apiserver/plane/api/views/page.py @@ -301,7 +301,7 @@ def post(self, request, slug, project_id, page_id, page_block_id): issue=issue, actor=request.user, project_id=project_id, - comment=f"{request.user.email} created the issue from {page_block.name} block", + comment=f"created the issue from {page_block.name} block", verb="created", ) diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/user.py similarity index 100% rename from apiserver/plane/api/views/people.py rename to apiserver/plane/api/views/user.py diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index b0ab7215977..210f2cff650 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -1,6 +1,7 @@ # Python imports -from enum import unique import uuid +import string +import random # Django imports from django.db import models @@ -18,6 +19,12 @@ from slack_sdk import WebClient from slack_sdk.errors import SlackApiError +def generate_display_name(): + letters = string.ascii_letters + random_letters = ''.join(random.choice(letters) for _ in range(6)) + return random_letters + + class User(AbstractBaseUser, PermissionsMixin): id = models.UUIDField( @@ -73,6 +80,7 @@ class User(AbstractBaseUser, PermissionsMixin): role = models.CharField(max_length=300, null=True, blank=True) is_bot = models.BooleanField(default=False) theme = models.JSONField(default=dict) + display_name = models.CharField(max_length=255, default=generate_display_name) USERNAME_FIELD = "email" From 9308986901d76d9d3311ce2dcce863e885567656 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 2 Jun 2023 14:33:54 +0530 Subject: [PATCH 02/16] feat: update issue activity to remove emails --- .../plane/bgtasks/issue_activites_task.py | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 417fe2324f7..aea3a086bbd 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -44,7 +44,7 @@ def track_name( field="name", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the start date to {requested_data.get('name')}", + comment=f"updated the name to {requested_data.get('name')}", ) ) @@ -71,7 +71,7 @@ def track_parent( field="parent", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to None", + comment=f"updated the parent issue to None", old_identifier=old_parent.id, new_identifier=None, ) @@ -91,7 +91,7 @@ def track_parent( field="parent", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to {new_parent.name}", + comment=f"updated the parent issue to {new_parent.name}", old_identifier=old_parent.id if old_parent is not None else None, new_identifier=new_parent.id, ) @@ -119,7 +119,7 @@ def track_priority( field="priority", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the priority to None", + comment=f"updated the priority to None", ) ) else: @@ -133,7 +133,7 @@ def track_priority( field="priority", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the priority to {requested_data.get('priority')}", + comment=f"updated the priority to {requested_data.get('priority')}", ) ) @@ -161,7 +161,7 @@ def track_state( field="state", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the state to {new_state.name}", + comment=f"updated the state to {new_state.name}", old_identifier=old_state.id, new_identifier=new_state.id, ) @@ -190,7 +190,7 @@ def track_description( field="description", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the description to {requested_data.get('description_html')}", + comment=f"updated the description to {requested_data.get('description_html')}", ) ) @@ -216,7 +216,7 @@ def track_target_date( field="target_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the target date to None", + comment=f"updated the target date to None", ) ) else: @@ -230,7 +230,7 @@ def track_target_date( field="target_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the target date to {requested_data.get('target_date')}", + comment=f"updated the target date to {requested_data.get('target_date')}", ) ) @@ -256,7 +256,7 @@ def track_start_date( field="start_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the start date to None", + comment=f"updated the start date to None", ) ) else: @@ -270,7 +270,7 @@ def track_start_date( field="start_date", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the start date to {requested_data.get('start_date')}", + comment=f"updated the start date to {requested_data.get('start_date')}", ) ) @@ -299,7 +299,7 @@ def track_labels( field="labels", project=project, workspace=project.workspace, - comment=f"{actor.email} added label {label.name}", + comment=f"added label {label.name}", new_identifier=label.id, old_identifier=None, ) @@ -320,7 +320,7 @@ def track_labels( field="labels", project=project, workspace=project.workspace, - comment=f"{actor.email} removed label {label.name}", + comment=f"removed label {label.name}", old_identifier=label.id, new_identifier=None, ) @@ -349,12 +349,12 @@ def track_assignees( actor=actor, verb="updated", old_value="", - new_value=assignee.email, + new_value=assignee.display_name, field="assignees", project=project, workspace=project.workspace, - comment=f"{actor.email} added assignee {assignee.email}", - new_identifier=actor.id, + comment=f"added assignee {assignee.display_name}", + new_identifier=assignee, ) ) @@ -370,13 +370,13 @@ def track_assignees( issue_id=issue_id, actor=actor, verb="updated", - old_value=assignee.email, + old_value=assignee.display_name, new_value="", - field="assignee", + field="assignees", project=project, workspace=project.workspace, - comment=f"{actor.email} removed assignee {assignee.email}", - old_identifier=actor.id, + comment=f"removed assignee {assignee.display_name}", + old_identifier=assignee, ) ) @@ -415,7 +415,7 @@ def track_blocks( field="blocks", project=project, workspace=project.workspace, - comment=f"{actor.email} added blocking issue {project.identifier}-{issue.sequence_id}", + comment=f"added blocking issue {project.identifier}-{issue.sequence_id}", new_identifier=issue.id, ) ) @@ -437,7 +437,7 @@ def track_blocks( field="blocks", project=project, workspace=project.workspace, - comment=f"{actor.email} removed blocking issue {project.identifier}-{issue.sequence_id}", + comment=f"removed blocking issue {project.identifier}-{issue.sequence_id}", old_identifier=issue.id, ) ) @@ -477,7 +477,7 @@ def track_blockings( field="blocking", project=project, workspace=project.workspace, - comment=f"{actor.email} added blocked by issue {project.identifier}-{issue.sequence_id}", + comment=f"added blocked by issue {project.identifier}-{issue.sequence_id}", new_identifier=issue.id, ) ) @@ -499,7 +499,7 @@ def track_blockings( field="blocking", project=project, workspace=project.workspace, - comment=f"{actor.email} removed blocked by issue {project.identifier}-{issue.sequence_id}", + comment=f"removed blocked by issue {project.identifier}-{issue.sequence_id}", old_identifier=issue.id, ) ) @@ -513,7 +513,7 @@ def create_issue_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created the issue", + comment=f"created the issue", verb="created", actor=actor, ) @@ -535,7 +535,7 @@ def track_estimate_points( field="estimate_point", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the estimate point to None", + comment=f"updated the estimate point to None", ) ) else: @@ -549,7 +549,7 @@ def track_estimate_points( field="estimate_point", project=project, workspace=project.workspace, - comment=f"{actor.email} updated the estimate point to {requested_data.get('estimate_point')}", + comment=f"updated the estimate point to {requested_data.get('estimate_point')}", ) ) @@ -597,7 +597,7 @@ def delete_issue_activity( IssueActivity( project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the issue", + comment=f"deleted the issue", verb="deleted", actor=actor, field="issue", @@ -618,7 +618,7 @@ def create_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created a comment", + comment=f"created a comment", verb="created", actor=actor, field="comment", @@ -643,7 +643,7 @@ def update_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} updated a comment", + comment=f"updated a comment", verb="updated", actor=actor, field="comment", @@ -664,7 +664,7 @@ def delete_comment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the comment", + comment=f"deleted the comment", verb="deleted", actor=actor, field="comment", @@ -702,7 +702,7 @@ def create_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}", + comment=f"updated cycle from {old_cycle.name} to {new_cycle.name}", old_identifier=old_cycle.id, new_identifier=new_cycle.id, ) @@ -723,7 +723,7 @@ def create_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} added cycle {cycle.name}", + comment=f"added cycle {cycle.name}", new_identifier=cycle.id, ) ) @@ -752,7 +752,7 @@ def delete_cycle_issue_activity( field="cycles", project=project, workspace=project.workspace, - comment=f"{actor.email} removed this issue from {cycle.name if cycle is not None else None}", + comment=f"removed this issue from {cycle.name if cycle is not None else None}", old_identifier=cycle.id if cycle is not None else None, ) ) @@ -788,7 +788,7 @@ def create_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}", + comment=f"updated module from {old_module.name} to {new_module.name}", old_identifier=old_module.id, new_identifier=new_module.id, ) @@ -808,7 +808,7 @@ def create_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} added module {module.name}", + comment=f"added module {module.name}", new_identifier=module.id, ) ) @@ -837,7 +837,7 @@ def delete_module_issue_activity( field="modules", project=project, workspace=project.workspace, - comment=f"{actor.email} removed this issue from {module.name if module is not None else None}", + comment=f"removed this issue from {module.name if module is not None else None}", old_identifier=module.id if module is not None else None, ) ) @@ -856,7 +856,7 @@ def create_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created a link", + comment=f"created a link", verb="created", actor=actor, field="link", @@ -880,7 +880,7 @@ def update_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} updated a link", + comment=f"updated a link", verb="updated", actor=actor, field="link", @@ -900,7 +900,7 @@ def delete_link_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the link", + comment=f"deleted the link", verb="deleted", actor=actor, field="link", @@ -921,7 +921,7 @@ def create_attachment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} created an attachment", + comment=f"created an attachment", verb="created", actor=actor, field="attachment", @@ -939,7 +939,7 @@ def delete_attachment_activity( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} deleted the attachment", + comment=f"deleted the attachment", verb="deleted", actor=actor, field="attachment", From 73a81d99a84921a7c0785562dac0b10fa89af6d3 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 12 Jun 2023 13:05:12 +0530 Subject: [PATCH 03/16] dev: update to display name wherever assignees__email and member__email --- apiserver/plane/api/views/analytic.py | 10 +++++----- apiserver/plane/api/views/project.py | 2 +- apiserver/plane/api/views/workspace.py | 4 ++-- .../plane/bgtasks/analytic_plot_export.py | 20 +++++++++---------- apiserver/plane/db/models/user.py | 14 +++++++++---- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/api/views/analytic.py index 56ca12baef2..60fe636c1a5 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/api/views/analytic.py @@ -77,12 +77,12 @@ def get(self, request, slug): ) assignee_details = {} - if x_axis in ["assignees__email"] or segment in ["assignees__email"]: + if x_axis in ["assignees__display_name"] or segment in ["assignees__display_name"]: assignee_details = ( Issue.objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") .distinct("assignees__id") - .values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name") + .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name") ) @@ -241,21 +241,21 @@ def get(self, request, slug): ) most_issue_created_user = ( queryset.exclude(created_by=None) - .values("created_by__first_name", "created_by__last_name", "created_by__avatar", "created_by__email") + .values("created_by__first_name", "created_by__last_name", "created_by__avatar", "created_by__display_name") .annotate(count=Count("id")) .order_by("-count") )[:5] most_issue_closed_user = ( queryset.filter(completed_at__isnull=False, assignees__isnull=False) - .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email") + .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__display_name") .annotate(count=Count("id")) .order_by("-count") )[:5] pending_issue_user = ( queryset.filter(completed_at__isnull=True) - .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email") + .values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__display_name") .annotate(count=Count("id")) .order_by("-count") ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 9b2b9ce5e06..172f3121997 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -397,7 +397,7 @@ class ProjectMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 64154500f11..a3a0c9dfccd 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -419,7 +419,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] @@ -519,7 +519,7 @@ class TeamMemberViewSet(BaseViewSet): ] search_fields = [ - "member__email", + "member__display_name", "member__first_name", ] diff --git a/apiserver/plane/bgtasks/analytic_plot_export.py b/apiserver/plane/bgtasks/analytic_plot_export.py index 7f276be82c7..f16a2d166cb 100644 --- a/apiserver/plane/bgtasks/analytic_plot_export.py +++ b/apiserver/plane/bgtasks/analytic_plot_export.py @@ -21,7 +21,7 @@ "state__name": "State", "state__group": "State Group", "labels__name": "Label", - "assignees__email": "Assignee Name", + "assignees__display_name": "Assignee Name", "start_date": "Start Date", "target_date": "Due Date", "completed_at": "Completed At", @@ -51,12 +51,12 @@ def analytic_export_task(email, data, slug): segmented = segment assignee_details = {} - if x_axis in ["assignees__email"] or segment in ["assignees__email"]: + if x_axis in ["assignees__display_name"] or segment in ["assignees__display_name"]: assignee_details = ( Issue.objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") .distinct("assignees__id") - .values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name") + .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name") ) if segment: @@ -93,17 +93,17 @@ def analytic_export_task(email, data, slug): else: generated_row.append("0") # x-axis replacement for names - if x_axis in ["assignees__email"]: - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)] + if x_axis in ["assignees__display_name"]: + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(item)] if len(assignee): generated_row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) rows.append(tuple(generated_row)) - # If segment is ["assignees__email"] then replace segment_zero rows with first and last names - if segmented in ["assignees__email"]: + # If segment is ["assignees__display_name"] then replace segment_zero rows with first and last names + if segmented in ["assignees__display_name"]: for index, segm in enumerate(row_zero[2:]): # find the name of the user - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(segm)] + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(segm)] if len(assignee): row_zero[index] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) @@ -141,8 +141,8 @@ def analytic_export_task(email, data, slug): else distribution.get(item)[0].get("estimate "), ] # x-axis replacement to names - if x_axis in ["assignees__email"]: - assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)] + if x_axis in ["assignees__display_name"]: + assignee = [user for user in assignee_details if str(user.get("assignees__display_name")) == str(item)] if len(assignee): row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name")) diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 210f2cff650..4ddcbc4c135 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -19,10 +19,13 @@ from slack_sdk import WebClient from slack_sdk.errors import SlackApiError -def generate_display_name(): - letters = string.ascii_letters - random_letters = ''.join(random.choice(letters) for _ in range(6)) - return random_letters +def generate_display_name(instance): + if not instance.first_name: + letters = string.ascii_letters + random_letters = ''.join(random.choice(letters) for _ in range(6)) + return random_letters + else: + return instance.first_name.lower() @@ -105,6 +108,9 @@ def save(self, *args, **kwargs): self.token = uuid.uuid4().hex + uuid.uuid4().hex self.token_updated_at = timezone.now() + if not self.display_name: + self.display_name = generate_display_name(self) + if self.is_superuser: self.is_staff = True From f43e8490d89e9b6c1d7ffb49a6b91636b8d5034b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 24 Jul 2023 16:39:35 +0530 Subject: [PATCH 04/16] dev: update display names on issue activity and the user script --- apiserver/bin/user_script.py | 7 +++---- apiserver/plane/bgtasks/issue_activites_task.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apiserver/bin/user_script.py b/apiserver/bin/user_script.py index b554d2c405e..b12f9fd4ae5 100644 --- a/apiserver/bin/user_script.py +++ b/apiserver/bin/user_script.py @@ -19,10 +19,9 @@ def populate(): user = User.objects.create(email=default_email, username=uuid.uuid4().hex) user.set_password(default_password) user.save() - print("User created") - - print("Success") - + print(f"User created with an email: {default_email}") + else: + print(f"User already exists with the default email: {default_email}") if __name__ == "__main__": populate() diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index ab97b0d331e..e1799fab5c2 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -567,7 +567,7 @@ def track_archive_at( issue_id=issue_id, project=project, workspace=project.workspace, - comment=f"{actor.email} has restored the issue", + comment=f"has restored the issue", verb="updated", actor=actor, field="archived_at", From d4268fafa74adda8a1c3b451d497ffcbfe7a4302 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 24 Jul 2023 16:47:58 +0530 Subject: [PATCH 05/16] dev: update display_name function to generate display_name from email --- apiserver/bin/user_script.py | 3 ++- apiserver/plane/db/models/user.py | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apiserver/bin/user_script.py b/apiserver/bin/user_script.py index b12f9fd4ae5..e115b20b8c0 100644 --- a/apiserver/bin/user_script.py +++ b/apiserver/bin/user_script.py @@ -1,4 +1,4 @@ -import os, sys +import os, sys, random, string import uuid sys.path.append("/code") @@ -23,5 +23,6 @@ def populate(): else: print(f"User already exists with the default email: {default_email}") + if __name__ == "__main__": populate() diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 8df3180806a..745a5df2f31 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -19,13 +19,13 @@ from slack_sdk import WebClient from slack_sdk.errors import SlackApiError + def generate_display_name(instance): - if not instance.first_name: - letters = string.ascii_letters - random_letters = ''.join(random.choice(letters) for _ in range(6)) - return random_letters - else: - return instance.first_name.lower() + return ( + instance.email.split("@")[0] + if len(instance.email.split("@")) + else "".join(random.choice(string.ascii_letters) for _ in range(6)) + ) def get_default_onboarding(): @@ -36,6 +36,7 @@ def get_default_onboarding(): "workspace_join": False, } + class User(AbstractBaseUser, PermissionsMixin): id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True From 676d385a537ed74ad0825f41f149ff224bd44af4 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 4 Aug 2023 14:18:37 +0530 Subject: [PATCH 06/16] dev: add email for test purpose --- apiserver/plane/api/serializers/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index dbcb028db06..2c32ec970d9 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -43,6 +43,7 @@ class Meta: "avatar", "is_bot", "display_name", + "email", ] read_only_fields = [ "id", From f17879a42e5df4557823713cd05deadb3de2cd3c Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 4 Aug 2023 14:40:44 +0530 Subject: [PATCH 07/16] dev: set default display name for the user --- apiserver/plane/db/models/user.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 2a29c5afcd8..6a852c534ab 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -20,14 +20,6 @@ from slack_sdk.errors import SlackApiError -def generate_display_name(instance): - return ( - instance.email.split("@")[0] - if len(instance.email.split("@")) - else "".join(random.choice(string.ascii_letters) for _ in range(6)) - ) - - def get_default_onboarding(): return { "profile_complete": False, @@ -92,7 +84,7 @@ class User(AbstractBaseUser, PermissionsMixin): role = models.CharField(max_length=300, null=True, blank=True) is_bot = models.BooleanField(default=False) theme = models.JSONField(default=dict) - display_name = models.CharField(max_length=255, default=generate_display_name) + display_name = models.CharField(max_length=255) is_tour_completed = models.BooleanField(default=False) onboarding_step = models.JSONField(default=get_default_onboarding) @@ -120,7 +112,11 @@ def save(self, *args, **kwargs): self.token_updated_at = timezone.now() if not self.display_name: - self.display_name = generate_display_name(self) + self.display_name = ( + self.email.split("@")[0] + if len(self.email.split("@")) + else "".join(random.choice(string.ascii_letters) for _ in range(6)) + ) if self.is_superuser: self.is_staff = True From 94efc07859e68e73981663ff8cf27f89a5636d6b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 4 Aug 2023 14:46:42 +0530 Subject: [PATCH 08/16] dev: add migration script and default value --- ..._alter_analyticview_created_by_and_more.py | 101 ++++++++++++++++++ apiserver/plane/db/models/user.py | 2 +- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py diff --git a/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py b/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py new file mode 100644 index 00000000000..0a561db5ead --- /dev/null +++ b/apiserver/plane/db/migrations/0041_user_display_name_alter_analyticview_created_by_and_more.py @@ -0,0 +1,101 @@ +# Generated by Django 4.2.3 on 2023-08-04 09:12 +import string +import random +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def generate_display_name(apps, schema_editor): + UserModel = apps.get_model("db", "User") + updated_users = [] + for obj in UserModel.objects.all(): + obj.display_name = ( + obj.email.split("@")[0] + if len(obj.email.split("@")) + else "".join(random.choice(string.ascii_letters) for _ in range(6)) + ) + updated_users.append(obj) + UserModel.objects.bulk_update(updated_users, ["display_name"], batch_size=100) + + +def rectify_field_issue_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + updated_activity = [] + for obj in Model.objects.filter(field="assignee"): + obj.field = "assignees" + updated_activity.append(obj) + + Model.objects.bulk_update(updated_activity, ["field"], batch_size=100) + + +def update_assignee_issue_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + updated_activity = [] + + # Get all the users + User = apps.get_model("db", "User") + users = User.objects.values("id", "email", "display_name") + + for obj in Model.objects.filter(field="assignees"): + if bool(obj.new_value) and not bool(obj.old_value): + # Get user from list + assigned_user = [ + user for user in users if user.get("email") == obj.new_value + ] + if assigned_user: + obj.new_value = assigned_user[0].get("display_name") + obj.new_identifier = assigned_user[0].get("id") + # Update the comment + words = obj.comment.split() + words[-1] = assigned_user[0].get("display_name") + obj.comment = " ".join(words) + + if bool(obj.old_value) and not bool(obj.new_value): + # Get user from list + assigned_user = [ + user for user in users if user.get("email") == obj.old_value + ] + if assigned_user: + obj.old_value = assigned_user[0].get("display_name") + obj.old_identifier = assigned_user[0].get("id") + # Update the comment + words = obj.comment.split() + words[-1] = assigned_user[0].get("display_name") + obj.comment = " ".join(words) + + updated_activity.append(obj) + + Model.objects.bulk_update( + updated_activity, + ["old_value", "new_value", "old_identifier", "new_identifier", "comment"], + batch_size=200, + ) + + +def update_name_activity(apps, schema_editor): + Model = apps.get_model("db", "IssueActivity") + update_activity = [] + for obj in Model.objects.filter(field="name"): + obj.comment = obj.comment.replace("start date", "name") + update_activity.append(obj) + + Model.objects.bulk_update(update_activity, ["comment"], batch_size=1000) + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0040_projectmember_preferences_user_cover_image_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="display_name", + field=models.CharField(default="", max_length=255), + ), + migrations.RunPython(generate_display_name), + migrations.RunPython(rectify_field_issue_activity), + migrations.RunPython(update_assignee_issue_activity), + migrations.RunPython(update_name_activity), + ] diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 6a852c534ab..3975a3b9311 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -84,7 +84,7 @@ class User(AbstractBaseUser, PermissionsMixin): role = models.CharField(max_length=300, null=True, blank=True) is_bot = models.BooleanField(default=False) theme = models.JSONField(default=dict) - display_name = models.CharField(max_length=255) + display_name = models.CharField(max_length=255, default="") is_tour_completed = models.BooleanField(default=False) onboarding_step = models.JSONField(default=get_default_onboarding) From 3c93a6d367d2cfad4164aaa237e659ad91717275 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 4 Aug 2023 18:40:22 +0530 Subject: [PATCH 09/16] dev: annotate with assignees_id --- apiserver/plane/api/views/analytic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/api/views/analytic.py index 7a9ee9146f0..91a7f9b15c0 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/api/views/analytic.py @@ -79,7 +79,7 @@ def get(self, request, slug): ) assignee_details = {} - if x_axis in ["assignees__display_name"] or segment in ["assignees__display_name"]: + if x_axis in ["assignees__id"] or segment in ["assignees__id"]: assignee_details = ( Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") From df385a579b93ca3180efd815a59669fafa9dc5c8 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 13:22:47 +0530 Subject: [PATCH 10/16] dev: return assignees id --- apiserver/plane/api/views/analytic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/api/views/analytic.py index 91a7f9b15c0..7d5786c19ef 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/api/views/analytic.py @@ -84,7 +84,7 @@ def get(self, request, slug): Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False) .order_by("assignees__id") .distinct("assignees__id") - .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name") + .values("assignees__avatar", "assignees__display_name", "assignees__first_name", "assignees__last_name", "assignees__id") ) From 518f49be9af6e91afa09fda49050e5332f98dc55 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 15:49:05 +0530 Subject: [PATCH 11/16] dev: display name for the profile --- apiserver/plane/api/views/workspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 1c894a95514..fbde75eb3f5 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -1282,6 +1282,7 @@ def get(self, request, slug, user_id): "cover_image": user_data.cover_image, "date_joined": user_data.date_joined, "user_timezone": user_data.user_timezone, + "display_name":user_data.display_name, }, }, status=status.HTTP_200_OK, From 329de64c304d7ffbec2bd450b36b766141cd5346 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 18:18:44 +0530 Subject: [PATCH 12/16] dev: project members endpoint --- apiserver/plane/api/serializers/__init__.py | 3 +- apiserver/plane/api/serializers/project.py | 13 +++++- apiserver/plane/api/serializers/user.py | 18 ++++++++ apiserver/plane/api/serializers/workspace.py | 35 +++++++++------ apiserver/plane/api/urls.py | 12 ++++++ apiserver/plane/api/views/__init__.py | 2 + apiserver/plane/api/views/project.py | 22 +++++++++- apiserver/plane/api/views/workspace.py | 45 +++++++++++++++----- 8 files changed, 124 insertions(+), 26 deletions(-) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index c912093b0e6..683ed9670db 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -1,5 +1,5 @@ from .base import BaseSerializer -from .user import UserSerializer, UserLiteSerializer, ChangePasswordSerializer, ResetPasswordSerializer +from .user import UserSerializer, UserLiteSerializer, ChangePasswordSerializer, ResetPasswordSerializer, UserAdminLiteSerializer from .workspace import ( WorkSpaceSerializer, WorkSpaceMemberSerializer, @@ -7,6 +7,7 @@ WorkSpaceMemberInviteSerializer, WorkspaceLiteSerializer, WorkspaceThemeSerializer, + WorkspaceMemberAdminSerializer, ) from .project import ( ProjectSerializer, diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index fa97c5a6ddf..643518daa10 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -7,7 +7,7 @@ # Module imports from .base import BaseSerializer from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer -from plane.api.serializers.user import UserLiteSerializer +from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( Project, ProjectMember, @@ -110,6 +110,17 @@ class Meta: fields = "__all__" +class ProjectMemberAdminSerializer(BaseSerializer): + workspace = WorkspaceLiteSerializer(read_only=True) + project = ProjectLiteSerializer(read_only=True) + member = UserAdminLiteSerializer(read_only=True) + + + class Meta: + model = ProjectMember + fields = "__all__" + + class ProjectMemberInviteSerializer(BaseSerializer): project = ProjectLiteSerializer(read_only=True) workspace = WorkspaceLiteSerializer(read_only=True) diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index 2c32ec970d9..dcb00c6cbfe 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -34,6 +34,24 @@ def get_is_onboarded(self, obj): class UserLiteSerializer(BaseSerializer): + class Meta: + model = User + fields = [ + "id", + "first_name", + "last_name", + "avatar", + "is_bot", + "display_name", + ] + read_only_fields = [ + "id", + "is_bot", + ] + + +class UserAdminLiteSerializer(BaseSerializer): + class Meta: model = User fields = [ diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/api/serializers/workspace.py index 4d83d6262a9..5fa9a346775 100644 --- a/apiserver/plane/api/serializers/workspace.py +++ b/apiserver/plane/api/serializers/workspace.py @@ -3,7 +3,7 @@ # Module imports from .base import BaseSerializer -from .user import UserLiteSerializer +from .user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( User, @@ -33,10 +33,30 @@ class Meta: "owner", ] +class WorkspaceLiteSerializer(BaseSerializer): + class Meta: + model = Workspace + fields = [ + "name", + "slug", + "id", + ] + read_only_fields = fields + + class WorkSpaceMemberSerializer(BaseSerializer): member = UserLiteSerializer(read_only=True) - workspace = WorkSpaceSerializer(read_only=True) + workspace = WorkspaceLiteSerializer(read_only=True) + + class Meta: + model = WorkspaceMember + fields = "__all__" + + +class WorkspaceMemberAdminSerializer(BaseSerializer): + member = UserAdminLiteSerializer + workspace = WorkspaceLiteSerializer class Meta: model = WorkspaceMember @@ -101,17 +121,6 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) -class WorkspaceLiteSerializer(BaseSerializer): - class Meta: - model = Workspace - fields = [ - "name", - "slug", - "id", - ] - read_only_fields = fields - - class WorkspaceThemeSerializer(BaseSerializer): class Meta: model = WorkspaceTheme diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index c8b5e7b5eff..1396d992f09 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -32,6 +32,7 @@ InviteWorkspaceEndpoint, JoinWorkspaceEndpoint, WorkSpaceMemberViewSet, + WorkspaceMembersEndpoint, WorkspaceInvitationsViewset, UserWorkspaceInvitationsEndpoint, WorkspaceMemberUserEndpoint, @@ -59,6 +60,7 @@ ProjectViewSet, InviteProjectEndpoint, ProjectMemberViewSet, + ProjectMemberEndpoint, ProjectMemberInvitationsViewset, ProjectMemberUserEndpoint, AddMemberToProjectEndpoint, @@ -334,6 +336,11 @@ ), name="workspace", ), + path( + "workspaces//workspace-members", + WorkspaceMembersEndpoint.as_view(), + name="workspace-members", + ), path( "workspaces//teams/", TeamMemberViewSet.as_view( @@ -467,6 +474,11 @@ ), name="project", ), + path( + "workspaces//projects//project-members/", + ProjectMemberEndpoint.as_view({"get": "list"}), + name="project", + ), path( "workspaces//projects//members/add/", AddMemberToProjectEndpoint.as_view(), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 6aaccad634d..47309baabf6 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -12,6 +12,7 @@ ProjectUserViewsEndpoint, ProjectMemberUserEndpoint, ProjectFavoritesViewSet, + ProjectMemberEndpoint, ) from .user import ( UserEndpoint, @@ -47,6 +48,7 @@ WorkspaceUserProfileEndpoint, WorkspaceUserProfileIssuesEndpoint, WorkspaceLabelsEndpoint, + WorkspaceMembersEndpoint, ) from .state import StateViewSet from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 6aa65a3a8d3..98484f74bff 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -25,7 +25,7 @@ ProjectFavoriteSerializer, ) -from plane.api.permissions import ProjectBasePermission +from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission from plane.db.models import ( Project, @@ -984,3 +984,23 @@ def destroy(self, request, slug, project_id): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class ProjectMemberEndpoint(BaseAPIView): + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id): + try: + project_members = ProjectMember.objects.filter( + project_id=project_id, workspace__slug=slug + ).select_related("project", "member") + serializer = ProjectMemberSerializer(project_members, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index fbde75eb3f5..3957bcae0a5 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -47,6 +47,7 @@ WorkspaceThemeSerializer, IssueActivitySerializer, IssueLiteSerializer, + WorkspaceMemberAdminSerializer ) from plane.api.views.base import BaseAPIView from . import BaseViewSet @@ -537,7 +538,7 @@ def create(self, request): class WorkSpaceMemberViewSet(BaseViewSet): - serializer_class = WorkSpaceMemberSerializer + serializer_class = WorkspaceMemberAdminSerializer model = WorkspaceMember permission_classes = [ @@ -1048,7 +1049,6 @@ def create(self, request, slug): class WorkspaceUserProfileStatsEndpoint(BaseAPIView): - def get(self, request, slug, user_id): try: filters = issue_filters(request.query_params, "GET") @@ -1146,14 +1146,18 @@ def get(self, request, slug, user_id): upcoming_cycles = CycleIssue.objects.filter( workspace__slug=slug, cycle__start_date__gt=timezone.now().date(), - issue__assignees__in=[user_id,] + issue__assignees__in=[ + user_id, + ], ).values("cycle__name", "cycle__id", "cycle__project_id") present_cycle = CycleIssue.objects.filter( workspace__slug=slug, cycle__start_date__lt=timezone.now().date(), cycle__end_date__gt=timezone.now().date(), - issue__assignees__in=[user_id,] + issue__assignees__in=[ + user_id, + ], ).values("cycle__name", "cycle__id", "cycle__project_id") return Response( @@ -1166,7 +1170,7 @@ def get(self, request, slug, user_id): "pending_issues": pending_issues_count, "subscribed_issues": subscribed_issues_count, "present_cycles": present_cycle, - "upcoming_cycles": upcoming_cycles, + "upcoming_cycles": upcoming_cycles, } ) except Exception as e: @@ -1184,7 +1188,6 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): def get(self, request, slug, user_id): try: - projects = request.query_params.getlist("project", []) queryset = IssueActivity.objects.filter( @@ -1212,12 +1215,13 @@ def get(self, request, slug, user_id): class WorkspaceUserProfileEndpoint(BaseAPIView): - def get(self, request, slug, user_id): try: user_data = User.objects.get(pk=user_id) - requesting_workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user) + requesting_workspace_member = WorkspaceMember.objects.get( + workspace__slug=slug, member=request.user + ) projects = [] if requesting_workspace_member.role >= 10: projects = ( @@ -1227,7 +1231,8 @@ def get(self, request, slug, user_id): ) .annotate( created_issues=Count( - "project_issue", filter=Q(project_issue__created_by_id=user_id) + "project_issue", + filter=Q(project_issue__created_by_id=user_id), ) ) .annotate( @@ -1282,7 +1287,7 @@ def get(self, request, slug, user_id): "cover_image": user_data.cover_image, "date_joined": user_data.date_joined, "user_timezone": user_data.user_timezone, - "display_name":user_data.display_name, + "display_name": user_data.display_name, }, }, status=status.HTTP_200_OK, @@ -1440,3 +1445,23 @@ def get(self, request, slug): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class WorkspaceMembersEndpoint(BaseAPIView): + permission_classes = [ + WorkspaceEntityPermission, + ] + + def get(self, request, slug): + try: + workspace_members = WorkspaceMember.objects.filter( + workspace__slug=slug + ).select_related("workspace", "member") + serialzier = WorkSpaceMemberSerializer(workspace_members, many=True) + return Response(serialzier.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) From e9162c7f1394bd09e2925a2ff0c7fed005e5568e Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 18:19:47 +0530 Subject: [PATCH 13/16] dev: url update --- apiserver/plane/api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 1396d992f09..d7ef0214fa1 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -476,7 +476,7 @@ ), path( "workspaces//projects//project-members/", - ProjectMemberEndpoint.as_view({"get": "list"}), + ProjectMemberEndpoint.as_view(), name="project", ), path( From 21ea89b43af8782ba9991c4ebe4edc2a42d63e70 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 18:21:53 +0530 Subject: [PATCH 14/16] dev: trailing / --- apiserver/plane/api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index d7ef0214fa1..39cc8a5cb22 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -337,7 +337,7 @@ name="workspace", ), path( - "workspaces//workspace-members", + "workspaces//workspace-members/", WorkspaceMembersEndpoint.as_view(), name="workspace-members", ), From fa3c49c173b9af5a9f1079ed04c76677cbaf95a3 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 19:33:20 +0530 Subject: [PATCH 15/16] dev: update workspace member serializer --- apiserver/plane/api/serializers/workspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/api/serializers/workspace.py index 5fa9a346775..d27b66481c0 100644 --- a/apiserver/plane/api/serializers/workspace.py +++ b/apiserver/plane/api/serializers/workspace.py @@ -55,8 +55,8 @@ class Meta: class WorkspaceMemberAdminSerializer(BaseSerializer): - member = UserAdminLiteSerializer - workspace = WorkspaceLiteSerializer + member = UserAdminLiteSerializer(read_only=True) + workspace = WorkspaceLiteSerializer(read_only=True) class Meta: model = WorkspaceMember From 3682cb59e044561131af025e443820c9838a1225 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 7 Aug 2023 20:33:05 +0530 Subject: [PATCH 16/16] fix: activity for assignees --- apiserver/plane/bgtasks/issue_activites_task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 66d9f35132f..9150d7c9478 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -358,7 +358,7 @@ def track_assignees( project=project, workspace=project.workspace, comment=f"added assignee {assignee.display_name}", - new_identifier=assignee, + new_identifier=assignee.id, ) ) @@ -380,7 +380,7 @@ def track_assignees( project=project, workspace=project.workspace, comment=f"removed assignee {assignee.display_name}", - old_identifier=assignee, + old_identifier=assignee.id, ) )