Compare commits

...
Author SHA1 Message Date
Jayash Tripathy b4c40bb928 fix: ensure quick add functionality correctly awaits promise resolution and streamline role tracking in workspace creation 2025-12-22 15:10:31 +05:30
Jayash Tripathy 889c173b99 fix: ensure quick add functionality correctly awaits promise resolution and streamline role tracking in workspace creation 2025-12-22 14:16:58 +05:30
Jayash Tripathy af33860a55 refactor: improve user role handling in event tracking functions 2025-12-22 14:06:05 +05:30
Jayash Tripathy 91b421d6d6 refactor: role extraction 2025-12-22 13:15:50 +05:30
Jayash Tripathy 5e2f83f95c fix: update event tracking to use created_at from response in pages list component 2025-12-16 12:55:05 +05:30
Jayash Tripathy 1f89fcdafa fix: update event tracking to use created_at from response in cycle and issue creation, and reorganize imports in workspace components 2025-12-16 12:41:58 +05:30
Jayash Tripathy 9941459c6d refactor: reorganize imports and enhance error handling in project and page creation components 2025-12-12 18:18:19 +05:30
pablohashescobar a64bab77e5 feat: replace invitation event with user join tracking for workspace invitations 2025-12-12 17:50:17 +05:30
pablohashescobar f14bf3b60e feat: enhance event tracking for workspace invitations and joins 2025-12-11 17:28:26 +05:30
pablohashescobar 6e2f0f1601 Merge branch 'chore-event-updates' of github.com:makeplane/plane into chore-event-updates 2025-12-11 17:01:42 +05:30
pablohashescobar f8f07a6b3e chore: add tracking events through posthog 2025-12-11 17:01:20 +05:30
Jayash Tripathy dd0ac58db9 refactor: simplify joinWorkspaceGroup by removing extraProperties parameter and update event tracking in workspace creation 2025-12-10 16:24:42 +05:30
Jayash Tripathy 031fb89d6b refactor: simplify joinWorkspaceGroup by removing extraProperties parameter and update event tracking in workspace creation 2025-12-10 16:18:46 +05:30
pablohashescobar 96ccde551b Merge branch 'chore-event-updates' of github.com:makeplane/plane into chore-event-updates 2025-12-10 15:14:55 +05:30
Jayash Tripathy f958aada3c feat: update joinWorkspaceGroup to utilize workspace data from usePosthogWorkspace 2025-12-10 15:02:12 +05:30
Jayash Tripathy f9d42dfd00 feat: extend joinWorkspaceGroup to accept additional properties 2025-12-10 14:19:31 +05:30
pablohashescobar 72dd432d88 Merge branch 'preview' of github.com:makeplane/plane into chore-event-updates 2025-12-10 13:34:30 +05:30
Jayash Tripathy 54c32f694d Merge branch 'preview' of https://github.com/makeplane/plane into chore-event-updates 2025-12-10 13:33:34 +05:30
Jayash Tripathy 4d22ef21f1 feat: integrate enhanced new set of event trackers 2025-12-10 13:32:43 +05:30
pablohashescobar 6f64e07eb4 feat: add last_login_time field to UserMeSerializer for enhanced user tracking 2025-12-09 18:16:20 +05:30
sriramveeraghanta 8bbf853feb fix: update events identity 2025-12-08 21:21:28 +05:30
21 changed files with 686 additions and 227 deletions
+1
View File
@@ -78,6 +78,7 @@ class UserMeSerializer(BaseSerializer):
"is_password_autoset",
"is_email_verified",
"last_login_medium",
"last_login_time",
]
read_only_fields = fields
+40 -11
View File
@@ -21,7 +21,7 @@ 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 workspace_invite_event, 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
@@ -121,6 +121,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 +199,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 +267,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,7 @@ from plane.db.models import (
WorkspaceMemberInvite,
)
from plane.utils.cache import invalidate_cache_directly
from plane.bgtasks.event_tracking_task import track_event
def process_workspace_project_invitations(user):
@@ -25,6 +30,22 @@ def process_workspace_project_invitations(user):
ignore_conflicts=True,
)
[
track_event.delay(
user_id=user.id,
event_name="user_joined_workspaces",
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()),
},
)
for workspace_member_invite in workspace_member_invites
]
[
invalidate_cache_directly(
path=f"/api/workspaces/{str(workspace_member_invite.workspace.slug)}/members/",
+45 -27
View File
@@ -1,5 +1,7 @@
import logging
import os
import uuid
from typing import Dict, Any
# third party imports
from celery import shared_task
@@ -8,45 +10,61 @@ 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
logger = logging.getLogger("plane.worker")
def posthogConfiguration():
POSTHOG_API_KEY, POSTHOG_HOST = get_configuration_value(
[
{
"key": "POSTHOG_API_KEY",
"default": os.environ.get("POSTHOG_API_KEY", None),
},
{"key": "POSTHOG_HOST", "default": os.environ.get("POSTHOG_HOST", None)},
]
)
POSTHOG_API_KEY, POSTHOG_HOST = get_configuration_value([
{
"key": "POSTHOG_API_KEY",
"default": os.environ.get("POSTHOG_API_KEY", None),
},
{"key": "POSTHOG_HOST", "default": os.environ.get("POSTHOG_HOST", None)},
])
if POSTHOG_API_KEY and POSTHOG_HOST:
return POSTHOG_API_KEY, POSTHOG_HOST
else:
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":
# 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"
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,
},
)
return data_properties
@shared_task
def track_event(user_id: uuid.UUID, event_name: str, slug: str, event_properties: Dict[str, Any]):
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
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=user_id, event=event_name, properties=data_properties, groups=groups)
except Exception as e:
log_exception(e)
return
return False
@shared_task
@@ -1,6 +1,6 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
// constants
import { EPageAccess, PROJECT_PAGE_TRACKER_EVENTS, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
// plane types
@@ -12,11 +12,14 @@ import type { TPage } from "@plane/types";
import { Breadcrumbs, Header } from "@plane/ui";
// helpers
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
// hooks
import { useProject } from "@/hooks/store/use-project";
// plane web imports
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
import { trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
export const PagesListHeader = observer(function PagesListHeader() {
@@ -28,42 +31,54 @@ export const PagesListHeader = observer(function PagesListHeader() {
const searchParams = useSearchParams();
const pageType = searchParams.get("type");
// store hooks
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { data: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const { currentProjectDetails, loader } = useProject();
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
// handle page create
const handleCreatePage = async () => {
setIsCreatingPage(true);
try {
setIsCreatingPage(true);
const payload: Partial<TPage> = {
access: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
};
const payload: Partial<TPage> = {
access: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
};
await createPage(payload)
.then((res) => {
captureSuccess({
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
payload: {
id: res?.id,
state: "SUCCESS",
const pageData = await createPage(payload);
if (!pageData?.id) throw new Error("Invalid response");
if (currentWorkspace && currentUser) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackPageCreated(
{
id: pageData.id,
project_id: projectId,
created_at: pageData.created_at ?? "",
},
});
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
router.push(pageId);
})
.catch((err) => {
captureError({
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
payload: {
state: "ERROR",
},
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.data?.error || "Page could not be created. Please try again.",
});
})
.finally(() => setIsCreatingPage(false));
currentWorkspace,
currentUser,
"project",
role
);
}
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${pageData.id}`;
router.push(pageId);
} catch (err: any) {
captureError({
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
payload: {
state: "ERROR",
error: err?.data?.error,
},
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.data?.error || "Page could not be created. Please try again.",
});
} finally {
setIsCreatingPage(false);
}
};
return (
+4 -4
View File
@@ -29,6 +29,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web services
import { WorkspaceService } from "@/plane-web/services";
import { joinWorkspaceGroup } from "@/plane-web/helpers/event-tracker-v2.helper";
const workspaceService = new WorkspaceService();
@@ -80,10 +81,9 @@ function UserInvitationsPage() {
const invitation = invitations?.find((i) => i.id === firstInviteId);
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
if (redirectWorkspace?.id) {
joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, redirectWorkspace?.id, {
date: new Date().toDateString(),
workspace_id: redirectWorkspace?.id,
});
if (redirectWorkspace) {
joinWorkspaceGroup(redirectWorkspace);
}
}
captureSuccess({
eventName: MEMBER_TRACKER_EVENTS.accept,
+18 -13
View File
@@ -1,22 +1,24 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { FormProvider, useForm } from "react-hook-form";
import { PROJECT_TRACKER_EVENTS, RANDOM_EMOJI_CODES } from "@plane/constants";
import { PROJECT_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { observer } from "mobx-react";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
// ui
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { EFileAssetType } from "@plane/types";
import type { IProject } from "@plane/types";
// constants
import ProjectCommonAttributes from "@/components/project/create/common-attributes";
import ProjectCreateHeader from "@/components/project/create/header";
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
// hooks
import { DEFAULT_COVER_IMAGE_URL, getCoverImageType, uploadCoverImage } from "@/helpers/cover-image.helper";
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { getCoverImageType, uploadCoverImage } from "@/helpers/cover-image.helper";
import { captureError } from "@/helpers/event-tracker.helper";
import { useProject } from "@/hooks/store/use-project";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web types
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackProjectCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import type { TProject } from "@/plane-web/types/projects";
import ProjectAttributes from "./attributes";
import { getProjectFormValues } from "./utils";
@@ -36,6 +38,9 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
// store
const { t } = useTranslation();
const { addProjectToFavorites, createProject, updateProject } = useProject();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { data: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
// states
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
// form info
@@ -98,12 +103,10 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
await updateCoverImageStatus(res.id, coverImage);
await updateProject(workspaceSlug.toString(), res.id, { cover_image_url: coverImage });
}
captureSuccess({
eventName: PROJECT_TRACKER_EVENTS.create,
payload: {
identifier: formData.identifier,
},
});
if (currentUser && currentWorkspace && res) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackProjectCreated({ id: res.id, created_at: res.created_at ?? "" }, currentWorkspace, currentUser, role);
}
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("success"),
@@ -114,6 +117,8 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
handleAddToFavorites(res.id);
}
handleNextStep(res.id);
return res;
})
.catch((err) => {
try {
@@ -0,0 +1,280 @@
import { posthog } from "posthog-js";
import { EUserPermissions } from "@plane/types";
import type { EUserProjectRoles, EUserWorkspaceRoles, IUser, IWorkspace, TUserProfile } from "@plane/types";
type TUserRole = "guest" | "member" | "admin" | "unknown";
/**
* ============================================================================
* Utilities
* ============================================================================
*/
/**
* Get the user role string from the user role enum
* @param role - The user role enum
* @returns The user role string
*/
const getUserRoleString = (role: EUserPermissions | EUserWorkspaceRoles | EUserProjectRoles | undefined): TUserRole => {
if (!role) return "unknown";
switch (role) {
case EUserPermissions.GUEST:
return "guest";
case EUserPermissions.MEMBER:
return "member";
case EUserPermissions.ADMIN:
return "admin";
default:
return "unknown";
}
};
/**
* ============================================================================
* USER IDENTIFICATION
* ============================================================================
*/
/**
* Identify a user in PostHog with all required person properties
* Call this after signup, login, or whenever session becomes authenticated
*
* @param user - User object from the store
* @param profile - Optional user profile object (for onboarding status, role, use_case)
*/
export const identifyUser = (user: IUser, profile?: TUserProfile) => {
if (!posthog || !user) return;
posthog.identify(user.id, {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
display_name: user.display_name,
date_joined: user.date_joined,
last_login_medium: user.last_login_medium || "EMAIL",
timezone: user.user_timezone,
is_email_verified: user.is_email_verified,
is_onboarded: profile?.is_onboarded || false,
role: profile?.role || null,
use_case: profile?.use_case || null,
last_workspace_id: user.last_workspace_id || null,
language: profile?.language || null,
last_login_time: user.last_login_time || null,
});
};
/**
* ============================================================================
* WORKSPACE GROUP TRACKING
* ============================================================================
*/
/**
* Join workspace group properties in PostHog
* Call this whenever a user views a workspace (e.g., on workspace switch)
*
* @param workspace - Workspace object
*/
export const joinWorkspaceGroup = (workspace: Partial<IWorkspace>) => {
if (!posthog || !workspace.slug) return;
posthog.group("workspace", workspace.slug, {
workspace_id: workspace.id,
workspace_name: workspace.name,
workspace_slug: workspace.slug,
workspace_size: workspace.organization_size,
created_at: workspace.created_at instanceof Date ? workspace.created_at.toISOString() : workspace.created_at,
owner_user_id: workspace.owner?.id || workspace.created_by,
is_deleted: false,
deleted_at: null,
});
};
/**
* ============================================================================
* GENERIC EVENT TRACKING
* ============================================================================
*/
/**
* Generic event tracking function with workspace context
* All workspace events must include workspace_id, role, and groups
*
* @param eventName - Event name in snake_case (e.g., "workspace_created")
* @param properties - Event-specific properties
* @param workspaceSlug - Workspace slug for group association
* @param role - User's role in the workspace
*/
export const trackEvent = (eventName: string, properties: Record<string, unknown>, role: TUserRole) => {
if (!posthog) return;
const eventProperties = {
...properties,
role: role || "unknown",
};
posthog.capture(eventName, eventProperties);
};
/**
* ============================================================================
* LIFECYCLE EVENTS
* ============================================================================
*/
/**
* Track workspace creation
* Call this immediately after a workspace is created
*/
export const trackWorkspaceCreated = (
workspace: IWorkspace,
user: IUser,
role: EUserPermissions | EUserWorkspaceRoles | undefined,
extraProperties?: Record<string, unknown>
) => {
const userRole = getUserRoleString(role);
joinWorkspaceGroup(workspace);
trackEvent(
"workspace_created",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
workspace_name: workspace.name,
created_at: workspace.created_at instanceof Date ? workspace.created_at.toISOString() : workspace.created_at,
...extraProperties,
},
userRole
);
};
/**
* Track workspace deletion
*/
export const trackWorkspaceDeleted = (
workspace: IWorkspace,
user: IUser,
role: EUserPermissions | EUserWorkspaceRoles | undefined
) => {
const userRole = getUserRoleString(role);
trackEvent(
"workspace_deleted",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
deleted_at: new Date().toISOString(),
},
userRole
);
};
/**
* ============================================================================
* PRODUCT ACTIVATION EVENTS
* ============================================================================
*/
/**
* Track project creation
*/
export const trackProjectCreated = (
project: { id: string; created_at: string | Date },
workspace: IWorkspace,
user: IUser,
role: EUserPermissions | EUserWorkspaceRoles | undefined
) => {
const userRole = getUserRoleString(role);
trackEvent(
"project_created",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
project_id: project.id,
created_at: project.created_at instanceof Date ? project.created_at.toISOString() : project.created_at,
},
userRole
);
};
/**
* Track work item creation
*/
export const trackWorkItemCreated = (
workItem: { id: string; type?: string; created_at: string | Date },
project: { id: string },
workspace: IWorkspace,
user: IUser,
role: EUserPermissions | EUserWorkspaceRoles | undefined
) => {
const userRole = getUserRoleString(role);
trackEvent(
"work_item_created",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
project_id: project.id,
work_item_id: workItem.id,
work_item_type: workItem.type,
created_at: workItem.created_at instanceof Date ? workItem.created_at.toISOString() : workItem.created_at,
},
userRole
);
};
/**
* Track cycle creation
*/
export const trackCycleCreated = (
cycle: { id: string; length_days?: number; created_at: string | Date },
project: { id: string },
workspace: IWorkspace,
user: IUser,
role: EUserPermissions | EUserWorkspaceRoles | undefined
) => {
const userRole = getUserRoleString(role);
trackEvent(
"cycle_created",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
project_id: project.id,
cycle_id: cycle.id,
cycle_length_days: cycle.length_days || null,
created_at: cycle.created_at instanceof Date ? cycle.created_at.toISOString() : cycle.created_at,
},
userRole
);
};
/**
* Track page creation
*/
export const trackPageCreated = (
page: { id: string; created_at: string | Date; project_id?: string | null },
workspace: IWorkspace,
user: IUser,
location: "project" | "wiki" | "teamspace" | "workitem",
role: EUserPermissions | EUserWorkspaceRoles | undefined
) => {
const userRole = getUserRoleString(role);
trackEvent(
"page_created",
{
id: user.id,
workspace_id: workspace.id,
workspace_slug: workspace.slug,
page_id: page.id,
location,
project_id: page.project_id || null,
created_at: page.created_at instanceof Date ? page.created_at.toISOString() : page.created_at,
},
userRole
);
};
+19 -8
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { mutate } from "swr";
// types
import { CYCLE_TRACKER_EVENTS } from "@plane/constants";
@@ -7,16 +7,19 @@ import type { CycleDateCheckData, ICycle, TCycleTabOptions } from "@plane/types"
// ui
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
// hooks
import { renderFormattedPayloadDate } from "@plane/utils";
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { useCycle } from "@/hooks/store/use-cycle";
import { useProject } from "@/hooks/store/use-project";
import useKeypress from "@/hooks/use-keypress";
import useLocalStorage from "@/hooks/use-local-storage";
import { usePlatformOS } from "@/hooks/use-platform-os";
import { renderFormattedPayloadDate } from "@plane/utils";
// services
import { CycleService } from "@/services/cycle.service";
// local imports
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackCycleCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { CycleForm } from "./form";
type CycleModalProps = {
@@ -39,6 +42,10 @@ export function CycleCreateUpdateModal(props: CycleModalProps) {
const { createCycle, updateCycleDetails } = useCycle();
const { isMobile } = usePlatformOS();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { data: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const { setValue: setCycleTab } = useLocalStorage<TCycleTabOptions>("cycle_tab", "active");
const handleCreateCycle = async (payload: Partial<ICycle>) => {
@@ -62,12 +69,16 @@ export function CycleCreateUpdateModal(props: CycleModalProps) {
title: "Success!",
message: "Cycle created successfully.",
});
captureSuccess({
eventName: CYCLE_TRACKER_EVENTS.create,
payload: {
id: res.id,
},
});
if (currentWorkspace && currentUser) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackCycleCreated(
{ id: res.id, created_at: res?.created_at ?? "" },
{ id: projectId },
currentWorkspace,
currentUser,
role
);
}
})
.catch((err) => {
setToast({
@@ -1,6 +1,6 @@
import type { FC, FormEvent } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import type { FormEvent } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
// plane imports
import { ETabIndices, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
import type { EditorRefApi } from "@plane/editor";
@@ -9,9 +9,9 @@ import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TIssue } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
import { getTabIndex, renderFormattedPayloadDate } from "@plane/utils";
// helpers
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
@@ -26,6 +26,8 @@ import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-dup
// services
import { FileService } from "@/services/file.service";
// local imports
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { InboxIssueDescription } from "./issue-description";
import { InboxIssueProperties } from "./issue-properties";
import { InboxIssueTitle } from "./issue-title";
@@ -69,6 +71,9 @@ export const InboxIssueCreateRoot = observer(function InboxIssueCreateRoot(props
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
const { isMobile } = usePlatformOS();
const { getProjectById } = useProject();
const { currentWorkspace } = useWorkspace();
const { data: currentUser } = useUser();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { t } = useTranslation();
// states
const [createMore, setCreateMore] = useState<boolean>(false);
@@ -157,30 +162,39 @@ export const InboxIssueCreateRoot = observer(function InboxIssueCreateRoot(props
await createInboxIssue(workspaceSlug, projectId, payload)
.then(async (res) => {
if (!res?.issue) return;
if (uploadedAssetIds.length > 0) {
await fileService.updateBulkProjectAssetsUploadStatus(workspaceSlug, projectId, res?.issue.id ?? "", {
await fileService.updateBulkProjectAssetsUploadStatus(workspaceSlug, projectId, res.issue?.id ?? "", {
asset_ids: uploadedAssetIds,
});
setUploadedAssetIds([]);
}
if (!createMore) {
router.push(`/${workspaceSlug}/projects/${projectId}/intake/?currentTab=open&inboxIssueId=${res?.issue?.id}`);
router.push(`/${workspaceSlug}/projects/${projectId}/intake/?currentTab=open&inboxIssueId=${res.issue?.id}`);
handleModalClose();
} else {
descriptionEditorRef?.current?.clearEditor();
setFormData(defaultIssueData);
}
captureSuccess({
eventName: WORK_ITEM_TRACKER_EVENTS.create,
payload: {
id: res?.issue?.id,
},
});
if (currentWorkspace && currentUser) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackWorkItemCreated(
{ id: res?.issue?.id ?? "", created_at: res.issue?.created_at ?? "" },
{ id: projectId },
currentWorkspace,
currentUser,
role
);
}
setToast({
type: TOAST_TYPE.SUCCESS,
title: `Success!`,
message: "Work item created successfully.",
});
return res;
})
.catch((error) => {
console.error(error);
@@ -1,21 +1,24 @@
import type { FC } from "react";
import { useEffect, useState } from "react";
import { PlusIcon } from "lucide-react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { FC } from "react";
import { useEffect, useState } from "react";
import type { UseFormRegister } from "react-hook-form";
import { useForm } from "react-hook-form";
import { PlusIcon } from "lucide-react";
// plane imports
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { setPromiseToast } from "@plane/propel/toast";
import type { IProject, TIssue, EIssueLayoutTypes } from "@plane/types";
import type { EIssueLayoutTypes, IProject, TIssue } from "@plane/types";
import { cn, createIssuePayload } from "@plane/utils";
// helpers
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
// plane web imports
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues/quick-add";
// local imports
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { CreateIssueToastActionItems } from "../../create-issue-toast-action-items";
export type TQuickAddIssueForm = {
@@ -67,6 +70,12 @@ export const QuickAddIssueRoot = observer(function QuickAddIssueRoot(props: TQui
const { workspaceSlug, projectId } = useParams();
// states
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
// store hooks
const { currentWorkspace } = useWorkspace();
const { data: currentUser } = useUser();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// form info
const {
reset,
@@ -127,20 +136,26 @@ export const QuickAddIssueRoot = observer(function QuickAddIssueRoot(props: TQui
},
});
await quickAddPromise
.then((res) => {
captureSuccess({
eventName: WORK_ITEM_TRACKER_EVENTS.create,
payload: { id: res?.id },
});
})
.catch((error) => {
captureError({
eventName: WORK_ITEM_TRACKER_EVENTS.create,
payload: { id: payload.id },
error: error as Error,
});
try {
const quickAddRes = await quickAddPromise;
if (currentWorkspace && currentUser && quickAddRes) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackWorkItemCreated(
{ id: quickAddRes.id, created_at: quickAddRes.created_at ?? "" },
{ id: projectId.toString() },
currentWorkspace,
currentUser,
role
);
}
} catch (error) {
captureError({
eventName: WORK_ITEM_TRACKER_EVENTS.create,
payload: { id: payload.id },
error: error as Error,
});
}
}
};
@@ -1,8 +1,8 @@
import React, { useEffect, useRef, useState } from "react";
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
import { xor } from "lodash-es";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
import { useEffect, useRef, useState } from "react";
// Plane imports
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@@ -23,10 +23,13 @@ import { useIssuesActions } from "@/hooks/use-issues-actions";
import { FileService } from "@/services/file.service";
const fileService = new FileService();
// local imports
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { CreateIssueToastActionItems } from "../create-issue-toast-action-items";
import { DraftIssueLayout } from "./draft-issue-layout";
import { IssueFormRoot } from "./form";
import type { IssueFormProps } from "./form";
import { IssueFormRoot } from "./form";
import type { IssuesModalProps } from "./modal";
export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueModalBase(props: IssuesModalProps) {
@@ -72,6 +75,9 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
const { fetchIssue } = useIssueDetail();
const { allowedProjectIds, handleCreateUpdatePropertyValues, handleCreateSubWorkItem } = useIssueModal();
const { getProjectByIdentifier } = useProject();
const { currentWorkspace } = useWorkspace();
const { data: currentUser } = useUser();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// current store details
const { createIssue, updateIssue } = useIssuesActions(storeType);
// derived values
@@ -240,10 +246,16 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
/>
),
});
captureSuccess({
eventName: WORK_ITEM_TRACKER_EVENTS.create,
payload: { id: response.id },
});
if (currentWorkspace && currentUser && response) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackWorkItemCreated(
{ id: response.id, created_at: response.created_at },
{ id: payload.project_id },
currentWorkspace,
currentUser,
role
);
}
if (!createMore) handleClose();
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
setDescription("<p></p>");
@@ -319,10 +331,13 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
title: t("success"),
message: t("issue_updated_successfully"),
});
captureSuccess({
eventName: WORK_ITEM_TRACKER_EVENTS.update,
payload: { id: data.id },
});
if (currentWorkspace && currentUser) {
captureSuccess({
eventName: WORK_ITEM_TRACKER_EVENTS.update,
payload: { id: data.id },
});
}
handleClose();
} catch (error: any) {
console.error(error);
@@ -1,12 +1,12 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
// constants
import {
ORGANIZATION_SIZE,
RESTRICTED_URLS,
WORKSPACE_TRACKER_EVENTS,
WORKSPACE_TRACKER_ELEMENTS,
WORKSPACE_TRACKER_EVENTS,
} from "@plane/constants";
// types
import { useTranslation } from "@plane/i18n";
@@ -16,10 +16,11 @@ import type { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
// ui
import { CustomSelect, Input, Spinner } from "@plane/ui";
// hooks
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserProfile, useUserSettings } from "@/hooks/store/user";
import { useUser, useUserPermissions, useUserProfile, useUserSettings } from "@/hooks/store/user";
// services
import { trackWorkspaceCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { WorkspaceService } from "@/plane-web/services";
type Props = {
@@ -42,7 +43,10 @@ export const CreateWorkspace = observer(function CreateWorkspace(props: Props) {
// store hooks
const { updateUserProfile } = useUserProfile();
const { fetchCurrentUserSettings } = useUserSettings();
const { createWorkspace, fetchWorkspaces } = useWorkspace();
const { createWorkspace, fetchWorkspaces, currentWorkspace } = useWorkspace();
const { data: currentUser } = useUser();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// form info
const {
handleSubmit,
@@ -74,11 +78,14 @@ export const CreateWorkspace = observer(function CreateWorkspace(props: Props) {
title: t("workspace_creation.toast.success.title"),
message: t("workspace_creation.toast.success.message"),
});
captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.create,
payload: { slug: formData.slug },
});
await fetchWorkspaces();
const role = getWorkspaceRoleByWorkspaceSlug(workspaceResponse.slug);
if (currentUser) {
trackWorkspaceCreated(workspaceResponse, currentUser, role);
}
await completeStep(workspaceResponse.id);
})
.catch(() => {
@@ -1,7 +1,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { CircleCheck } from "lucide-react";
import { observer } from "mobx-react";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
// plane imports
import {
ORGANIZATION_SIZE,
@@ -16,14 +16,15 @@ import type { IUser, IWorkspace } from "@plane/types";
import { Spinner } from "@plane/ui";
import { cn } from "@plane/utils";
// helpers
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserProfile, useUserSettings } from "@/hooks/store/user";
import { useUser, useUserPermissions, useUserProfile, useUserSettings } from "@/hooks/store/user";
// plane-web imports
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
import { WorkspaceService } from "@/plane-web/services";
// local components
import { trackWorkspaceCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { CommonOnboardingHeader } from "../common";
type Props = {
@@ -48,9 +49,10 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
const { t } = useTranslation();
// store hooks
const { updateUserProfile } = useUserProfile();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { fetchCurrentUserSettings } = useUserSettings();
const { createWorkspace, fetchWorkspaces } = useWorkspace();
const { data: currentUser } = useUser();
const isWorkspaceCreationEnabled = getIsWorkspaceCreationDisabled() === false;
// form info
@@ -82,11 +84,14 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
title: t("workspace_creation.toast.success.title"),
message: t("workspace_creation.toast.success.message"),
});
captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.create,
payload: { slug: formData.slug },
});
await fetchWorkspaces();
const role = getWorkspaceRoleByWorkspaceSlug(workspaceResponse.slug);
if (currentUser) {
trackWorkspaceCreated(workspaceResponse, currentUser, role);
}
await completeStep(workspaceResponse.id);
onComplete(formData.organization_size === "Just myself");
} catch {
@@ -1,4 +1,3 @@
import type { FC } from "react";
import { useEffect, useState } from "react";
// constants
import type { EPageAccess } from "@plane/constants";
@@ -7,12 +6,16 @@ import type { TPage } from "@plane/types";
// ui
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
// hooks
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
import { useAppRouter } from "@/hooks/use-app-router";
import { setToast, TOAST_TYPE } from "@plane/propel/toast";
// plane web hooks
import type { EPageStoreType } from "@/plane-web/hooks/store";
import { usePageStore } from "@/plane-web/hooks/store";
// local imports
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { PageForm } from "./page-form";
type Props = {
@@ -45,6 +48,10 @@ export function CreatePageModal(props: Props) {
const router = useAppRouter();
// store hooks
const { createPage } = usePageStore(storeType);
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { data: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
setPageFormData((prev) => ({ ...prev, [key]: value }));
@@ -64,12 +71,20 @@ export function CreatePageModal(props: Props) {
try {
const pageData = await createPage(pageFormData);
if (pageData) {
captureSuccess({
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
payload: {
id: pageData.id,
},
});
if (currentWorkspace && currentUser) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackPageCreated(
{
id: pageData.id ?? "",
project_id: projectId,
created_at: pageData.created_at ?? "",
},
currentWorkspace,
currentUser,
"project",
role
);
}
handleStateClear();
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
}
@@ -78,6 +93,11 @@ export function CreatePageModal(props: Props) {
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
error,
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error?.data?.error || "Page could not be created. Please try again.",
});
}
};
@@ -1,10 +1,9 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useState } from "react";
// plane imports
import { useParams, useRouter } from "next/navigation";
import {
EUserPermissionsLevel,
EPageAccess,
EUserPermissionsLevel,
PROJECT_PAGE_TRACKER_ELEMENTS,
PROJECT_PAGE_TRACKER_EVENTS,
} from "@plane/constants";
@@ -13,12 +12,15 @@ import { EmptyStateDetailed } from "@plane/propel/empty-state";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TPage, TPageNavigationTabs } from "@plane/types";
import { EUserProjectRoles } from "@plane/types";
import { useParams, useRouter } from "next/navigation";
// components
import { PageLoader } from "@/components/pages/loaders/page-loader";
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureClick, captureError } from "@/helpers/event-tracker.helper";
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
import { useUser, useUserPermissions } from "@/hooks/store/user";
// plane web hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
import { trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
type Props = {
@@ -35,8 +37,10 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
const { currentProjectDetails } = useProject();
const { isAnyPageAvailable, getCurrentProjectFilteredPageIdsByTab, getCurrentProjectPageIdsByTab, loader } =
usePageStore(storeType);
const { allowPermissions } = useUserPermissions();
const { allowPermissions, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { createPage } = usePageStore(EPageStoreType.PROJECT);
const { data: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
// states
const [isCreatingPage, setIsCreatingPage] = useState(false);
// router
@@ -60,13 +64,16 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
await createPage(payload)
.then((res) => {
captureSuccess({
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
payload: {
id: res?.id,
state: "SUCCESS",
},
});
if (currentWorkspace && currentUser && res?.id) {
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
trackPageCreated(
{ id: res.id, created_at: res.created_at ?? "" },
currentWorkspace,
currentUser,
"project",
role
);
}
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
router.push(pageId);
})
@@ -1,7 +1,3 @@
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import {
ORGANIZATION_SIZE,
RESTRICTED_URLS,
@@ -12,13 +8,19 @@ import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IWorkspace } from "@plane/types";
import { observer } from "mobx-react";
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// ui
import { CustomSelect, Input } from "@plane/ui";
// hooks
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useAppRouter } from "@/hooks/use-app-router";
// services
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { trackWorkspaceCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
import { WorkspaceService } from "@/plane-web/services";
type Props = {
@@ -57,6 +59,9 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
const router = useAppRouter();
// store hooks
const { createWorkspace } = useWorkspace();
const { data: currentUser } = useUser();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// form info
const {
handleSubmit,
@@ -74,10 +79,10 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
try {
const workspaceResponse = await createWorkspace(formData);
captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.create,
payload: { slug: formData.slug },
});
if (currentUser) {
const role = getWorkspaceRoleByWorkspaceSlug(workspaceResponse.slug);
trackWorkspaceCreated(workspaceResponse, currentUser, role);
}
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("workspace_creation.toast.success.title"),
@@ -1,7 +1,6 @@
import React from "react";
import { AlertTriangle } from "lucide-react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { AlertTriangle } from "lucide-react";
// types
import { WORKSPACE_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -11,11 +10,12 @@ import type { IWorkspace } from "@plane/types";
// ui
import { Input } from "@plane/ui";
// hooks
import { cn } from "@plane/utils";
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { captureError } from "@/helpers/event-tracker.helper";
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserSettings } from "@/hooks/store/user";
import { useUser, useUserPermissions, useUserSettings } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { trackWorkspaceDeleted } from "@/plane-web/helpers/event-tracker-v2.helper";
import { cn } from "@plane/utils";
type Props = {
data: IWorkspace | null;
@@ -36,6 +36,9 @@ export const DeleteWorkspaceForm = observer(function DeleteWorkspaceForm(props:
const { t } = useTranslation();
const { getWorkspaceRedirectionUrl } = useWorkspace();
const { fetchCurrentUserSettings } = useUserSettings();
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
const { data: currentUser } = useUser();
// form info
const {
control,
@@ -64,10 +67,13 @@ export const DeleteWorkspaceForm = observer(function DeleteWorkspaceForm(props:
await fetchCurrentUserSettings();
handleClose();
router.push(getWorkspaceRedirectionUrl());
captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.delete,
payload: { slug: data.slug },
});
const role = getWorkspaceRoleByWorkspaceSlug(data.slug);
if (currentUser) {
trackWorkspaceDeleted(data, currentUser, role);
}
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("workspace_settings.settings.general.delete_modal.success_title"),
+17 -34
View File
@@ -1,18 +1,15 @@
import type { ReactNode } from "react";
import { lazy, Suspense, useEffect, useCallback, useRef, useState } from "react";
import { PostHogProvider as PHProvider } from "@posthog/react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import posthog from "posthog-js";
import type { ReactNode } from "react";
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
// constants
import { GROUP_WORKSPACE_TRACKER_EVENT } from "@plane/constants";
// helpers
import { getUserRole } from "@plane/utils";
// hooks
import { captureClick, joinEventGroup } from "@/helpers/event-tracker.helper";
import { captureClick } from "@/helpers/event-tracker.helper";
import { useInstance } from "@/hooks/store/use-instance";
import { useUser, useUserProfile } from "@/hooks/store/user";
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { identifyUser, joinWorkspaceGroup } from "@/plane-web/helpers/event-tracker-v2.helper";
// dynamic imports
const PostHogPageView = lazy(function PostHogPageView() {
return import("@/lib/posthog-view");
@@ -25,42 +22,28 @@ export interface IPosthogWrapper {
const PostHogProvider = observer(function PostHogProvider(props: IPosthogWrapper) {
const { children } = props;
const { data: user } = useUser();
const { currentWorkspace } = useWorkspace();
const { data: profile } = useUserProfile();
const { instance } = useInstance();
const { workspaceSlug, projectId } = useParams();
const { getWorkspaceRoleByWorkspaceSlug, getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
const { currentWorkspace } = useWorkspace();
// refs
const isInitializedRef = useRef(false);
// states
const [hydrated, setHydrated] = useState(false);
// derived values
const currentProjectRole = getProjectRoleByWorkspaceSlugAndProjectId(
workspaceSlug?.toString(),
projectId?.toString()
);
const currentWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug?.toString());
const is_telemetry_enabled = instance?.is_telemetry_enabled || false;
const is_posthog_enabled = process.env.VITE_POSTHOG_KEY && process.env.VITE_POSTHOG_HOST && is_telemetry_enabled;
useEffect(() => {
if (user && hydrated) {
// Identify sends an event, so you want may want to limit how often you call it
posthog?.identify(user.email, {
id: user.id,
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
workspace_role: currentWorkspaceRole ? getUserRole(currentWorkspaceRole) : undefined,
project_role: currentProjectRole ? getUserRole(currentProjectRole) : undefined,
});
if (currentWorkspace) {
joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, currentWorkspace?.id, {
date: new Date().toDateString(),
workspace_id: currentWorkspace?.id,
});
}
if (user && profile && hydrated && is_posthog_enabled) {
identifyUser(user, profile);
}
}, [user, currentProjectRole, currentWorkspaceRole, currentWorkspace, hydrated]);
}, [user, profile, hydrated, is_posthog_enabled]);
useEffect(() => {
if (currentWorkspace && hydrated && is_posthog_enabled) {
joinWorkspaceGroup(currentWorkspace);
}
}, [currentWorkspace, hydrated, is_posthog_enabled]);
useEffect(() => {
if (isInitializedRef.current) return; // prevent multiple initializations
@@ -0,0 +1 @@
export * from "ce/helpers/event-tracker-v2.helper";
+1
View File
@@ -43,6 +43,7 @@ export interface IUser extends IUserLite {
user_timezone: string;
username: string;
last_login_medium: TLoginMediums;
last_login_time: string | null;
theme: IUserTheme;
}