chore: workspace events (#8439)
* chore: adding invite and joined events * chore: adding workspace create and update events
This commit is contained in:
parent
d09c91b838
commit
777200db7b
6 changed files with 137 additions and 51 deletions
|
|
@ -78,6 +78,7 @@ class UserMeSerializer(BaseSerializer):
|
|||
"is_password_autoset",
|
||||
"is_email_verified",
|
||||
"last_login_medium",
|
||||
"last_login_time",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ from plane.app.permissions import ROLE, allow_permission
|
|||
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
from plane.bgtasks.workspace_seed_task import workspace_seed
|
||||
from plane.bgtasks.event_tracking_task import track_event
|
||||
from plane.utils.url import contains_url
|
||||
from plane.utils.analytics_events import WORKSPACE_CREATED, WORKSPACE_DELETED
|
||||
|
||||
|
||||
class WorkSpaceViewSet(BaseViewSet):
|
||||
|
|
@ -131,6 +133,20 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||
|
||||
workspace_seed.delay(serializer.data["id"])
|
||||
|
||||
track_event.delay(
|
||||
user_id=request.user.id,
|
||||
event_name=WORKSPACE_CREATED,
|
||||
slug=data["slug"],
|
||||
event_properties={
|
||||
"user_id": request.user.id,
|
||||
"workspace_id": data["id"],
|
||||
"workspace_slug": data["slug"],
|
||||
"role": "owner",
|
||||
"workspace_name": data["name"],
|
||||
"created_at": data["created_at"],
|
||||
},
|
||||
)
|
||||
|
||||
return Response(data, status=status.HTTP_201_CREATED)
|
||||
return Response(
|
||||
[serializer.errors[error][0] for error in serializer.errors],
|
||||
|
|
@ -164,6 +180,19 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||
# Get the workspace
|
||||
workspace = self.get_object()
|
||||
self.remove_last_workspace_ids_from_user_settings(workspace.id)
|
||||
track_event.delay(
|
||||
user_id=request.user.id,
|
||||
event_name=WORKSPACE_DELETED,
|
||||
slug=workspace.slug,
|
||||
event_properties={
|
||||
"user_id": request.user.id,
|
||||
"workspace_id": workspace.id,
|
||||
"workspace_slug": workspace.slug,
|
||||
"role": "owner",
|
||||
"workspace_name": workspace.name,
|
||||
"deleted_at": str(timezone.now().isoformat()),
|
||||
},
|
||||
)
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@ from plane.app.serializers import (
|
|||
WorkSpaceMemberSerializer,
|
||||
)
|
||||
from plane.app.views.base import BaseAPIView
|
||||
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
||||
from plane.bgtasks.event_tracking_task import track_event
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
from plane.db.models import User, Workspace, WorkspaceMember, WorkspaceMemberInvite
|
||||
from plane.utils.cache import invalidate_cache, invalidate_cache_directly
|
||||
from plane.utils.host import base_host
|
||||
from plane.utils.ip_address import get_client_ip
|
||||
from plane.utils.analytics_events import USER_JOINED_WORKSPACE, USER_INVITED_TO_WORKSPACE
|
||||
from .. import BaseViewSet
|
||||
|
||||
|
||||
|
|
@ -121,6 +122,19 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||
current_site,
|
||||
request.user.email,
|
||||
)
|
||||
track_event.delay(
|
||||
user_id=request.user.id,
|
||||
event_name=USER_INVITED_TO_WORKSPACE,
|
||||
slug=slug,
|
||||
event_properties={
|
||||
"user_id": request.user.id,
|
||||
"workspace_id": workspace.id,
|
||||
"workspace_slug": workspace.slug,
|
||||
"invitee_role": invitation.role,
|
||||
"invited_at": str(timezone.now()),
|
||||
"invitee_email": invitation.email,
|
||||
},
|
||||
)
|
||||
|
||||
return Response({"message": "Emails sent successfully"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
@ -186,20 +200,22 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||
# Set the user last_workspace_id to the accepted workspace
|
||||
user.last_workspace_id = workspace_invite.workspace.id
|
||||
user.save()
|
||||
track_event.delay(
|
||||
user_id=user.id,
|
||||
event_name=USER_JOINED_WORKSPACE,
|
||||
slug=slug,
|
||||
event_properties={
|
||||
"user_id": user.id,
|
||||
"workspace_id": workspace_invite.workspace.id,
|
||||
"workspace_slug": workspace_invite.workspace.slug,
|
||||
"role": workspace_invite.role,
|
||||
"joined_at": str(timezone.now()),
|
||||
},
|
||||
)
|
||||
|
||||
# Delete the invitation
|
||||
workspace_invite.delete()
|
||||
|
||||
# Send event
|
||||
workspace_invite_event.delay(
|
||||
user=user.id if user is not None else None,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=get_client_ip(request=request),
|
||||
event_name="MEMBER_ACCEPTED",
|
||||
accepted_from="EMAIL",
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Workspace Invitation Accepted"},
|
||||
status=status.HTTP_200_OK,
|
||||
|
|
@ -252,6 +268,20 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
|
|||
is_active=True, role=invitation.role
|
||||
)
|
||||
|
||||
# Track event
|
||||
track_event.delay(
|
||||
user_id=request.user.id,
|
||||
event_name=USER_JOINED_WORKSPACE,
|
||||
slug=invitation.workspace.slug,
|
||||
event_properties={
|
||||
"user_id": request.user.id,
|
||||
"workspace_id": invitation.workspace.id,
|
||||
"workspace_slug": invitation.workspace.slug,
|
||||
"role": invitation.role,
|
||||
"joined_at": str(timezone.now()),
|
||||
},
|
||||
)
|
||||
|
||||
# Bulk create the user for all the workspaces
|
||||
WorkspaceMember.objects.bulk_create(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
ProjectMember,
|
||||
ProjectMemberInvite,
|
||||
|
|
@ -5,6 +9,8 @@ from plane.db.models import (
|
|||
WorkspaceMemberInvite,
|
||||
)
|
||||
from plane.utils.cache import invalidate_cache_directly
|
||||
from plane.bgtasks.event_tracking_task import track_event
|
||||
from plane.utils.analytics_events import USER_JOINED_WORKSPACE
|
||||
|
||||
|
||||
def process_workspace_project_invitations(user):
|
||||
|
|
@ -25,15 +31,25 @@ def process_workspace_project_invitations(user):
|
|||
ignore_conflicts=True,
|
||||
)
|
||||
|
||||
[
|
||||
for workspace_member_invite in workspace_member_invites:
|
||||
invalidate_cache_directly(
|
||||
path=f"/api/workspaces/{str(workspace_member_invite.workspace.slug)}/members/",
|
||||
url_params=False,
|
||||
user=False,
|
||||
multiple=True,
|
||||
)
|
||||
for workspace_member_invite in workspace_member_invites
|
||||
]
|
||||
track_event.delay(
|
||||
user_id=user.id,
|
||||
event_name=USER_JOINED_WORKSPACE,
|
||||
slug=workspace_member_invite.workspace.slug,
|
||||
event_properties={
|
||||
"user_id": user.id,
|
||||
"workspace_id": workspace_member_invite.workspace.id,
|
||||
"workspace_slug": workspace_member_invite.workspace.slug,
|
||||
"role": workspace_member_invite.role,
|
||||
"joined_at": str(timezone.now().isoformat()),
|
||||
},
|
||||
)
|
||||
|
||||
# Check if user has any project invites
|
||||
project_member_invites = ProjectMemberInvite.objects.filter(email=user.email, accepted=True)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from typing import Dict, Any
|
||||
|
||||
# third party imports
|
||||
from celery import shared_task
|
||||
|
|
@ -8,6 +10,11 @@ from posthog import Posthog
|
|||
# module imports
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.db.models import Workspace
|
||||
from plane.utils.analytics_events import USER_INVITED_TO_WORKSPACE, WORKSPACE_DELETED
|
||||
|
||||
|
||||
logger = logging.getLogger("plane.worker")
|
||||
|
||||
|
||||
def posthogConfiguration():
|
||||
|
|
@ -17,7 +24,10 @@ def posthogConfiguration():
|
|||
"key": "POSTHOG_API_KEY",
|
||||
"default": os.environ.get("POSTHOG_API_KEY", None),
|
||||
},
|
||||
{"key": "POSTHOG_HOST", "default": os.environ.get("POSTHOG_HOST", None)},
|
||||
{
|
||||
"key": "POSTHOG_HOST",
|
||||
"default": os.environ.get("POSTHOG_HOST", None),
|
||||
},
|
||||
]
|
||||
)
|
||||
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||
|
|
@ -26,46 +36,42 @@ def posthogConfiguration():
|
|||
return None, None
|
||||
|
||||
|
||||
@shared_task
|
||||
def auth_events(user, email, user_agent, ip, event_name, medium, first_time):
|
||||
try:
|
||||
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
|
||||
def preprocess_data_properties(
|
||||
user_id: uuid.UUID, event_name: str, slug: str, data_properties: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
if event_name == USER_INVITED_TO_WORKSPACE or event_name == WORKSPACE_DELETED:
|
||||
try:
|
||||
# Check if the current user is the workspace owner
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
if str(workspace.owner_id) == str(user_id):
|
||||
data_properties["role"] = "owner"
|
||||
else:
|
||||
data_properties["role"] = "admin"
|
||||
except Workspace.DoesNotExist:
|
||||
logger.warning(f"Workspace {slug} does not exist while sending event {event_name} for user {user_id}")
|
||||
data_properties["role"] = "unknown"
|
||||
|
||||
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||
posthog = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST)
|
||||
posthog.capture(
|
||||
email,
|
||||
event=event_name,
|
||||
properties={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"user": {"email": email, "id": str(user)},
|
||||
"device_ctx": {"ip": ip, "user_agent": user_agent},
|
||||
"medium": medium,
|
||||
"first_time": first_time,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
return
|
||||
return data_properties
|
||||
|
||||
|
||||
@shared_task
|
||||
def workspace_invite_event(user, email, user_agent, ip, event_name, accepted_from):
|
||||
try:
|
||||
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
|
||||
def track_event(user_id: uuid.UUID, event_name: str, slug: str, event_properties: Dict[str, Any]):
|
||||
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
|
||||
|
||||
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||
posthog = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST)
|
||||
posthog.capture(
|
||||
email,
|
||||
event=event_name,
|
||||
properties={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"user": {"email": email, "id": str(user)},
|
||||
"device_ctx": {"ip": ip, "user_agent": user_agent},
|
||||
"accepted_from": accepted_from,
|
||||
},
|
||||
)
|
||||
if not (POSTHOG_API_KEY and POSTHOG_HOST):
|
||||
logger.warning("Event tracking is not configured")
|
||||
return
|
||||
|
||||
try:
|
||||
# preprocess the data properties for massaging the payload
|
||||
# in the correct format for posthog
|
||||
data_properties = preprocess_data_properties(user_id, event_name, slug, event_properties)
|
||||
groups = {
|
||||
"workspace": slug,
|
||||
}
|
||||
# track the event using posthog
|
||||
posthog = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST)
|
||||
posthog.capture(distinct_id=str(user_id), event=event_name, properties=data_properties, groups=groups)
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
return
|
||||
return False
|
||||
|
|
|
|||
4
apps/api/plane/utils/analytics_events.py
Normal file
4
apps/api/plane/utils/analytics_events.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
USER_JOINED_WORKSPACE = "user_joined_workspace"
|
||||
USER_INVITED_TO_WORKSPACE = "user_invited_to_workspace"
|
||||
WORKSPACE_CREATED = "workspace_created"
|
||||
WORKSPACE_DELETED = "workspace_deleted"
|
||||
Loading…
Add table
Add a link
Reference in a new issue