From f65c1f4510973e815e46af9c0cf0718a643bc4f9 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 30 Jun 2025 17:19:04 +0530 Subject: [PATCH 01/38] feat: event tracker helper --- web/helpers/event-tracker.helper.ts | 170 ++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 web/helpers/event-tracker.helper.ts diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts new file mode 100644 index 00000000000..8ee3b8a8d73 --- /dev/null +++ b/web/helpers/event-tracker.helper.ts @@ -0,0 +1,170 @@ +import posthog from "posthog-js"; + +export type TEventState = "SUCCESS" | "ERROR"; + +export type TTrackingElement = + | "HEADER_BUTTON" + | "SIDEBAR_BUTTON" + | "EMPTY_STATE_BUTTON" + | "COMMAND_PALETTE_ITEM" + | "MODAL_BUTTON" + | "DROPDOWN_ITEM" + | "LIST_ITEM_BUTTON" + | "CARD_BUTTON" + | "TAB_BUTTON" + | "MENU_ITEM" + | "SEARCH_BAR" + | "FILTER_BUTTON"; + +export type TEventName = + | "workspace_created" + | "workspace_updated" + | "workspace_deleted" + | "project_created" + | "project_updated" + | "project_deleted" + | "cycle_created" + | "cycle_updated" + | "cycle_deleted" + | "module_created" + | "module_updated" + | "module_deleted" + | "issue_created" + | "issue_updated" + | "issue_deleted"; + +export type TElementContext = Record; +export type TEventContext = Record; + +/** + * Track UI element interactions (clicks, hovers, views, etc.) + * This helps understand user behavior and interaction patterns + * + * @param element - Generic UI element type + * @param context - Context about where and why the interaction happened + */ +export const trackElement = (element: TTrackingElement, context?: TElementContext) => { + if (!posthog) return; + + const elementEvent = "element_interacted"; + + const payload = { + element_type: element, + timestamp: new Date().toISOString(), + ...context, + }; + + posthog.capture(elementEvent, payload); +}; + +/** + * Track click events + * @param element - The element that was clicked + * @param context - Additional context + */ +export const trackClick = (element: TTrackingElement, context?: TElementContext) => { + trackElement(element, { ...context, interaction_type: "click" }); +}; + +/** + * Track view events + * @param element - The element that was viewed + * @param context - Additional context + */ +export const trackView = (element: TTrackingElement, context?: TElementContext) => { + trackElement(element, { ...context, interaction_type: "view" }); +}; + +/** + * Track hover events + * @param element - The element that was hovered + * @param context - Additional context + */ +export const trackHover = (element: TTrackingElement, context?: TElementContext) => { + trackElement(element, { ...context, interaction_type: "hover" }); +}; + +/** + * Track business events (outcomes, state changes, etc.) + * This helps understand business metrics and conversion rates + * + * @param eventName - Business event name (e.g., "cycle_created", "project_updated") + * @param state - Success or error state + * @param payload - Event-specific data + * @param context - Additional context + */ +export const trackEvent = ( + eventName: TEventName, + state: TEventState, + payload?: Record, + context?: TEventContext +) => { + if (!posthog) return; + + const finalPayload = { + ...context, + ...payload, + state, + timestamp: new Date().toISOString(), + }; + + posthog.capture(eventName, finalPayload); +}; + +/** + * Track success events + * @param eventName - The name of the event + * @param payload - Additional payload + * @param context - Additional context + */ +export const trackSuccess = (eventName: TEventName, payload?: Record, context?: TEventContext) => { + trackEvent(eventName, "SUCCESS", payload, context); +}; + +/** + * Track error events + * @param eventName - The name of the event + * @param error - The error object + * @param payload - Additional payload + * @param context - Additional context + */ +export const trackError = ( + eventName: TEventName, + error?: Error | string, + payload?: Record, + context?: TEventContext +) => { + trackEvent( + eventName, + "ERROR", + { + ...payload, + error: typeof error === "string" ? error : error?.message, + }, + context + ); +}; + +/** + * Track both element interaction and business event together + * @param element - The element that was interacted with + * @param elementContext - Additional context for the element + * @param eventName - The name of the event + * @param eventState - The state of the event + * @param eventPayload - Additional payload for the event + * @param eventContext - Additional context for the event + */ +export const trackElementAndEvent = ( + element: TTrackingElement, + elementContext: TElementContext, + eventName: TEventName, + eventState: TEventState, + eventPayload?: Record, + eventContext?: TEventContext +) => { + // Track the element interaction first + trackElement(element, elementContext); + + // Then track the business event + trackEvent(eventName, eventState, eventPayload, eventContext); +}; From edc3c817dcda4e273c37ca9d04e08adfdc924ed0 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 30 Jun 2025 17:52:59 +0530 Subject: [PATCH 02/38] feat: track click events for `data-ph-element` --- web/core/lib/posthog-provider.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/web/core/lib/posthog-provider.tsx b/web/core/lib/posthog-provider.tsx index f714a9e7339..2dced768af2 100644 --- a/web/core/lib/posthog-provider.tsx +++ b/web/core/lib/posthog-provider.tsx @@ -11,6 +11,7 @@ import { GROUP_WORKSPACE_TRACKER_EVENT } from "@plane/constants"; // helpers import { getUserRole } from "@plane/utils"; // hooks +import { trackClick, TTrackingElement } from "@/helpers/event-tracker.helper"; import { useWorkspace, useUser, useInstance, useUserPermissions } from "@/hooks/store"; // dynamic imports const PostHogPageView = dynamic(() => import("@/lib/posthog-view"), { ssr: false }); @@ -33,6 +34,8 @@ const PostHogProvider: FC = observer((props) => { ); const currentWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug?.toString()); const is_telemetry_enabled = instance?.is_telemetry_enabled || false; + const is_posthog_enabled = + process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST && is_telemetry_enabled; useEffect(() => { if (user) { @@ -65,7 +68,24 @@ const PostHogProvider: FC = observer((props) => { } }, []); - if (process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST && is_telemetry_enabled) + useEffect(() => { + const clickHandler = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (target.hasAttribute("data-ph-element")) { + trackClick(target.getAttribute("data-ph-element") as TTrackingElement); + } + }; + + if (is_posthog_enabled) { + document.addEventListener("click", clickHandler); + } + + return () => { + document.removeEventListener("click", clickHandler); + }; + }, [is_posthog_enabled]); + + if (is_posthog_enabled) return ( From bb56fc36bfd433218f763c6ebf44d97b680ea75a Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 18:07:26 +0530 Subject: [PATCH 03/38] fix: handled click events --- web/helpers/event-tracker.helper.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts index 8ee3b8a8d73..6b9fbc2b7a8 100644 --- a/web/helpers/event-tracker.helper.ts +++ b/web/helpers/event-tracker.helper.ts @@ -35,6 +35,7 @@ export type TEventName = export type TElementContext = Record; export type TEventContext = Record; +export type TInteractionType = "clicked" | "viewed" | "hovered"; /** * Track UI element interactions (clicks, hovers, views, etc.) @@ -43,10 +44,14 @@ export type TEventContext = Record; * @param element - Generic UI element type * @param context - Context about where and why the interaction happened */ -export const trackElement = (element: TTrackingElement, context?: TElementContext) => { +export const trackElement = ( + element: TTrackingElement, + interaction_type: TInteractionType, + context?: TElementContext +) => { if (!posthog) return; - const elementEvent = "element_interacted"; + const elementEvent = `${element}_${interaction_type}` ?? "element_interacted"; const payload = { element_type: element, @@ -63,7 +68,7 @@ export const trackElement = (element: TTrackingElement, context?: TElementContex * @param context - Additional context */ export const trackClick = (element: TTrackingElement, context?: TElementContext) => { - trackElement(element, { ...context, interaction_type: "click" }); + trackElement(element, "clicked", { ...context }); }; /** @@ -72,7 +77,7 @@ export const trackClick = (element: TTrackingElement, context?: TElementContext) * @param context - Additional context */ export const trackView = (element: TTrackingElement, context?: TElementContext) => { - trackElement(element, { ...context, interaction_type: "view" }); + trackElement(element, "viewed", { ...context }); }; /** @@ -81,7 +86,7 @@ export const trackView = (element: TTrackingElement, context?: TElementContext) * @param context - Additional context */ export const trackHover = (element: TTrackingElement, context?: TElementContext) => { - trackElement(element, { ...context, interaction_type: "hover" }); + trackElement(element, "hovered", { ...context }); }; /** @@ -163,7 +168,7 @@ export const trackElementAndEvent = ( eventContext?: TEventContext ) => { // Track the element interaction first - trackElement(element, elementContext); + trackElement(element, "clicked", elementContext); // Then track the business event trackEvent(eventName, eventState, eventPayload, eventContext); From f19befd109167b8d25d248dd6e5605901204f66c Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 18:09:16 +0530 Subject: [PATCH 04/38] fix: handled name --- .../projects/(detail)/[projectId]/modules/(list)/header.tsx | 1 + web/helpers/event-tracker.helper.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx index aded9e3619c..89a11d5c008 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx @@ -55,6 +55,7 @@ export const ModulesListHeader: React.FC = observer(() => { diff --git a/web/core/components/workspace/create-workspace-form.tsx b/web/core/components/workspace/create-workspace-form.tsx index 0f51e74b26f..adf60d0dc8b 100644 --- a/web/core/components/workspace/create-workspace-form.tsx +++ b/web/core/components/workspace/create-workspace-form.tsx @@ -11,7 +11,8 @@ import { IWorkspace } from "@plane/types"; // ui import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui"; // hooks -import { useEventTracker, useWorkspace } from "@/hooks/store"; +import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // services import { WorkspaceService } from "@/plane-web/services"; @@ -51,7 +52,6 @@ export const CreateWorkspaceForm: FC = observer((props) => { // router const router = useAppRouter(); // store hooks - const { captureWorkspaceEvent } = useEventTracker(); const { createWorkspace } = useWorkspace(); // form info const { @@ -71,14 +71,7 @@ export const CreateWorkspaceForm: FC = observer((props) => { await createWorkspace(formData) .then(async (res) => { - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - ...res, - state: "SUCCESS", - element: "Create workspace page", - }, - }); + trackSuccess(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_creation.toast.success.title"), @@ -88,13 +81,7 @@ export const CreateWorkspaceForm: FC = observer((props) => { if (onSubmit) await onSubmit(res); }) .catch(() => { - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - state: "FAILED", - element: "Create workspace page", - }, - }); + trackError(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); setToast({ type: TOAST_TYPE.ERROR, title: t("workspace_creation.toast.error.title"), @@ -248,7 +235,14 @@ export const CreateWorkspaceForm: FC = observer((props) => {
{secondaryButton} - {!secondaryButton && ( diff --git a/web/core/components/workspace/delete-workspace-form.tsx b/web/core/components/workspace/delete-workspace-form.tsx index c668ad300c6..ae891f0fae6 100644 --- a/web/core/components/workspace/delete-workspace-form.tsx +++ b/web/core/components/workspace/delete-workspace-form.tsx @@ -13,7 +13,8 @@ import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // constants // hooks import { cn } from "@plane/utils"; -import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store"; +import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { useUserSettings, useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; type Props = { @@ -31,7 +32,6 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { // router const router = useAppRouter(); // store hooks - const { captureWorkspaceEvent } = useEventTracker(); const { deleteWorkspace } = useWorkspace(); const { t } = useTranslation(); const { getWorkspaceRedirectionUrl } = useWorkspace(); @@ -64,14 +64,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { await fetchCurrentUserSettings(); handleClose(); router.push(getWorkspaceRedirectionUrl()); - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.delete, - payload: { - ...data, - state: "SUCCESS", - element: "Workspace general settings page", - }, - }); + trackSuccess(WORKSPACE_TRACKER_EVENTS.delete); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.general.delete_modal.success_title"), @@ -84,14 +77,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { title: t("workspace_settings.settings.general.delete_modal.error_title"), message: t("workspace_settings.settings.general.delete_modal.error_message"), }); - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.delete, - payload: { - ...data, - state: "FAILED", - element: "Workspace general settings page", - }, - }); + trackError(WORKSPACE_TRACKER_EVENTS.delete); }); }; @@ -169,7 +155,14 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { -
diff --git a/web/core/components/workspace/settings/workspace-details.tsx b/web/core/components/workspace/settings/workspace-details.tsx index e9a83784b58..675cc0bcee6 100644 --- a/web/core/components/workspace/settings/workspace-details.tsx +++ b/web/core/components/workspace/settings/workspace-details.tsx @@ -15,7 +15,8 @@ import { LogoSpinner } from "@/components/common"; import { WorkspaceImageUploadModal } from "@/components/core"; // helpers // hooks -import { useEventTracker, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web components import { DeleteWorkspaceSection } from "@/plane-web/components/workspace"; @@ -31,7 +32,6 @@ export const WorkspaceDetails: FC = observer(() => { const [isLoading, setIsLoading] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); // store hooks - const { captureWorkspaceEvent } = useEventTracker(); const { currentWorkspace, updateWorkspace } = useWorkspace(); const { allowPermissions } = useUserPermissions(); const { t } = useTranslation(); @@ -61,14 +61,7 @@ export const WorkspaceDetails: FC = observer(() => { await updateWorkspace(currentWorkspace.slug, payload) .then((res) => { - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.update, - payload: { - ...res, - state: "SUCCESS", - element: "Workspace general settings page", - }, - }); + trackSuccess(WORKSPACE_TRACKER_EVENTS.update); setToast({ title: "Success!", type: TOAST_TYPE.SUCCESS, @@ -76,13 +69,7 @@ export const WorkspaceDetails: FC = observer(() => { }); }) .catch((err) => { - captureWorkspaceEvent({ - eventName: WORKSPACE_TRACKER_EVENTS.update, - payload: { - state: "FAILED", - element: "Workspace general settings page", - }, - }); + trackError(WORKSPACE_TRACKER_EVENTS.update); console.error(err); }); setTimeout(() => { @@ -282,7 +269,12 @@ export const WorkspaceDetails: FC = observer(() => { {isAdmin && (
-
diff --git a/web/core/store/event-tracker.store.ts b/web/core/store/event-tracker.store.ts index 8116eaaf0f3..a72f12a026e 100644 --- a/web/core/store/event-tracker.store.ts +++ b/web/core/store/event-tracker.store.ts @@ -10,9 +10,7 @@ import { getModuleEventPayload, getProjectEventPayload, getProjectStateEventPayload, - getWorkspaceEventPayload, getPageEventPayload, - WORKSPACE_TRACKER_EVENTS, } from "@plane/constants"; import { CoreRootStore } from "./root.store"; @@ -26,7 +24,6 @@ export interface ICoreEventTrackerStore { setTrackElement: (element: string) => void; captureEvent: (eventName: string, payload?: any) => void; joinWorkspaceMetricGroup: (workspaceId?: string) => void; - captureWorkspaceEvent: (props: EventProps) => void; captureProjectEvent: (props: EventProps) => void; captureCycleEvent: (props: EventProps) => void; captureModuleEvent: (props: EventProps) => void; @@ -109,23 +106,6 @@ export abstract class CoreEventTrackerStore implements ICoreEventTrackerStore { this.setTrackElement(undefined); }; - /** - * @description: Captures the workspace crud related events. - * @param {EventProps} props - */ - captureWorkspaceEvent = (props: EventProps) => { - const { eventName, payload } = props; - if (eventName === WORKSPACE_TRACKER_EVENTS.create && payload.state == "SUCCESS") { - this.joinWorkspaceMetricGroup(payload.id); - } - const eventPayload: any = getWorkspaceEventPayload({ - ...payload, - element: payload.element ?? this.trackElement, - }); - posthog?.capture(eventName, eventPayload); - this.setTrackElement(undefined); - }; - /** * @description: Captures the project related events. * @param {EventProps} props diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts index 6f4d3e33280..d230c5e3076 100644 --- a/web/helpers/event-tracker.helper.ts +++ b/web/helpers/event-tracker.helper.ts @@ -13,11 +13,7 @@ export type TInteractionType = "clicked" | "viewed" | "hovered"; * @param element - Generic UI element type * @param context - Context about where and why the interaction happened */ -const trackElement = ( - element: TTrackingElement, - interaction_type: TInteractionType, - context?: TElementContext -) => { +const trackElement = (element: TTrackingElement, interaction_type: TInteractionType, context?: TElementContext) => { if (!posthog) return; const elementEvent = `${element}_${interaction_type}`; @@ -67,12 +63,7 @@ export const trackHover = (element: TTrackingElement, context?: TElementContext) * @param payload - Event-specific data * @param context - Additional context */ -const trackEvent = ( - eventName: string, - state: TEventState, - payload?: Record, - context?: TEventContext -) => { +const trackEvent = (eventName: string, state: TEventState, payload?: Record, context?: TEventContext) => { if (!posthog) return; const finalPayload = { @@ -104,8 +95,8 @@ export const trackSuccess = (eventName: string, payload?: Record, c */ export const trackError = ( eventName: string, - error?: Error | string, payload?: Record, + error?: Error | string, context?: TEventContext ) => { trackEvent( From 9f8872e43171fe13756f6b4a88cd56521f6e377a Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 19:29:44 +0530 Subject: [PATCH 12/38] fix: added slug --- web/core/components/workspace/delete-workspace-form.tsx | 4 ++-- web/core/components/workspace/settings/workspace-details.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/core/components/workspace/delete-workspace-form.tsx b/web/core/components/workspace/delete-workspace-form.tsx index ae891f0fae6..b36ae566efc 100644 --- a/web/core/components/workspace/delete-workspace-form.tsx +++ b/web/core/components/workspace/delete-workspace-form.tsx @@ -64,7 +64,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { await fetchCurrentUserSettings(); handleClose(); router.push(getWorkspaceRedirectionUrl()); - trackSuccess(WORKSPACE_TRACKER_EVENTS.delete); + trackSuccess(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.general.delete_modal.success_title"), @@ -77,7 +77,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { title: t("workspace_settings.settings.general.delete_modal.error_title"), message: t("workspace_settings.settings.general.delete_modal.error_message"), }); - trackError(WORKSPACE_TRACKER_EVENTS.delete); + trackError(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); }); }; diff --git a/web/core/components/workspace/settings/workspace-details.tsx b/web/core/components/workspace/settings/workspace-details.tsx index 675cc0bcee6..3a3de5d10cf 100644 --- a/web/core/components/workspace/settings/workspace-details.tsx +++ b/web/core/components/workspace/settings/workspace-details.tsx @@ -61,7 +61,7 @@ export const WorkspaceDetails: FC = observer(() => { await updateWorkspace(currentWorkspace.slug, payload) .then((res) => { - trackSuccess(WORKSPACE_TRACKER_EVENTS.update); + trackSuccess(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); setToast({ title: "Success!", type: TOAST_TYPE.SUCCESS, @@ -69,7 +69,7 @@ export const WorkspaceDetails: FC = observer(() => { }); }) .catch((err) => { - trackError(WORKSPACE_TRACKER_EVENTS.update); + trackError(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); console.error(err); }); setTimeout(() => { From 0d58c18fdd49a44b506acd52094877b33a396dea Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 19:33:48 +0530 Subject: [PATCH 13/38] fix: changes nomenclature --- web/core/components/onboarding/create-workspace.tsx | 6 +++--- web/core/components/workspace/create-workspace-form.tsx | 6 +++--- web/core/components/workspace/delete-workspace-form.tsx | 6 +++--- .../components/workspace/settings/workspace-details.tsx | 6 +++--- web/helpers/event-tracker.helper.ts | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/web/core/components/onboarding/create-workspace.tsx b/web/core/components/onboarding/create-workspace.tsx index 238729a0a73..c7643045644 100644 --- a/web/core/components/onboarding/create-workspace.tsx +++ b/web/core/components/onboarding/create-workspace.tsx @@ -11,7 +11,7 @@ import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types"; // ui import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // hooks -import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store"; // services import { WorkspaceService } from "@/plane-web/services"; @@ -68,12 +68,12 @@ export const CreateWorkspace: React.FC = observer((props) => { title: t("workspace_creation.toast.success.title"), message: t("workspace_creation.toast.success.message"), }); - trackSuccess(WORKSPACE_TRACKER_EVENTS.create, { ...workspaceResponse }); + captureSuccess(WORKSPACE_TRACKER_EVENTS.create, { ...workspaceResponse }); await fetchWorkspaces(); await completeStep(workspaceResponse.id); }) .catch(() => { - trackError(WORKSPACE_TRACKER_EVENTS.create); + captureError(WORKSPACE_TRACKER_EVENTS.create); setToast({ type: TOAST_TYPE.ERROR, title: t("workspace_creation.toast.error.title"), diff --git a/web/core/components/workspace/create-workspace-form.tsx b/web/core/components/workspace/create-workspace-form.tsx index adf60d0dc8b..d35c94657c5 100644 --- a/web/core/components/workspace/create-workspace-form.tsx +++ b/web/core/components/workspace/create-workspace-form.tsx @@ -11,7 +11,7 @@ import { IWorkspace } from "@plane/types"; // ui import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui"; // hooks -import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // services @@ -71,7 +71,7 @@ export const CreateWorkspaceForm: FC = observer((props) => { await createWorkspace(formData) .then(async (res) => { - trackSuccess(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); + captureSuccess(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_creation.toast.success.title"), @@ -81,7 +81,7 @@ export const CreateWorkspaceForm: FC = observer((props) => { if (onSubmit) await onSubmit(res); }) .catch(() => { - trackError(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); + captureError(WORKSPACE_TRACKER_EVENTS.create, { ...formData }); setToast({ type: TOAST_TYPE.ERROR, title: t("workspace_creation.toast.error.title"), diff --git a/web/core/components/workspace/delete-workspace-form.tsx b/web/core/components/workspace/delete-workspace-form.tsx index b36ae566efc..7784b4f08db 100644 --- a/web/core/components/workspace/delete-workspace-form.tsx +++ b/web/core/components/workspace/delete-workspace-form.tsx @@ -13,7 +13,7 @@ import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // constants // hooks import { cn } from "@plane/utils"; -import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUserSettings, useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -64,7 +64,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { await fetchCurrentUserSettings(); handleClose(); router.push(getWorkspaceRedirectionUrl()); - trackSuccess(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); + captureSuccess(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.general.delete_modal.success_title"), @@ -77,7 +77,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { title: t("workspace_settings.settings.general.delete_modal.error_title"), message: t("workspace_settings.settings.general.delete_modal.error_message"), }); - trackError(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); + captureError(WORKSPACE_TRACKER_EVENTS.delete, { slug: data.slug }); }); }; diff --git a/web/core/components/workspace/settings/workspace-details.tsx b/web/core/components/workspace/settings/workspace-details.tsx index 3a3de5d10cf..1318a246a2e 100644 --- a/web/core/components/workspace/settings/workspace-details.tsx +++ b/web/core/components/workspace/settings/workspace-details.tsx @@ -15,7 +15,7 @@ import { LogoSpinner } from "@/components/common"; import { WorkspaceImageUploadModal } from "@/components/core"; // helpers // hooks -import { trackError, trackSuccess } from "@/helpers/event-tracker.helper"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web components import { DeleteWorkspaceSection } from "@/plane-web/components/workspace"; @@ -61,7 +61,7 @@ export const WorkspaceDetails: FC = observer(() => { await updateWorkspace(currentWorkspace.slug, payload) .then((res) => { - trackSuccess(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); + captureSuccess(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); setToast({ title: "Success!", type: TOAST_TYPE.SUCCESS, @@ -69,7 +69,7 @@ export const WorkspaceDetails: FC = observer(() => { }); }) .catch((err) => { - trackError(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); + captureError(WORKSPACE_TRACKER_EVENTS.update, { slug: currentWorkspace.slug }); console.error(err); }); setTimeout(() => { diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts index d230c5e3076..6a08c323f8a 100644 --- a/web/helpers/event-tracker.helper.ts +++ b/web/helpers/event-tracker.helper.ts @@ -82,7 +82,7 @@ const trackEvent = (eventName: string, state: TEventState, payload?: Record, context?: TEventContext) => { +export const captureSuccess = (eventName: string, payload?: Record, context?: TEventContext) => { trackEvent(eventName, "SUCCESS", payload, context); }; @@ -93,7 +93,7 @@ export const trackSuccess = (eventName: string, payload?: Record, c * @param payload - Additional payload * @param context - Additional context */ -export const trackError = ( +export const captureError = ( eventName: string, payload?: Record, error?: Error | string, From bc390536c6480c982998ea818cfbfe365a82ba11 Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 19:51:56 +0530 Subject: [PATCH 14/38] fix: nomenclature --- web/helpers/event-tracker.helper.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts index 175c6458a7a..0327ac3da90 100644 --- a/web/helpers/event-tracker.helper.ts +++ b/web/helpers/event-tracker.helper.ts @@ -13,7 +13,7 @@ type TTrackElementParams = { }; /** - * Track UI element interactions (clicks, hovers, views, etc.) + * Capture UI element interactions (clicks, hovers, views, etc.) * This helps understand user behavior and interaction patterns * * @param element - Generic UI element type @@ -36,7 +36,7 @@ const captureElement = (params: TTrackElementParams) => { type TTrackClickParams = Omit; /** - * Track click events + * Capture click events * @param element - The element that was clicked * @param context - Additional context */ @@ -46,7 +46,7 @@ export const captureClick = (params: TTrackClickParams) => { type TTrackViewParams = Omit; /** - * Track view events + * Capture view events * @param element - The element that was viewed * @param context - Additional context */ @@ -56,7 +56,7 @@ export const captureView = (params: TTrackViewParams) => { type TTrackHoverParams = Omit; /** - * Track hover events + * Capture hover events * @param element - The element that was hovered * @param context - Additional context */ @@ -72,7 +72,7 @@ type TTrackEventParams = { }; /** - * Track business events (outcomes, state changes, etc.) + * Capture business events (outcomes, state changes, etc.) * This helps understand business metrics and conversion rates * * @param eventName - Business event name (e.g., "cycle_created", "project_updated") @@ -96,7 +96,7 @@ const captureEvent = (params: TTrackEventParams) => { type TTrackSuccessParams = Omit; /** - * Track success events + * Capture success events * @param eventName - The name of the event * @param payload - Additional payload * @param context - Additional context @@ -110,7 +110,7 @@ type TTrackErrorParams = Omit & { }; /** - * Track error events + * Capture error events * @param eventName - The name of the event * @param error - The error object * @param payload - Additional payload @@ -126,13 +126,13 @@ type TTrackElementAndEventParams = { }; /** - * Track both element interaction and business event together + * Capture both element interaction and business event together * @param element - The element that was interacted with * @param event - The business event that was triggered */ export const captureElementAndEvent = (params: TTrackElementAndEventParams) => { const { element, event } = params; - // Track the element interaction first + // Capture the element interaction first captureElement({ ...element, interaction_type: "clicked" }); // Then capture the business event From f5cc076ca6b666f0544949df4f1de634ec7b728f Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 30 Jun 2025 19:56:47 +0530 Subject: [PATCH 15/38] chore: update event tracker helper types --- web/helpers/event-tracker.helper.ts | 41 +++++++++++++---------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts index 175c6458a7a..f48187849a8 100644 --- a/web/helpers/event-tracker.helper.ts +++ b/web/helpers/event-tracker.helper.ts @@ -6,7 +6,7 @@ export type TElementContext = Record; export type TEventContext = Record; export type TInteractionType = "clicked" | "viewed" | "hovered"; -type TTrackElementParams = { +type TCaptureElementParams = { elementName: TTrackingElement; interaction_type: TInteractionType; context?: TElementContext; @@ -19,7 +19,7 @@ type TTrackElementParams = { * @param element - Generic UI element type * @param context - Context about where and why the interaction happened */ -const captureElement = (params: TTrackElementParams) => { +const captureElement = (params: TCaptureElementParams) => { const { elementName, interaction_type, context } = params; if (!posthog) return; @@ -34,43 +34,42 @@ const captureElement = (params: TTrackElementParams) => { posthog.capture(elementEvent, payload); }; -type TTrackClickParams = Omit; +type TCaptureClickParams = Omit; /** * Track click events * @param element - The element that was clicked * @param context - Additional context */ -export const captureClick = (params: TTrackClickParams) => { +export const captureClick = (params: TCaptureClickParams) => { captureElement({ ...params, interaction_type: "clicked" }); }; -type TTrackViewParams = Omit; +type TCaptureViewParams = Omit; /** * Track view events * @param element - The element that was viewed * @param context - Additional context */ -export const captureView = (params: TTrackViewParams) => { +export const captureView = (params: TCaptureViewParams) => { captureElement({ ...params, interaction_type: "viewed" }); }; -type TTrackHoverParams = Omit; +type TCaptureHoverParams = Omit; /** * Track hover events * @param element - The element that was hovered * @param context - Additional context */ -export const captureHover = (params: TTrackHoverParams) => { +export const captureHover = (params: TCaptureHoverParams) => { captureElement({ ...params, interaction_type: "hovered" }); }; -type TTrackEventParams = { +type TCaptureEventParams = { eventName: string; payload?: Record; context?: TEventContext; state: TEventState; }; - /** * Track business events (outcomes, state changes, etc.) * This helps understand business metrics and conversion rates @@ -80,7 +79,7 @@ type TTrackEventParams = { * @param payload - Event-specific data * @param context - Additional context */ -const captureEvent = (params: TTrackEventParams) => { +const captureEvent = (params: TCaptureEventParams) => { const { eventName, payload, context, state } = params; if (!posthog) return; @@ -94,21 +93,20 @@ const captureEvent = (params: TTrackEventParams) => { posthog.capture(eventName, finalPayload); }; -type TTrackSuccessParams = Omit; +type TCaptureSuccessParams = Omit; /** * Track success events * @param eventName - The name of the event * @param payload - Additional payload * @param context - Additional context */ -export const captureSuccess = (params: TTrackSuccessParams) => { +export const captureSuccess = (params: TCaptureSuccessParams) => { captureEvent({ ...params, state: "SUCCESS" }); }; -type TTrackErrorParams = Omit & { - error: Error | string; +type TCaptureErrorParams = Omit & { + error?: Error | string; }; - /** * Track error events * @param eventName - The name of the event @@ -116,21 +114,20 @@ type TTrackErrorParams = Omit & { * @param payload - Additional payload * @param context - Additional context */ -export const captureError = (params: TTrackErrorParams) => { +export const captureError = (params: TCaptureErrorParams) => { captureEvent({ ...params, state: "ERROR", payload: { ...params.payload, error: params.error } }); }; -type TTrackElementAndEventParams = { - element: Omit; - event: TTrackEventParams; +type TCaptureElementAndEventParams = { + element: Omit; + event: TCaptureEventParams; }; - /** * Track both element interaction and business event together * @param element - The element that was interacted with * @param event - The business event that was triggered */ -export const captureElementAndEvent = (params: TTrackElementAndEventParams) => { +export const captureElementAndEvent = (params: TCaptureElementAndEventParams) => { const { element, event } = params; // Track the element interaction first captureElement({ ...element, interaction_type: "clicked" }); From 3545008a1ea8a8f75a8a5fd11908af43f7157955 Mon Sep 17 00:00:00 2001 From: gakshita Date: Mon, 30 Jun 2025 19:57:09 +0530 Subject: [PATCH 16/38] fix: data id --- web/ce/components/workspace/delete-workspace-section.tsx | 6 +++++- web/core/components/workspace/delete-workspace-form.tsx | 9 +-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/web/ce/components/workspace/delete-workspace-section.tsx b/web/ce/components/workspace/delete-workspace-section.tsx index 00fb7b87891..60474bd4870 100644 --- a/web/ce/components/workspace/delete-workspace-section.tsx +++ b/web/ce/components/workspace/delete-workspace-section.tsx @@ -48,7 +48,11 @@ export const DeleteWorkspaceSection: FC = observer((props) => {t("workspace_settings.settings.general.delete_workspace_description")}
-
diff --git a/web/core/components/workspace/delete-workspace-form.tsx b/web/core/components/workspace/delete-workspace-form.tsx index ea3358a1868..14cffe5459d 100644 --- a/web/core/components/workspace/delete-workspace-form.tsx +++ b/web/core/components/workspace/delete-workspace-form.tsx @@ -162,14 +162,7 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { - From db1caa79ebd86d83cdd53473f3576bc7146f8464 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 14:31:36 +0530 Subject: [PATCH 17/38] refactor: cycle events (#7290) * chore: update event tracker helper types * refactor: cycle events * refactor: cycle events * refactor: cycle event tracker * chore: update tracker elements * chore: check for closest element with data-ph-element attribute --------- Co-authored-by: Prateek Shourya --- packages/constants/src/event-tracker/core.ts | 47 +++--------- .../[projectId]/cycles/(list)/header.tsx | 7 +- .../[projectId]/cycles/(list)/page.tsx | 7 +- .../[projectId]/modules/(list)/header.tsx | 1 - web/ce/helpers/command-palette.ts | 7 +- .../actions/project-actions.tsx | 9 +-- .../analytics-sidebar/sidebar-header.tsx | 75 ++++++++----------- .../cycles/archived-cycles/modal.tsx | 20 ++++- web/core/components/cycles/delete-modal.tsx | 21 ++++-- web/core/components/cycles/form.tsx | 6 +- .../cycles/list/cycle-list-item-action.tsx | 65 ++++++++++------ web/core/components/cycles/modal.tsx | 35 +++++---- web/core/components/cycles/quick-actions.tsx | 46 +++++++++--- web/core/lib/posthog-provider.tsx | 12 ++- web/core/store/event-tracker.store.ts | 18 ----- web/helpers/event-tracker.helper.ts | 44 +++++------ 16 files changed, 220 insertions(+), 200 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index bbb1319ffbf..345567219d0 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -12,16 +12,6 @@ export type EventProps = { path?: string; }; -export const getWorkspaceEventPayload = (payload: any) => ({ - workspace_id: payload.id, - created_at: payload.created_at, - updated_at: payload.updated_at, - organization_size: payload.organization_size, - first_time: payload.first_time, - state: payload.state, - element: payload.element, -}); - export const getProjectEventPayload = (payload: any) => ({ workspace_id: payload.workspace_id, project_id: payload.id, @@ -35,20 +25,6 @@ export const getProjectEventPayload = (payload: any) => ({ element: payload.element, }); -export const getCycleEventPayload = (payload: any) => ({ - workspace_id: payload.workspace_id, - project_id: payload.project, - cycle_id: payload.id, - created_at: payload.created_at, - updated_at: payload.updated_at, - start_date: payload.start_date, - target_date: payload.target_date, - cycle_status: payload.status, - changed_properties: payload.changed_properties, - state: payload.state, - element: payload.element, -}); - export const getModuleEventPayload = (payload: any) => ({ workspace_id: payload.workspace_id, project_id: payload.project, @@ -147,16 +123,6 @@ export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; // Groups export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics"; -export const TRACKING_ELEMENTS = { - RIGHT_HEADER_BUTTON: "right_header_button", - EMPTY_STATE_BUTTON: "empty_state_button", - COMMAND_PALETTE_ITEM: "command_palette_item", - RIGHT_SIDEBAR: "right_sidebar", - QUICK_ACTIONS: "quick_actions", - CONTEXT_MENU: "context_menu", -} as const; -export type TTrackingElement = (typeof TRACKING_ELEMENTS)[keyof typeof TRACKING_ELEMENTS]; - export const WORKSPACE_TRACKER_EVENTS = { create: "workspace_created", update: "workspace_updated", @@ -175,7 +141,18 @@ export const CYCLE_TRACKER_EVENTS = { delete: "cycle_deleted", favorite: "cycle_favorited", unfavorite: "cycle_unfavorited", -}; + archive: "cycle_archived", + restore: "cycle_restored", +}; +export const CYCLE_TRACKER_ELEMENTS = { + RIGHT_HEADER_ADD_BUTTON: "right_header_add_cycle_button", + EMPTY_STATE_ADD_BUTTON: "empty_state_add_cycle_button", + COMMAND_PALETTE_ADD_ITEM: "command_palette_add_cycle_item", + RIGHT_SIDEBAR: "cycle_right_sidebar", + QUICK_ACTIONS: "cycle_quick_actions", + CONTEXT_MENU: "cycle_context_menu", + LIST_ITEM: "cycle_list_item", +} as const; export const MODULE_TRACKER_EVENTS = { create: "module_created", diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx index 41dfde747cf..fe950378765 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx @@ -4,13 +4,13 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui -import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, Header } from "@plane/ui"; // components import { CyclesViewHeader } from "@/components/cycles"; // hooks -import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; +import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane web import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common"; @@ -23,7 +23,6 @@ export const CyclesListHeader: FC = observer(() => { // store hooks const { toggleCreateCycleModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const { currentProjectDetails, loader } = useProject(); const { t } = useTranslation(); @@ -51,8 +50,8 @@ export const CyclesListHeader: FC = observer(() => {
diff --git a/web/app/(all)/onboarding/page.tsx b/web/app/(all)/onboarding/page.tsx index 078d6fefaec..7e7d15a10ee 100644 --- a/web/app/(all)/onboarding/page.tsx +++ b/web/app/(all)/onboarding/page.tsx @@ -16,7 +16,8 @@ import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; // hooks -import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store"; +import { captureSuccess } from "@/helpers/event-tracker.helper"; +import { useUser, useWorkspace, useUserProfile } from "@/hooks/store"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; import { WorkspaceService } from "@/plane-web/services"; @@ -35,7 +36,6 @@ const OnboardingPage = observer(() => { const [step, setStep] = useState(null); const [totalSteps, setTotalSteps] = useState(null); // store hooks - const { captureEvent } = useEventTracker(); const { isLoading: userLoader, data: user, updateCurrentUser } = useUser(); const { data: profile, updateUserProfile, finishUserOnboarding } = useUserProfile(); const { workspaces, fetchWorkspaces } = useWorkspace(); @@ -73,10 +73,12 @@ const OnboardingPage = observer(() => { await finishUserOnboarding() .then(() => { - captureEvent(USER_TRACKER_EVENTS.onboarding_complete, { - email: user.email, - user_id: user.id, - status: "SUCCESS", + captureSuccess({ + eventName: USER_TRACKER_EVENTS.onboarding_complete, + payload: { + email: user.email, + user_id: user.id, + }, }); }) .catch(() => { diff --git a/web/core/components/home/root.tsx b/web/core/components/home/root.tsx index 4c25bc3f68e..fd3c256b0ee 100644 --- a/web/core/components/home/root.tsx +++ b/web/core/components/home/root.tsx @@ -1,18 +1,21 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -// components import useSWR from "swr"; +// plane imports import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants"; import { ContentWrapper } from "@plane/ui"; import { cn } from "@plane/utils"; +// components import { TourRoot } from "@/components/onboarding"; -// constants // helpers +import { captureSuccess } from "@/helpers/event-tracker.helper"; // hooks -import { useUserProfile, useEventTracker, useUser } from "@/hooks/store"; +import { useUserProfile, useUser } from "@/hooks/store"; import { useHome } from "@/hooks/store/use-home"; import useSize from "@/hooks/use-window-size"; +// plane web components import { HomePeekOverviewsRoot } from "@/plane-web/components/home"; +// local imports import { DashboardWidgets } from "./home-dashboard-widgets"; import { UserGreetingsView } from "./user-greetings"; @@ -21,7 +24,6 @@ export const WorkspaceHomeView = observer(() => { const { workspaceSlug } = useParams(); const { data: currentUser } = useUser(); const { data: currentUserProfile, updateTourCompleted } = useUserProfile(); - const { captureEvent } = useEventTracker(); const { toggleWidgetSettings, fetchWidgets } = useHome(); const [windowWidth] = useSize(); @@ -38,9 +40,11 @@ export const WorkspaceHomeView = observer(() => { const handleTourCompleted = () => { updateTourCompleted() .then(() => { - captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.complete, { - user_id: currentUser?.id, - state: "SUCCESS", + captureSuccess({ + eventName: PRODUCT_TOUR_TRACKER_EVENTS.complete, + payload: { + user_id: currentUser?.id, + }, }); }) .catch((error) => { diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index 6bf8ed36160..124b9465f9a 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -13,7 +13,7 @@ import { EViewAccess, EUserPermissions, EUserPermissionsLevel, - GLOBAL_VIEW_TOUR_TRACKER_EVENTS, + GLOBAL_VIEW_TRACKER_EVENTS, } from "@plane/constants"; import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types"; //ui @@ -26,7 +26,8 @@ import { CreateUpdateWorkspaceViewModal } from "@/components/workspace"; // constants // helpers // hooks -import { useEventTracker, useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store"; import { getAreFiltersEqual } from "../../../utils"; type Props = { @@ -44,7 +45,6 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { } = useIssues(EIssuesStoreType.GLOBAL); const { workspaceLabels } = useLabel(); const { globalViewMap, updateGlobalView } = useGlobalView(); - const { captureEvent } = useEventTracker(); const { data } = useUser(); const { allowPermissions } = useUserPermissions(); @@ -112,15 +112,25 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { const handleUpdateView = () => { if (!workspaceSlug || !globalViewId) return; - updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters).then((res) => { - if (res) - captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, { - view_id: res.id, - applied_filters: res.filters, - state: "SUCCESS", - element: "Spreadsheet view", + updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters) + .then((res) => { + if (res) + captureSuccess({ + eventName: GLOBAL_VIEW_TRACKER_EVENTS.update, + payload: { + view_id: globalViewId, + }, + }); + }) + .catch((error) => { + captureError({ + eventName: GLOBAL_VIEW_TRACKER_EVENTS.update, + payload: { + view_id: globalViewId, + }, + error: error, }); - }); + }); }; // add a placeholder object instead of appliedFilters if it is undefined diff --git a/web/core/components/onboarding/profile-setup.tsx b/web/core/components/onboarding/profile-setup.tsx index 68c4d7c05a0..8fbafd6af8e 100644 --- a/web/core/components/onboarding/profile-setup.tsx +++ b/web/core/components/onboarding/profile-setup.tsx @@ -6,7 +6,7 @@ import Image from "next/image"; import { useTheme } from "next-themes"; import { Controller, useForm } from "react-hook-form"; import { Eye, EyeOff } from "lucide-react"; -import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_EVENTS, USER_TRACKER_EVENTS } from "@plane/constants"; +import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_ELEMENTS, USER_TRACKER_EVENTS } from "@plane/constants"; // types import { useTranslation } from "@plane/i18n"; import { IUser, TUserProfile, TOnboardingSteps } from "@plane/types"; @@ -20,7 +20,8 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding // constants // helpers // hooks -import { useEventTracker, useUser, useUserProfile } from "@/hooks/store"; +import { captureError, captureSuccess, captureView } from "@/helpers/event-tracker.helper"; +import { useUser, useUserProfile } from "@/hooks/store"; // assets import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp"; import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp"; @@ -98,7 +99,6 @@ export const ProfileSetup: React.FC = observer((props) => { // store hooks const { updateCurrentUser } = useUser(); const { updateUserProfile } = useUserProfile(); - const { captureEvent } = useEventTracker(); // form info const { getValues, @@ -143,11 +143,12 @@ export const ProfileSetup: React.FC = observer((props) => { updateUserProfile(profileUpdatePayload), totalSteps > 2 && stepChange({ profile_complete: true }), ]); - captureEvent(USER_TRACKER_EVENTS.add_details, { - use_case: formData.use_case, - role: formData.role, - state: "SUCCESS", - element: ONBOARDING_TRACKER_EVENTS.step_1, + captureSuccess({ + eventName: USER_TRACKER_EVENTS.add_details, + payload: { + use_case: formData.use_case, + role: formData.role, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -159,9 +160,8 @@ export const ProfileSetup: React.FC = observer((props) => { finishOnboarding(); } } catch { - captureEvent(USER_TRACKER_EVENTS.add_details, { - state: "FAILED", - element: ONBOARDING_TRACKER_EVENTS.step_1, + captureError({ + eventName: USER_TRACKER_EVENTS.add_details, }); setToast({ type: TOAST_TYPE.ERROR, @@ -183,9 +183,8 @@ export const ProfileSetup: React.FC = observer((props) => { formData.password && handleSetPassword(formData.password), ]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION)); } catch { - captureEvent(USER_TRACKER_EVENTS.add_details, { - state: "FAILED", - element: ONBOARDING_TRACKER_EVENTS.step_1, + captureError({ + eventName: USER_TRACKER_EVENTS.add_details, }); setToast({ type: TOAST_TYPE.ERROR, @@ -205,11 +204,12 @@ export const ProfileSetup: React.FC = observer((props) => { updateUserProfile(profileUpdatePayload), totalSteps > 2 && stepChange({ profile_complete: true }), ]); - captureEvent(USER_TRACKER_EVENTS.add_details, { - use_case: formData.use_case, - role: formData.role, - state: "SUCCESS", - element: ONBOARDING_TRACKER_EVENTS.step_2, + captureSuccess({ + eventName: USER_TRACKER_EVENTS.add_details, + payload: { + use_case: formData.use_case, + role: formData.role, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -221,9 +221,8 @@ export const ProfileSetup: React.FC = observer((props) => { finishOnboarding(); } } catch { - captureEvent(USER_TRACKER_EVENTS.add_details, { - state: "FAILED", - element: ONBOARDING_TRACKER_EVENTS.step_2, + captureError({ + eventName: USER_TRACKER_EVENTS.add_details, }); setToast({ type: TOAST_TYPE.ERROR, @@ -235,6 +234,9 @@ export const ProfileSetup: React.FC = observer((props) => { const onSubmit = async (formData: TProfileSetupFormValues) => { if (!user) return; + captureView({ + elementName: ONBOARDING_TRACKER_ELEMENTS.PROFILE_SETUP_FORM, + }); if (profileSetupStep === EProfileSetupSteps.ALL) await handleSubmitProfileSetup(formData); if (profileSetupStep === EProfileSetupSteps.USER_DETAILS) await handleSubmitUserDetail(formData); if (profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION) await handleSubmitUserPersonalization(formData); diff --git a/web/core/components/onboarding/tour/root.tsx b/web/core/components/onboarding/tour/root.tsx index 90a183e0b3f..167ee752f54 100644 --- a/web/core/components/onboarding/tour/root.tsx +++ b/web/core/components/onboarding/tour/root.tsx @@ -5,13 +5,14 @@ import { observer } from "mobx-react"; import Image, { StaticImageData } from "next/image"; import { X } from "lucide-react"; // ui -import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants"; +import { PRODUCT_TOUR_TRACKER_ELEMENTS } from "@plane/constants"; import { Button } from "@plane/ui"; // components import { TourSidebar } from "@/components/onboarding"; // constants // hooks -import { useCommandPalette, useEventTracker, useUser } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useCommandPalette, useUser } from "@/hooks/store"; // assets import CyclesTour from "@/public/onboarding/cycles.webp"; import IssuesTour from "@/public/onboarding/issues.webp"; @@ -85,7 +86,6 @@ export const TourRoot: React.FC = observer((props) => { const [step, setStep] = useState("welcome"); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement, captureEvent } = useEventTracker(); const { data: currentUser } = useUser(); const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step); @@ -112,7 +112,9 @@ export const TourRoot: React.FC = observer((props) => { {isOwner && <>{updateButton}} diff --git a/web/core/components/workspace-notifications/sidebar/header/options/root.tsx b/web/core/components/workspace-notifications/sidebar/header/options/root.tsx index 4de95375929..f15520ec326 100644 --- a/web/core/components/workspace-notifications/sidebar/header/options/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/header/options/root.tsx @@ -2,14 +2,20 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { CheckCheck, RefreshCw } from "lucide-react"; // plane imports -import { ENotificationLoader, ENotificationQueryParamType, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants"; +import { + ENotificationLoader, + ENotificationQueryParamType, + NOTIFICATION_TRACKER_ELEMENTS, + NOTIFICATION_TRACKER_EVENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Spinner, Tooltip } from "@plane/ui"; // components import { NotificationFilter, NotificationHeaderMenuOption } from "@/components/workspace-notifications"; // constants // hooks -import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store"; +import { captureSuccess } from "@/helpers/event-tracker.helper"; +import { useWorkspaceNotifications } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; type TNotificationSidebarHeaderOptions = { @@ -21,7 +27,6 @@ export const NotificationSidebarHeaderOptions: FC { @@ -49,8 +54,11 @@ export const NotificationSidebarHeaderOptions: FC
{ - captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read); + captureSuccess({ + eventName: NOTIFICATION_TRACKER_EVENTS.all_marked_read, + }); handleMarkAllNotificationsAsRead(); }} > diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx index 0c7c87411fc..402ee37f4b8 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx @@ -3,14 +3,15 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { ArchiveRestore } from "lucide-react"; -import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants"; +import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { ArchiveIcon, TOAST_TYPE, setToast } from "@plane/ui"; // components import { NotificationItemOptionButton } from "@/components/workspace-notifications"; // constants // hooks -import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store"; +import { captureClick, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useWorkspaceNotifications } from "@/hooks/store"; // store import { INotification } from "@/store/notifications/notification"; @@ -22,7 +23,6 @@ type TNotificationItemArchiveOption = { export const NotificationItemArchiveOption: FC = observer((props) => { const { workspaceSlug, notification } = props; // hooks - const { captureEvent } = useEventTracker(); const { currentNotificationTab } = useWorkspaceNotifications(); const { asJson: data, archiveNotification, unArchiveNotification } = notification; const { t } = useTranslation(); @@ -30,11 +30,16 @@ export const NotificationItemArchiveOption: FC = const handleNotificationUpdate = async () => { try { const request = data.archived_at ? unArchiveNotification : archiveNotification; + captureClick({ + elementName: NOTIFICATION_TRACKER_ELEMENTS.ARCHIVE_BUTTON, + }); await request(workspaceSlug); - captureEvent(NOTIFICATION_TRACKER_EVENTS.archive, { - issue_id: data?.data?.issue?.id, - tab: currentNotificationTab, - state: "SUCCESS", + captureSuccess({ + eventName: NOTIFICATION_TRACKER_EVENTS.archive, + payload: { + id: data?.data?.issue?.id, + tab: currentNotificationTab, + }, }); setToast({ title: data.archived_at ? t("notification.toasts.unarchived") : t("notification.toasts.archived"), diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/options/read.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/options/read.tsx index 74ca4b2fd2a..c9761d685c4 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/options/read.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/options/read.tsx @@ -3,14 +3,15 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { MessageSquare } from "lucide-react"; -import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants"; +import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { NotificationItemOptionButton } from "@/components/workspace-notifications"; // constants // hooks -import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store"; +import { captureClick, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useWorkspaceNotifications } from "@/hooks/store"; // store import { INotification } from "@/store/notifications/notification"; @@ -22,7 +23,6 @@ type TNotificationItemReadOption = { export const NotificationItemReadOption: FC = observer((props) => { const { workspaceSlug, notification } = props; // hooks - const { captureEvent } = useEventTracker(); const { currentNotificationTab } = useWorkspaceNotifications(); const { asJson: data, markNotificationAsRead, markNotificationAsUnRead } = notification; const { t } = useTranslation(); @@ -30,11 +30,16 @@ export const NotificationItemReadOption: FC = obser const handleNotificationUpdate = async () => { try { const request = data.read_at ? markNotificationAsUnRead : markNotificationAsRead; + captureClick({ + elementName: NOTIFICATION_TRACKER_ELEMENTS.MARK_READ_BUTTON, + }); await request(workspaceSlug); - captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read, { - issue_id: data?.data?.issue?.id, - tab: currentNotificationTab, - state: "SUCCESS", + captureSuccess({ + eventName: NOTIFICATION_TRACKER_EVENTS.all_marked_read, + payload: { + id: data?.data?.issue?.id, + tab: currentNotificationTab, + }, }); setToast({ title: data.read_at ? t("notification.toasts.unread") : t("notification.toasts.read"), diff --git a/web/core/components/workspace/sidebar/user-menu-item.tsx b/web/core/components/workspace/sidebar/user-menu-item.tsx index 3f89dd6875b..d3124c6659e 100644 --- a/web/core/components/workspace/sidebar/user-menu-item.tsx +++ b/web/core/components/workspace/sidebar/user-menu-item.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; // plane imports -import { EUserPermissionsLevel, EUserWorkspaceRoles, SIDEBAR_TRACKER_EVENTS } from "@plane/constants"; +import { EUserPermissionsLevel, EUserWorkspaceRoles, SIDEBAR_TRACKER_ELEMENTS } from "@plane/constants"; import { usePlatformOS } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; import { Tooltip } from "@plane/ui"; @@ -11,7 +11,8 @@ import { Tooltip } from "@plane/ui"; import { SidebarNavItem } from "@/components/sidebar"; import { NotificationAppSidebarOption } from "@/components/workspace-notifications"; // hooks -import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useAppTheme, useUserPermissions } from "@/hooks/store"; export interface SidebarUserMenuItemProps { item: { @@ -33,7 +34,6 @@ export const SidebarUserMenuItem: FC = observer((props // package hooks const { t } = useTranslation(); // store hooks - const { captureEvent } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const { toggleSidebar, sidebarCollapsed } = useAppTheme(); const { isMobile } = usePlatformOS(); @@ -49,8 +49,11 @@ export const SidebarUserMenuItem: FC = observer((props if (window.innerWidth < 768) { toggleSidebar(); } - captureEvent(SIDEBAR_TRACKER_EVENTS.click, { - destination: itemKey, + captureClick({ + elementName: SIDEBAR_TRACKER_ELEMENTS.USER_MENU_ITEM, + context: { + destination: itemKey, + }, }); }; diff --git a/web/core/components/workspace/views/delete-view-modal.tsx b/web/core/components/workspace/views/delete-view-modal.tsx index 65f4efdd4e5..0e6c5857e10 100644 --- a/web/core/components/workspace/views/delete-view-modal.tsx +++ b/web/core/components/workspace/views/delete-view-modal.tsx @@ -4,13 +4,14 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // types -import { GLOBAL_VIEW_TOUR_TRACKER_EVENTS } from "@plane/constants"; +import { GLOBAL_VIEW_TRACKER_EVENTS } from "@plane/constants"; import { IWorkspaceView } from "@plane/types"; // ui import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // constants // hooks -import { useGlobalView, useEventTracker } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useGlobalView } from "@/hooks/store"; type Props = { data: IWorkspaceView; @@ -26,7 +27,6 @@ export const DeleteGlobalViewModal: React.FC = observer((props) => { const { workspaceSlug } = useParams(); // store hooks const { deleteGlobalView } = useGlobalView(); - const { captureEvent } = useEventTracker(); const handleClose = () => onClose(); @@ -37,15 +37,20 @@ export const DeleteGlobalViewModal: React.FC = observer((props) => { await deleteGlobalView(workspaceSlug.toString(), data.id) .then(() => { - captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, { - view_id: data.id, - state: "SUCCESS", + captureSuccess({ + eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete, + payload: { + view_id: data.id, + }, }); }) .catch((error: any) => { - captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, { - view_id: data.id, - state: "FAILED", + captureError({ + eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete, + payload: { + view_id: data.id, + }, + error: error, }); setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/core/components/workspace/views/header.tsx b/web/core/components/workspace/views/header.tsx index 4aa17493c30..c61290dcd19 100644 --- a/web/core/components/workspace/views/header.tsx +++ b/web/core/components/workspace/views/header.tsx @@ -8,7 +8,8 @@ import { DEFAULT_GLOBAL_VIEWS_LIST, EUserPermissions, EUserPermissionsLevel, - GLOBAL_VIEW_TOUR_TRACKER_EVENTS, + GLOBAL_VIEW_TRACKER_ELEMENTS, + GLOBAL_VIEW_TRACKER_EVENTS, } from "@plane/constants"; import { TStaticViewTypes } from "@plane/types"; // components @@ -20,6 +21,7 @@ import { } from "@/components/workspace"; // constants // store hooks +import { captureSuccess } from "@/helpers/event-tracker.helper"; import { useEventTracker, useGlobalView, useUserPermissions } from "@/hooks/store"; const ViewTab = observer((props: { viewId: string }) => { @@ -77,11 +79,14 @@ export const GlobalViewsHeader: React.FC = observer(() => { // bring the active view to the centre of the header useEffect(() => { if (globalViewId && currentWorkspaceViews) { - captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.open, { - view_id: globalViewId, - view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString()) - ? "Default" - : "Custom", + captureSuccess({ + eventName: GLOBAL_VIEW_TRACKER_EVENTS.open, + payload: { + view_id: globalViewId, + view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString()) + ? "Default" + : "Custom", + }, }); const activeTabElement = document.querySelector(`#global-view-${globalViewId.toString()}`); if (activeTabElement && containerRef.current) { @@ -115,6 +120,7 @@ export const GlobalViewsHeader: React.FC = observer(() => { {isAuthorizedUser ? (
{canPerformWorkspaceAdminActions && ( - )} diff --git a/web/app/(all)/invitations/page.tsx b/web/app/(all)/invitations/page.tsx index 5e5f1958fe0..bb555ccc455 100644 --- a/web/app/(all)/invitations/page.tsx +++ b/web/app/(all)/invitations/page.tsx @@ -9,19 +9,20 @@ import { useTheme } from "next-themes"; import useSWR, { mutate } from "swr"; import { CheckCircle2 } from "lucide-react"; // plane imports -import { ROLE, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants"; +import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // types import type { IWorkspaceMemberInvitation } from "@plane/types"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; -import { truncateText, getUserRole } from "@plane/utils"; +import { truncateText } from "@plane/utils"; // components import { EmptyState } from "@/components/common"; import { WorkspaceLogo } from "@/components/workspace/logo"; import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys"; // helpers // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // services @@ -43,7 +44,7 @@ const UserInvitationsPage = observer(() => { const router = useAppRouter(); // store hooks const { t } = useTranslation(); - const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker(); + const { joinWorkspaceMetricGroup } = useEventTracker(); const { data: currentUser } = useUser(); const { updateUserProfile } = useUserProfile(); @@ -86,14 +87,11 @@ const UserInvitationsPage = observer(() => { const invitation = invitations?.find((i) => i.id === firstInviteId); const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace; joinWorkspaceMetricGroup(redirectWorkspace?.id); - captureEvent(MEMBER_TRACKER_EVENTS.accept, { - member_id: invitation?.id, - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - role: getUserRole((invitation?.role as unknown as EUserPermissions)!), - project_id: undefined, - accepted_from: "App", - state: "SUCCESS", - element: "Workspace invitations page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.accept, + payload: { + member_id: invitation?.id, + }, }); updateUserProfile({ last_workspace_id: redirectWorkspace?.id }) .then(() => { @@ -111,12 +109,13 @@ const UserInvitationsPage = observer(() => { setIsJoiningWorkspaces(false); }); }) - .catch(() => { - captureEvent(MEMBER_TRACKER_EVENTS.accept, { - project_id: undefined, - accepted_from: "App", - state: "FAILED", - element: "Workspace invitations page", + .catch((err) => { + captureError({ + eventName: MEMBER_TRACKER_EVENTS.accept, + payload: { + member_id: invitationsRespond?.[0], + }, + error: err, }); setToast({ type: TOAST_TYPE.ERROR, @@ -194,6 +193,7 @@ const UserInvitationsPage = observer(() => { onClick={submitInvitations} disabled={isJoiningWorkspaces || invitationsRespond.length === 0} loading={isJoiningWorkspaces} + data-ph-element={MEMBER_TRACKER_ELEMENTS.ACCEPT_INVITATION_BUTTON} > {t("accept_and_join")} diff --git a/web/core/components/onboarding/invitations.tsx b/web/core/components/onboarding/invitations.tsx index 349b9143101..3d16af81b0e 100644 --- a/web/core/components/onboarding/invitations.tsx +++ b/web/core/components/onboarding/invitations.tsx @@ -2,17 +2,18 @@ import React, { useState } from "react"; // plane imports -import { ROLE, MEMBER_TRACKER_EVENTS } from "@plane/constants"; +import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; // types import { IWorkspaceMemberInvitation } from "@plane/types"; // ui import { Button, Checkbox, Spinner } from "@plane/ui"; -import { truncateText, getUserRole } from "@plane/utils"; +import { truncateText } from "@plane/utils"; // constants // helpers import { WorkspaceLogo } from "@/components/workspace/logo"; // hooks -import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useUserSettings, useWorkspace } from "@/hooks/store"; // services import { WorkspaceService } from "@/plane-web/services"; @@ -29,7 +30,6 @@ export const Invitations: React.FC = (props) => { const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); const [invitationsRespond, setInvitationsRespond] = useState([]); // store hooks - const { captureEvent } = useEventTracker(); const { fetchWorkspaces } = useWorkspace(); const { fetchCurrentUserSettings } = useUserSettings(); @@ -50,26 +50,23 @@ export const Invitations: React.FC = (props) => { try { await workspaceService.joinWorkspaces({ invitations: invitationsRespond }); - captureEvent(MEMBER_TRACKER_EVENTS.accept, { - member_id: invitation?.id, - role: getUserRole(invitation?.role as any), - project_id: undefined, - accepted_from: "App", - state: "SUCCESS", - element: "Workspace invitations page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.accept, + payload: { + member_id: invitation?.id, + }, }); await fetchWorkspaces(); await fetchCurrentUserSettings(); await handleNextStep(); - } catch (error) { + } catch (error: any) { console.error(error); - captureEvent(MEMBER_TRACKER_EVENTS.accept, { - member_id: invitation?.id, - role: getUserRole(invitation?.role as any), - project_id: undefined, - accepted_from: "App", - state: "FAILED", - element: "Workspace invitations page", + captureError({ + eventName: MEMBER_TRACKER_EVENTS.accept, + payload: { + member_id: invitation?.id, + }, + error: error, }); setIsJoiningWorkspaces(false); } @@ -117,6 +114,7 @@ export const Invitations: React.FC = (props) => { className="w-full" onClick={submitInvitations} disabled={isJoiningWorkspaces || !invitationsRespond.length} + data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_JOIN_WORKSPACE} > {isJoiningWorkspaces ? : "Continue to workspace"} diff --git a/web/core/components/onboarding/invite-members.tsx b/web/core/components/onboarding/invite-members.tsx index 6865b78565b..bb7b24f7a7b 100644 --- a/web/core/components/onboarding/invite-members.tsx +++ b/web/core/components/onboarding/invite-members.tsx @@ -20,7 +20,7 @@ import { usePopper } from "react-popper"; import { Check, ChevronDown, Plus, XCircle } from "lucide-react"; import { Listbox } from "@headlessui/react"; // plane imports -import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants"; +import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // types import { IUser, IWorkspace } from "@plane/types"; @@ -28,9 +28,8 @@ import { IUser, IWorkspace } from "@plane/types"; import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // constants // helpers -import { getUserRole } from "@plane/utils"; // hooks -import { useEventTracker } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; // services import { WorkspaceService } from "@/plane-web/services"; // assets @@ -276,8 +275,6 @@ export const InviteMembers: React.FC = (props) => { const [isInvitationDisabled, setIsInvitationDisabled] = useState(true); const { resolvedTheme } = useTheme(); - // store hooks - const { captureEvent } = useEventTracker(); const { control, @@ -311,16 +308,11 @@ export const InviteMembers: React.FC = (props) => { })), }) .then(async () => { - captureEvent(MEMBER_TRACKER_EVENTS.invite, { - emails: [ - ...payload.emails.map((email) => ({ - email: email.email, - role: getUserRole(email.role), - })), - ], - project_id: undefined, - state: "SUCCESS", - element: "Onboarding", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + workspace: workspace.slug, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -331,10 +323,12 @@ export const InviteMembers: React.FC = (props) => { await nextStep(); }) .catch((err) => { - captureEvent(MEMBER_TRACKER_EVENTS.invite, { - project_id: undefined, - state: "FAILED", - element: "Onboarding", + captureError({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + workspace: workspace.slug, + }, + error: err, }); setToast({ type: TOAST_TYPE.ERROR, @@ -426,6 +420,7 @@ export const InviteMembers: React.FC = (props) => { size="lg" className="w-full" disabled={isInvitationDisabled || !isValid || isSubmitting} + data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_INVITE_MEMBER} > {isSubmitting ? : "Continue"} diff --git a/web/core/components/project/leave-project-modal.tsx b/web/core/components/project/leave-project-modal.tsx index 3e03c579bfd..7f9bde99f72 100644 --- a/web/core/components/project/leave-project-modal.tsx +++ b/web/core/components/project/leave-project-modal.tsx @@ -14,7 +14,8 @@ import { IProject } from "@plane/types"; import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // constants // hooks -import { useEventTracker, useUserPermissions } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; type FormData = { @@ -39,7 +40,6 @@ export const LeaveProjectModal: FC = observer((props) => { const router = useAppRouter(); const { workspaceSlug } = useParams(); // store hooks - const { captureEvent } = useEventTracker(); const { leaveProject } = useUserPermissions(); const { @@ -64,21 +64,26 @@ export const LeaveProjectModal: FC = observer((props) => { return leaveProject(workspaceSlug.toString(), project.id) .then(() => { handleClose(); - captureEvent(MEMBER_TRACKER_EVENTS.project.leave, { - state: "SUCCESS", - element: "Project settings members page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.project.leave, + payload: { + project: project.id, + }, }); }) - .catch(() => { + .catch((err) => { + captureError({ + eventName: MEMBER_TRACKER_EVENTS.project.leave, + payload: { + project: project.id, + }, + error: err, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong please try again later.", }); - captureEvent(MEMBER_TRACKER_EVENTS.project.leave, { - state: "FAILED", - element: "Project settings members page", - }); }); } else { setToast({ diff --git a/web/core/components/project/member-list-item.tsx b/web/core/components/project/member-list-item.tsx index 619f3b29263..d729c48b93f 100644 --- a/web/core/components/project/member-list-item.tsx +++ b/web/core/components/project/member-list-item.tsx @@ -7,7 +7,8 @@ import { TOAST_TYPE, Table, setToast } from "@plane/ui"; // components import { ConfirmProjectMemberRemove } from "@/components/project"; // hooks -import { useEventTracker, useMember, useUser, useUserPermissions } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useMember, useUser, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane web imports import { useProjectColumns } from "@/plane-web/components/projects/settings/useProjectColumns"; @@ -30,7 +31,6 @@ export const ProjectMemberListItem: React.FC = observer((props) => { const { project: { removeMemberFromProject }, } = useMember(); - const { captureEvent } = useEventTracker(); // helper hooks const { columns, removeMemberModal, setRemoveMemberModal } = useProjectColumns({ projectId, @@ -44,18 +44,27 @@ export const ProjectMemberListItem: React.FC = observer((props) => { await leaveProject(workspaceSlug.toString(), projectId.toString()) .then(async () => { router.push(`/${workspaceSlug}/projects`); - captureEvent(MEMBER_TRACKER_EVENTS.project.leave, { - state: "SUCCESS", - element: "Project settings members page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.project.leave, + payload: { + project: projectId, + }, }); }) - .catch((err) => + .catch((err) => { + captureError({ + eventName: MEMBER_TRACKER_EVENTS.project.leave, + payload: { + project: projectId, + }, + error: err, + }); setToast({ type: TOAST_TYPE.ERROR, title: "You can’t leave this project yet.", message: err?.error || "Something went wrong. Please try again.", - }) - ); + }); + }); } else await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), memberId).catch((err) => setToast({ diff --git a/web/core/components/project/member-list.tsx b/web/core/components/project/member-list.tsx index 2314bb10e44..ff9d63564eb 100644 --- a/web/core/components/project/member-list.tsx +++ b/web/core/components/project/member-list.tsx @@ -4,14 +4,14 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { Search } from "lucide-react"; // plane imports -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; // components import { ProjectMemberListItem, SendProjectInvitationModal } from "@/components/project"; import { MembersSettingsLoader } from "@/components/ui"; // hooks -import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store"; +import { useMember, useUserPermissions } from "@/hooks/store"; type TProjectMemberListProps = { projectId: string; @@ -23,8 +23,6 @@ export const ProjectMemberList: React.FC = observer((pr // states const [inviteModal, setInviteModal] = useState(false); const [searchQuery, setSearchQuery] = useState(""); - // store hooks - const { setTrackElement } = useEventTracker(); const { project: { projectMemberIds, getProjectMemberDetails }, } = useMember(); @@ -73,9 +71,9 @@ export const ProjectMemberList: React.FC = observer((pr variant="primary" size="sm" onClick={() => { - setTrackElement("PROJECT_SETTINGS_MEMBERS_PAGE_HEADER"); setInviteModal(true); }} + data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON} > {t("add_member")} diff --git a/web/core/components/project/send-project-invitation-modal.tsx b/web/core/components/project/send-project-invitation-modal.tsx index 114ee3a4f1e..561dcd6a832 100644 --- a/web/core/components/project/send-project-invitation-modal.tsx +++ b/web/core/components/project/send-project-invitation-modal.tsx @@ -12,7 +12,8 @@ import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast // helpers import { getFileURL } from "@plane/utils"; // hooks -import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useMember, useUserPermissions } from "@/hooks/store"; type Props = { isOpen: boolean; @@ -45,7 +46,6 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { // plane hooks const { t } = useTranslation(); // store hooks - const { captureEvent } = useEventTracker(); const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions(); const { project: { getProjectMemberDetails, bulkAddMembersToProject }, @@ -86,22 +86,22 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { type: TOAST_TYPE.SUCCESS, message: "Members added successfully.", }); - captureEvent(MEMBER_TRACKER_EVENTS.project.add, { - members: [ - ...payload.members.map((member) => ({ - member_id: member.member_id, - role: ROLE[member.role], - })), - ], - state: "SUCCESS", - element: "Project settings members page", + + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.project.add, + payload: { + members: [...payload.members.map((member) => member.member_id)], + }, }); }) .catch((error) => { console.error(error); - captureEvent(MEMBER_TRACKER_EVENTS.project.add, { - state: "FAILED", - element: "Project settings members page", + captureError({ + eventName: MEMBER_TRACKER_EVENTS.project.add, + payload: { + members: [...payload.members.map((member) => member.member_id)], + }, + error: error, }); }) .finally(() => { diff --git a/web/core/components/project/settings/member-columns.tsx b/web/core/components/project/settings/member-columns.tsx index 393a4012a60..4de2392a791 100644 --- a/web/core/components/project/settings/member-columns.tsx +++ b/web/core/components/project/settings/member-columns.tsx @@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form"; import { CircleMinus } from "lucide-react"; import { Disclosure } from "@headlessui/react"; // plane imports -import { ROLE, EUserPermissions, EUserProjectRoles } from "@plane/constants"; +import { ROLE, EUserPermissions, EUserProjectRoles, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { IUser, IWorkspaceMember, TProjectMembership } from "@plane/types"; import { CustomMenu, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; import { getFileURL } from "@plane/utils"; @@ -70,6 +70,7 @@ export const NameColumn: React.FC = (props) => {
setRemoveMemberModal(rowData)} > diff --git a/web/core/components/workspace/settings/invitations-list-item.tsx b/web/core/components/workspace/settings/invitations-list-item.tsx index dc815b52cc3..1fa1fce2f60 100644 --- a/web/core/components/workspace/settings/invitations-list-item.tsx +++ b/web/core/components/workspace/settings/invitations-list-item.tsx @@ -5,13 +5,14 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { ChevronDown, LinkIcon, Trash2 } from "lucide-react"; // plane imports -import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { CustomSelect, TOAST_TYPE, setToast, TContextMenuItem, CustomMenu } from "@plane/ui"; import { cn, copyTextToClipboard } from "@plane/utils"; // components import { ConfirmWorkspaceMemberRemove } from "@/components/workspace"; // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useMember, useUserPermissions } from "@/hooks/store"; type Props = { @@ -93,7 +94,12 @@ export const WorkspaceInvitationsListItem: FC = observer((props) => { }, { key: "remove", - action: () => setRemoveMemberModal(true), + action: () => { + captureClick({ + elementName: MEMBER_TRACKER_ELEMENTS.WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU, + }); + setRemoveMemberModal(true); + }, title: t("common.remove"), icon: Trash2, shouldRender: isAdmin, diff --git a/web/core/components/workspace/settings/member-columns.tsx b/web/core/components/workspace/settings/member-columns.tsx index 1e5036fefc2..edff3f15ad4 100644 --- a/web/core/components/workspace/settings/member-columns.tsx +++ b/web/core/components/workspace/settings/member-columns.tsx @@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form"; import { Trash2 } from "lucide-react"; import { Disclosure } from "@headlessui/react"; // plane imports -import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { IUser, IWorkspaceMember } from "@plane/types"; // plane ui import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui"; @@ -74,6 +74,7 @@ export const NameColumn: React.FC = (props) => {
setRemoveMemberModal(rowData)} + data-ph-element={MEMBER_TRACKER_ELEMENTS.WORKSPACE_MEMBER_TABLE_CONTEXT_MENU} > {id === currentUser?.id ? "Leave " : "Remove "}
diff --git a/web/core/components/workspace/settings/members-list-item.tsx b/web/core/components/workspace/settings/members-list-item.tsx index 978b757b368..db7e7e2753e 100644 --- a/web/core/components/workspace/settings/members-list-item.tsx +++ b/web/core/components/workspace/settings/members-list-item.tsx @@ -13,7 +13,8 @@ import { MembersLayoutLoader } from "@/components/ui/loader/layouts/members-layo import { ConfirmWorkspaceMemberRemove } from "@/components/workspace"; // constants // hooks -import { useEventTracker, useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { useMemberColumns } from "@/plane-web/components/workspace/settings/useMemberColumns"; @@ -32,7 +33,6 @@ export const WorkspaceMembersListItem: FC = observer((props) => { workspace: { removeMemberFromWorkspace }, } = useMember(); const { leaveWorkspace } = useUserPermissions(); - const { captureEvent } = useEventTracker(); const { getWorkspaceRedirectionUrl } = useWorkspace(); const { fetchCurrentUserSettings } = useUserSettings(); const { t } = useTranslation(); @@ -45,18 +45,27 @@ export const WorkspaceMembersListItem: FC = observer((props) => { .then(async () => { await fetchCurrentUserSettings(); router.push(getWorkspaceRedirectionUrl()); - captureEvent(MEMBER_TRACKER_EVENTS.workspace.leave, { - state: "SUCCESS", - element: "Workspace settings members page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.workspace.leave, + payload: { + workspace: workspaceSlug, + }, }); }) - .catch((err: any) => + .catch((err: any) => { + captureError({ + eventName: MEMBER_TRACKER_EVENTS.workspace.leave, + payload: { + workspace: workspaceSlug, + }, + error: err, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error || t("something_went_wrong_please_try_again"), - }) - ); + }); + }); }; const handleRemoveMember = async (memberId: string) => { diff --git a/web/core/components/workspace/sidebar/projects-list-item.tsx b/web/core/components/workspace/sidebar/projects-list-item.tsx index d6f4365793c..55278b0477b 100644 --- a/web/core/components/workspace/sidebar/projects-list-item.tsx +++ b/web/core/components/workspace/sidebar/projects-list-item.tsx @@ -12,7 +12,7 @@ import { createRoot } from "react-dom/client"; import { LinkIcon, Settings, Share2, LogOut, MoreHorizontal, ChevronRight } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // plane helpers -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; // ui @@ -23,7 +23,7 @@ import { Logo } from "@/components/common/logo"; import { LeaveProjectModal, PublishProjectModal } from "@/components/project"; // helpers // hooks -import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; +import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane-web components import { ProjectNavigationRoot } from "@/plane-web/components/sidebar"; @@ -59,7 +59,6 @@ export const SidebarProjectsListItem: React.FC = observer((props) => { // store hooks const { sidebarCollapsed } = useAppTheme(); const { t } = useTranslation(); - const { setTrackElement } = useEventTracker(); const { getPartialProjectById } = useProject(); const { isMobile } = usePlatformOS(); const { allowPermissions } = useUserPermissions(); @@ -97,7 +96,6 @@ export const SidebarProjectsListItem: React.FC = observer((props) => { ); const handleLeaveProject = () => { - setTrackElement("APP_SIDEBAR_PROJECT_DROPDOWN"); setLeaveProjectModal(true); }; @@ -376,7 +374,10 @@ export const SidebarProjectsListItem: React.FC = observer((props) => { {/* leave project */} {!isAuthorized && ( - +
{t("leave_project")} From 37fd368833c2309159b483abc82dbdac36a62ae7 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 16:10:51 +0530 Subject: [PATCH 21/38] refactor: update event tracker constants --- packages/constants/src/event-tracker/core.ts | 7 +++++-- .../sidebar/notification-card/options/archive.tsx | 15 ++++++++++----- .../sidebar/notification-card/options/read.tsx | 15 ++++++++++----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index 238db14b0fe..e531fc27f2f 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -242,12 +242,15 @@ export const PRODUCT_TOUR_TRACKER_ELEMENTS = { export const NOTIFICATION_TRACKER_EVENTS = { archive: "notification_archived", + unarchive: "notification_unarchived", + mark_read: "notification_marked_read", + mark_unread: "notification_marked_unread", all_marked_read: "all_notifications_marked_read", }; export const NOTIFICATION_TRACKER_ELEMENTS = { MARK_ALL_AS_READ_BUTTON: "mark_all_as_read_button", - ARCHIVE_BUTTON: "archive_button", - MARK_READ_BUTTON: "mark_read_button", + ARCHIVE_UNARCHIVE_BUTTON: "archive_unarchive_button", + MARK_READ_UNREAD_BUTTON: "mark_read_unread_button", }; export const USER_TRACKER_EVENTS = { diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx index 402ee37f4b8..9a03bd71143 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx @@ -10,7 +10,7 @@ import { ArchiveIcon, TOAST_TYPE, setToast } from "@plane/ui"; import { NotificationItemOptionButton } from "@/components/workspace-notifications"; // constants // hooks -import { captureClick, captureSuccess } from "@/helpers/event-tracker.helper"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useWorkspaceNotifications } from "@/hooks/store"; // store import { INotification } from "@/store/notifications/notification"; @@ -30,12 +30,9 @@ export const NotificationItemArchiveOption: FC = const handleNotificationUpdate = async () => { try { const request = data.archived_at ? unArchiveNotification : archiveNotification; - captureClick({ - elementName: NOTIFICATION_TRACKER_ELEMENTS.ARCHIVE_BUTTON, - }); await request(workspaceSlug); captureSuccess({ - eventName: NOTIFICATION_TRACKER_EVENTS.archive, + eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive, payload: { id: data?.data?.issue?.id, tab: currentNotificationTab, @@ -47,11 +44,19 @@ export const NotificationItemArchiveOption: FC = }); } catch (e) { console.error(e); + captureError({ + eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive, + payload: { + id: data?.data?.issue?.id, + tab: currentNotificationTab, + }, + }); } }; return ( = obser const handleNotificationUpdate = async () => { try { const request = data.read_at ? markNotificationAsUnRead : markNotificationAsRead; - captureClick({ - elementName: NOTIFICATION_TRACKER_ELEMENTS.MARK_READ_BUTTON, - }); await request(workspaceSlug); captureSuccess({ - eventName: NOTIFICATION_TRACKER_EVENTS.all_marked_read, + eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read, payload: { id: data?.data?.issue?.id, tab: currentNotificationTab, @@ -47,11 +44,19 @@ export const NotificationItemReadOption: FC = obser }); } catch (e) { console.error(e); + captureError({ + eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read, + payload: { + id: data?.data?.issue?.id, + tab: currentNotificationTab, + }, + }); } }; return ( From 72d9318994d1f2d6bf4ddd925c894010b428f97f Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 16:52:33 +0530 Subject: [PATCH 22/38] refactor: auth related event trackers (#7306) --- packages/constants/src/event-tracker/core.ts | 16 ++++--- .../(all)/accounts/forgot-password/page.tsx | 23 +++++---- web/app/(all)/sign-up/page.tsx | 8 +--- web/app/(home)/page.tsx | 8 ++-- .../account/auth-forms/password.tsx | 37 ++++++++++----- .../account/auth-forms/unique-code.tsx | 47 +++++++++++++++---- 6 files changed, 92 insertions(+), 47 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index e531fc27f2f..1bb180f79ca 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -194,7 +194,6 @@ export const MEMBER_TRACKER_EVENTS = { leave: "workspace_member_left", }, }; - export const MEMBER_TRACKER_ELEMENTS = { HEADER_ADD_BUTTON: "header_add_member_button", ACCEPT_INVITATION_BUTTON: "accept_invitation_button", @@ -207,15 +206,20 @@ export const MEMBER_TRACKER_ELEMENTS = { } as const; export const AUTH_TRACKER_EVENTS = { - navigate: { - sign_up: "navigate_to_sign_up_page", - sign_in: "navigate_to_sign_in_page", - }, code_verify: "code_verified", sign_up_with_password: "sign_up_with_password", sign_in_with_password: "sign_in_with_password", - sign_in_with_code: "sign_in_with_magic_link", forgot_password: "forgot_password_clicked", + new_code_requested: "new_code_requested", +}; +export const AUTH_TRACKER_ELEMENTS = { + NAVIGATE_TO_SIGN_UP: "navigate_to_sign_up", + FORGOT_PASSWORD_FROM_SIGNIN: "forgot_password_from_signin", + SIGNUP_FROM_FORGOT_PASSWORD: "signup_from_forgot_password", + SIGN_IN_FROM_SIGNUP: "sign_in_from_signup", + SIGN_IN_WITH_UNIQUE_CODE: "sign_in_with_unique_code", + REQUEST_NEW_CODE: "request_new_code", + VERIFY_CODE: "verify_code", }; export const GLOBAL_VIEW_TRACKER_EVENTS = { diff --git a/web/app/(all)/accounts/forgot-password/page.tsx b/web/app/(all)/accounts/forgot-password/page.tsx index a7e3e7ed407..fa9ff935859 100644 --- a/web/app/(all)/accounts/forgot-password/page.tsx +++ b/web/app/(all)/accounts/forgot-password/page.tsx @@ -9,14 +9,15 @@ import { Controller, useForm } from "react-hook-form"; // icons import { CircleCheck } from "lucide-react"; // plane imports -import { AUTH_TRACKER_EVENTS } from "@plane/constants"; +import { AUTH_TRACKER_ELEMENTS, AUTH_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; import { cn, checkEmailValidity } from "@plane/utils"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; // hooks -import { useEventTracker, useInstance } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useInstance } from "@/hooks/store"; import useTimer from "@/hooks/use-timer"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; @@ -45,8 +46,6 @@ const ForgotPasswordPage = observer(() => { const email = searchParams.get("email"); // plane hooks const { t } = useTranslation(); - // store hooks - const { captureEvent } = useEventTracker(); const { config } = useInstance(); // hooks const { resolvedTheme } = useTheme(); @@ -71,8 +70,11 @@ const ForgotPasswordPage = observer(() => { email: formData.email, }) .then(() => { - captureEvent(AUTH_TRACKER_EVENTS.forgot_password, { - state: "SUCCESS", + captureSuccess({ + eventName: AUTH_TRACKER_EVENTS.forgot_password, + payload: { + email: formData.email, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -82,8 +84,11 @@ const ForgotPasswordPage = observer(() => { setResendCodeTimer(30); }) .catch((err) => { - captureEvent(AUTH_TRACKER_EVENTS.forgot_password, { - state: "FAILED", + captureError({ + eventName: AUTH_TRACKER_EVENTS.forgot_password, + payload: { + email: formData.email, + }, }); setToast({ type: TOAST_TYPE.ERROR, @@ -120,7 +125,7 @@ const ForgotPasswordPage = observer(() => { {t("auth.common.new_to_plane")} captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})} + data-ph-element={AUTH_TRACKER_ELEMENTS.SIGNUP_FROM_FORGOT_PASSWORD} className="font-semibold text-custom-primary-100 hover:underline" > {t("auth.common.create_account")} diff --git a/web/app/(all)/sign-up/page.tsx b/web/app/(all)/sign-up/page.tsx index 786b9738437..25deaf8f167 100644 --- a/web/app/(all)/sign-up/page.tsx +++ b/web/app/(all)/sign-up/page.tsx @@ -6,14 +6,12 @@ import Link from "next/link"; // ui import { useTheme } from "next-themes"; // components -import { AUTH_TRACKER_EVENTS } from "@plane/constants"; +import { AUTH_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { AuthRoot } from "@/components/account"; // constants // helpers import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; -// hooks -import { useEventTracker } from "@/hooks/store"; // assets import { AuthenticationWrapper } from "@/lib/wrappers"; import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg"; @@ -26,8 +24,6 @@ export type AuthType = "sign-in" | "sign-up"; const SignInPage = observer(() => { // plane hooks const { t } = useTranslation(); - // store hooks - const { captureEvent } = useEventTracker(); // hooks const { resolvedTheme } = useTheme(); @@ -54,7 +50,7 @@ const SignInPage = observer(() => { {t("auth.common.already_have_an_account")} captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_in, {})} + data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_FROM_SIGNUP} className="font-semibold text-custom-primary-100 hover:underline" > {t("auth.common.login")} diff --git a/web/app/(home)/page.tsx b/web/app/(home)/page.tsx index 21e5c12d07c..c9f0c485011 100644 --- a/web/app/(home)/page.tsx +++ b/web/app/(home)/page.tsx @@ -6,7 +6,7 @@ import Link from "next/link"; // ui import { useTheme } from "next-themes"; // components -import { AUTH_TRACKER_EVENTS } from "@plane/constants"; +import { AUTH_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { AuthRoot } from "@/components/account"; import { PageHead } from "@/components/core"; @@ -14,7 +14,7 @@ import { PageHead } from "@/components/core"; // helpers import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; // hooks -import { useEventTracker, useInstance } from "@/hooks/store"; +import { useInstance } from "@/hooks/store"; // layouts import DefaultLayout from "@/layouts/default-layout"; // wrappers @@ -29,8 +29,6 @@ const HomePage = observer(() => { const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); - // hooks - const { captureEvent } = useEventTracker(); // store const { config } = useInstance(); // derived values @@ -63,7 +61,7 @@ const HomePage = observer(() => { {t("auth.common.new_to_plane")} captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})} + data-ph-element={AUTH_TRACKER_ELEMENTS.NAVIGATE_TO_SIGN_UP} className="font-semibold text-custom-primary-100 hover:underline" > {t("auth.common.create_account")} diff --git a/web/core/components/account/auth-forms/password.tsx b/web/core/components/account/auth-forms/password.tsx index 3c2927418dd..6e05d0c22b4 100644 --- a/web/core/components/account/auth-forms/password.tsx +++ b/web/core/components/account/auth-forms/password.tsx @@ -6,7 +6,7 @@ import Link from "next/link"; // icons import { Eye, EyeOff, Info, X, XCircle } from "lucide-react"; // plane imports -import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS } from "@plane/constants"; +import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS, AUTH_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Button, Input, Spinner } from "@plane/ui"; import { getPasswordStrength } from "@plane/utils"; @@ -16,7 +16,7 @@ import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/accou // helpers import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper"; // hooks -import { useEventTracker } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; // services import { AuthService } from "@/services/auth.service"; @@ -46,8 +46,6 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props; // plane imports const { t } = useTranslation(); - // hooks - const { captureEvent } = useEventTracker(); // ref const formRef = useRef(null); // states @@ -77,7 +75,6 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { const redirectToUniqueCodeSignIn = async () => { handleAuthStep(EAuthSteps.UNIQUE_CODE); - captureEvent(AUTH_TRACKER_EVENTS.sign_in_with_code); }; const passwordSupport = @@ -85,7 +82,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
{isSMTPConfigured ? ( captureEvent(AUTH_TRACKER_EVENTS.forgot_password)} + data-ph-element={AUTH_TRACKER_ELEMENTS.FORGOT_PASSWORD_FROM_SIGNIN} href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`} className="text-xs font-medium text-custom-primary-100" > @@ -154,17 +151,32 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { : true; if (isPasswordValid) { setIsSubmitting(true); - captureEvent( - mode === EAuthModes.SIGN_IN - ? AUTH_TRACKER_EVENTS.sign_in_with_password - : AUTH_TRACKER_EVENTS.sign_up_with_password - ); + captureSuccess({ + eventName: + mode === EAuthModes.SIGN_IN + ? AUTH_TRACKER_EVENTS.sign_in_with_password + : AUTH_TRACKER_EVENTS.sign_up_with_password, + payload: { + email: passwordFormData.email, + }, + }); if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met } else { setBannerMessage(true); } }} - onError={() => setIsSubmitting(false)} + onError={() => { + setIsSubmitting(false); + captureError({ + eventName: + mode === EAuthModes.SIGN_IN + ? AUTH_TRACKER_EVENTS.sign_in_with_password + : AUTH_TRACKER_EVENTS.sign_up_with_password, + payload: { + email: passwordFormData.email, + }, + }); + }} > @@ -292,6 +304,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { {isSMTPConfigured && (
-
From b17ee7282f21d15b2386fed69221460c1765f2ed Mon Sep 17 00:00:00 2001 From: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:09:15 +0530 Subject: [PATCH 24/38] chore: project events (#7305) * chore: project-events * fix: refactor --- packages/constants/src/event-tracker/core.ts | 26 ++++++++-------- .../(projects)/analytics/[tabId]/page.tsx | 4 ++- .../(projects)/extended-project-sidebar.tsx | 3 +- .../(settings)/settings/projects/page.tsx | 7 ++++- web/ce/components/projects/create/root.tsx | 21 ++++++++----- web/ce/helpers/command-palette.ts | 7 +++-- .../command-palette/command-modal.tsx | 10 ++++-- .../home/widgets/empty-states/no-projects.tsx | 4 ++- .../integration/jira/give-details.tsx | 2 ++ .../empty-states/global-view.tsx | 9 +++++- .../issues/workspace-draft/root.tsx | 4 ++- web/core/components/project/card-list.tsx | 8 ++--- .../project/delete-project-modal.tsx | 16 ++++++---- web/core/components/project/form.tsx | 31 ++++++++++--------- web/core/components/project/header.tsx | 8 ++--- .../settings/delete-project-section.tsx | 7 ++++- .../workspace/sidebar/projects-list.tsx | 9 +++--- .../layouts/auth-layout/project-wrapper.tsx | 5 +-- web/core/store/event-tracker.store.ts | 20 ------------ 19 files changed, 114 insertions(+), 87 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index 251432224c1..c1f911cca62 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -12,19 +12,6 @@ export type EventProps = { path?: string; }; -export const getProjectEventPayload = (payload: any) => ({ - workspace_id: payload.workspace_id, - project_id: payload.id, - identifier: payload.identifier, - project_visibility: payload.network == 2 ? "Public" : "Private", - changed_properties: payload.changed_properties, - lead_id: payload.project_lead, - created_at: payload.created_at, - updated_at: payload.updated_at, - state: payload.state, - element: payload.element, -}); - export const getPageEventPayload = (payload: any) => ({ workspace_id: payload.workspace_id, project_id: payload.project, @@ -119,6 +106,19 @@ export const PROJECT_TRACKER_EVENTS = { delete: "project_deleted", }; +export const PROJECT_TRACKER_ELEMENTS = { + EXTENDED_SIDEBAR_ADD_BUTTON: "extended_sidebar_add_project_button", + SIDEBAR_CREATE_PROJECT_BUTTON: "sidebar_create_project_button", + SIDEBAR_CREATE_PROJECT_TOOLTIP: "sidebar_create_project_tooltip", + COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_project_button", + COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_project_button", + EMPTY_STATE_CREATE_PROJECT_BUTTON: "empty_state_create_project_button", + CREATE_HEADER_BUTTON: "create_project_header_button", + CREATE_FIRST_PROJECT_BUTTON: "create_first_project_button", + DELETE_PROJECT_BUTTON: "delete_project_button", + UPDATE_PROJECT_BUTTON: "update_project_button", +}; + export const CYCLE_TRACKER_EVENTS = { create: "cycle_created", update: "cycle_updated", diff --git a/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx b/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx index 6100bc8d506..b72ef0fd5c8 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx @@ -4,7 +4,7 @@ import { useMemo } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/navigation"; // plane package imports -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { type TabItem, Tabs } from "@plane/ui"; // components @@ -12,6 +12,7 @@ import AnalyticsFilterActions from "@/components/analytics/analytics-filter-acti import { PageHead } from "@/components/core"; import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs"; @@ -103,6 +104,7 @@ const AnalyticsPage = observer((props: Props) => { onClick={() => { setTrackElement("Analytics empty state"); toggleCreateProjectModal(true); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON }); }} disabled={!canPerformEmptyStateActions} /> diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx index 0cd87200c9f..d33acbeb4fd 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // plane imports import { Plus, Search } from "lucide-react"; -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui"; import { cn, copyUrlToClipboard, orderJoinedProjects } from "@plane/utils"; @@ -122,6 +122,7 @@ export const ExtendedProjectSidebar = observer(() => {
diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx index 18f4878fb5b..c6b0552912a 100644 --- a/web/ce/components/projects/create/root.tsx +++ b/web/ce/components/projects/create/root.tsx @@ -12,7 +12,8 @@ import ProjectCommonAttributes from "@/components/project/create/common-attribut import ProjectCreateHeader from "@/components/project/create/header"; import ProjectCreateButtons from "@/components/project/create/project-create-buttons"; // hooks -import { useEventTracker, useProject } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useProject } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web types import { TProject } from "@/plane-web/types/projects"; @@ -32,7 +33,6 @@ export const CreateProjectForm: FC = observer((props) = const { setToFavorite, workspaceSlug, data, onClose, handleNextStep, updateCoverImageStatus } = props; // store const { t } = useTranslation(); - const { captureProjectEvent } = useEventTracker(); const { addProjectToFavorites, createProject } = useProject(); // states const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true); @@ -70,25 +70,30 @@ export const CreateProjectForm: FC = observer((props) = if (coverImage) { await updateCoverImageStatus(res.id, coverImage); } - const newPayload = { - ...res, - state: "SUCCESS", - }; - captureProjectEvent({ + captureSuccess({ eventName: PROJECT_TRACKER_EVENTS.create, - payload: newPayload, + payload: { + identifier: formData.identifier, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("success"), message: t("project_created_successfully"), }); + if (setToFavorite) { handleAddToFavorites(res.id); } handleNextStep(res.id); }) .catch((err) => { + captureError({ + eventName: PROJECT_TRACKER_EVENTS.create, + payload: { + identifier: formData.identifier, + }, + }); if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") { setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/ce/helpers/command-palette.ts b/web/ce/helpers/command-palette.ts index f10b2d979b3..6c40cf0efa7 100644 --- a/web/ce/helpers/command-palette.ts +++ b/web/ce/helpers/command-palette.ts @@ -1,5 +1,5 @@ // types -import { CYCLE_TRACKER_ELEMENTS, MODULE_TRACKER_ELEMENTS } from "@plane/constants"; +import { CYCLE_TRACKER_ELEMENTS, MODULE_TRACKER_ELEMENTS, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types"; // store import { captureClick } from "@/helpers/event-tracker.helper"; @@ -24,7 +24,10 @@ export const getWorkspaceShortcutsList: () => TCommandPaletteActionList = () => p: { title: "Create a new project", description: "Create a new project in the current workspace", - action: () => toggleCreateProjectModal(true), + action: () => { + toggleCreateProjectModal(true); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON }); + }, }, }; }; diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index ce8d8528d3c..e1c3840ca85 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -8,7 +8,12 @@ import useSWR from "swr"; import { CommandIcon, FolderPlus, Search, Settings, X } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // plane imports -import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_DEFAULT_SEARCH_RESULT } from "@plane/constants"; +import { + EUserPermissions, + EUserPermissionsLevel, + PROJECT_TRACKER_ELEMENTS, + WORKSPACE_DEFAULT_SEARCH_RESULT, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWorkspaceSearchResults } from "@plane/types"; import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui"; @@ -28,6 +33,7 @@ import { import { SimpleEmptyState } from "@/components/empty-state"; // helpers // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useCommandPalette, useEventTracker, @@ -364,7 +370,7 @@ export const CommandModal: React.FC = observer(() => { { closePalette(); - setTrackElement("Command palette"); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON }); toggleCreateProjectModal(true); }} className="focus:outline-none" diff --git a/web/core/components/home/widgets/empty-states/no-projects.tsx b/web/core/components/home/widgets/empty-states/no-projects.tsx index be4c8af6488..0e96d40482a 100644 --- a/web/core/components/home/widgets/empty-states/no-projects.tsx +++ b/web/core/components/home/widgets/empty-states/no-projects.tsx @@ -5,12 +5,13 @@ import Link from "next/link"; import { useParams } from "next/navigation"; import { Briefcase, Check, Hotel, Users, X } from "lucide-react"; // plane ui -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useLocalStorage } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; import { cn, getFileURL } from "@plane/utils"; // helpers // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useCommandPalette, useEventTracker, @@ -61,6 +62,7 @@ export const NoProjectsEmptyState = observer(() => { e.stopPropagation(); setTrackElement("Sidebar"); toggleCreateProjectModal(true); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON }); }, disabled: !canCreateProject, }, diff --git a/web/core/components/integration/jira/give-details.tsx b/web/core/components/integration/jira/give-details.tsx index a9f12e709bd..587471b0699 100644 --- a/web/core/components/integration/jira/give-details.tsx +++ b/web/core/components/integration/jira/give-details.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { useFormContext, Controller } from "react-hook-form"; import { Plus } from "lucide-react"; +import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { IJiraImporterForm } from "@plane/types"; // hooks // components @@ -201,6 +202,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
<> - diff --git a/web/core/components/project/header.tsx b/web/core/components/project/header.tsx index e40280373c3..1abd5a91dbf 100644 --- a/web/core/components/project/header.tsx +++ b/web/core/components/project/header.tsx @@ -4,14 +4,15 @@ import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; import { Briefcase } from "lucide-react"; // i18n -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; +import { captureClick } from "@/helpers/event-tracker.helper"; // hooks -import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store"; +import { useCommandPalette, useUserPermissions } from "@/hooks/store"; // plane web constants // components import HeaderFilters from "./filters"; @@ -22,7 +23,6 @@ export const ProjectsBaseHeader = observer(() => { const { t } = useTranslation(); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const pathname = usePathname(); @@ -57,9 +57,9 @@ export const ProjectsBaseHeader = observer(() => {
diff --git a/web/core/components/workspace/sidebar/projects-list.tsx b/web/core/components/workspace/sidebar/projects-list.tsx index 2ef487468cd..14419ffe882 100644 --- a/web/core/components/workspace/sidebar/projects-list.tsx +++ b/web/core/components/workspace/sidebar/projects-list.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { Briefcase, ChevronRight, Plus } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui import { Loader, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; @@ -17,7 +17,7 @@ import { CreateProjectModal } from "@/components/project"; import { SidebarProjectsListItem } from "@/components/workspace"; // helpers // hooks -import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; +import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store"; // plane web types import { TProject } from "@/plane-web/types"; @@ -33,7 +33,6 @@ export const SidebarProjectsList: FC = observer(() => { const { t } = useTranslation(); const { toggleCreateProjectModal } = useCommandPalette(); const { sidebarCollapsed } = useAppTheme(); - const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const { loader, getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject(); @@ -193,9 +192,9 @@ export const SidebarProjectsList: FC = observer(() => { diff --git a/web/core/components/onboarding/create-workspace.tsx b/web/core/components/onboarding/create-workspace.tsx index d9cf3d11637..e37e3c311d9 100644 --- a/web/core/components/onboarding/create-workspace.tsx +++ b/web/core/components/onboarding/create-workspace.tsx @@ -4,7 +4,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; // constants -import { ORGANIZATION_SIZE, RESTRICTED_URLS, WORKSPACE_TRACKER_EVENTS } from "@plane/constants"; +import { + ORGANIZATION_SIZE, + RESTRICTED_URLS, + WORKSPACE_TRACKER_EVENTS, + WORKSPACE_TRACKER_ELEMENTS, +} from "@plane/constants"; // types import { useTranslation } from "@plane/i18n"; import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types"; @@ -278,7 +283,7 @@ export const CreateWorkspace: React.FC = observer((props) => {
diff --git a/web/ce/components/issues/issue-layouts/quick-action-dropdowns/copy-menu-helper.tsx b/web/ce/components/issues/issue-layouts/quick-action-dropdowns/copy-menu-helper.tsx index f9aed404035..202d682e954 100644 --- a/web/ce/components/issues/issue-layouts/quick-action-dropdowns/copy-menu-helper.tsx +++ b/web/ce/components/issues/issue-layouts/quick-action-dropdowns/copy-menu-helper.tsx @@ -10,7 +10,6 @@ export interface CopyMenuHelperProps { shouldRender: boolean; }; activeLayout: string; - setTrackElement: (element: string) => void; setCreateUpdateIssueModal: (open: boolean) => void; setDuplicateWorkItemModal?: (open: boolean) => void; } diff --git a/web/ce/helpers/command-palette.ts b/web/ce/helpers/command-palette.ts index 3181c88e2eb..1cb04b623dd 100644 --- a/web/ce/helpers/command-palette.ts +++ b/web/ce/helpers/command-palette.ts @@ -20,7 +20,7 @@ export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => { description: "Create a new work item in the current project", action: () => { toggleCreateIssueModal(true); - captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM }); + captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON }); }, }, }; diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index e1c3840ca85..27493c8460f 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -12,6 +12,7 @@ import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS, + WORK_ITEM_TRACKER_ELEMENTS, WORKSPACE_DEFAULT_SEARCH_RESULT, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; @@ -34,14 +35,7 @@ import { SimpleEmptyState } from "@/components/empty-state"; // helpers // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { - useCommandPalette, - useEventTracker, - useIssueDetail, - useProject, - useUser, - useUserPermissions, -} from "@/hooks/store"; +import { useCommandPalette, useIssueDetail, useProject, useUser, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -80,7 +74,6 @@ export const CommandModal: React.FC = observer(() => { const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette(); const { allowPermissions } = useUserPermissions(); - const { setTrackElement } = useEventTracker(); const projectIdentifier = workItem?.toString().split("-")[0]; const sequence_id = workItem?.toString().split("-")[1]; // fetch work item details using identifier @@ -352,7 +345,9 @@ export const CommandModal: React.FC = observer(() => { { closePalette(); - setTrackElement("Command Palette"); + captureClick({ + elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON, + }); toggleCreateIssueModal(true); }} className="focus:bg-custom-background-80" diff --git a/web/core/components/command-palette/command-palette.tsx b/web/core/components/command-palette/command-palette.tsx index e7557c2fd1f..0bc174d6d11 100644 --- a/web/core/components/command-palette/command-palette.tsx +++ b/web/core/components/command-palette/command-palette.tsx @@ -5,21 +5,15 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // ui -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { COMMAND_PALETTE_TRACKER_ELEMENTS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { copyTextToClipboard } from "@plane/utils"; import { CommandModal, ShortcutsModal } from "@/components/command-palette"; // helpers // hooks -import { - useEventTracker, - useUser, - useAppTheme, - useCommandPalette, - useUserPermissions, - useIssueDetail, -} from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useUser, useAppTheme, useCommandPalette, useUserPermissions, useIssueDetail } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { @@ -42,7 +36,6 @@ export const CommandPalette: FC = observer(() => { // store hooks const { fetchIssueWithIdentifier } = useIssueDetail(); const { toggleSidebar } = useAppTheme(); - const { setTrackElement } = useEventTracker(); const { platform } = usePlatformOS(); const { data: currentUser, canPerformAnyCreateAction } = useUser(); const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette(); @@ -203,7 +196,7 @@ export const CommandPalette: FC = observer(() => { toggleSidebar(); } } else if (!isAnyModalOpen) { - setTrackElement("Shortcut key"); + captureClick({ elementName: COMMAND_PALETTE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_KEY }); if ( Object.keys(shortcutsList.global).includes(keyPressed) && ((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions()) @@ -242,7 +235,6 @@ export const CommandPalette: FC = observer(() => { performProjectCreateActions, performWorkspaceCreateActions, projectId, - setTrackElement, shortcutsList, toggleCommandPaletteModal, toggleShortcutModal, diff --git a/web/core/components/home/widgets/empty-states/no-projects.tsx b/web/core/components/home/widgets/empty-states/no-projects.tsx index 0e96d40482a..8621029eaa4 100644 --- a/web/core/components/home/widgets/empty-states/no-projects.tsx +++ b/web/core/components/home/widgets/empty-states/no-projects.tsx @@ -12,14 +12,7 @@ import { cn, getFileURL } from "@plane/utils"; // helpers // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { - useCommandPalette, - useEventTracker, - useProject, - useUser, - useUserPermissions, - useWorkspace, -} from "@/hooks/store"; +import { useCommandPalette, useProject, useUser, useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web constants export const NoProjectsEmptyState = observer(() => { @@ -28,7 +21,6 @@ export const NoProjectsEmptyState = observer(() => { // store hooks const { allowPermissions } = useUserPermissions(); const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { data: currentUser } = useUser(); const { joinedProjectIds } = useProject(); const { currentWorkspace: activeWorkspace } = useWorkspace(); @@ -60,7 +52,6 @@ export const NoProjectsEmptyState = observer(() => { if (!canCreateProject) return; e.preventDefault(); e.stopPropagation(); - setTrackElement("Sidebar"); toggleCreateProjectModal(true); captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON }); }, diff --git a/web/core/components/integration/jira/give-details.tsx b/web/core/components/integration/jira/give-details.tsx index 587471b0699..19eace8a5ba 100644 --- a/web/core/components/integration/jira/give-details.tsx +++ b/web/core/components/integration/jira/give-details.tsx @@ -12,13 +12,13 @@ import { IJiraImporterForm } from "@plane/types"; import { CustomSelect, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "@plane/utils"; -import { useCommandPalette, useEventTracker, useProject } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useCommandPalette, useProject } from "@/hooks/store"; // types export const JiraGetImportDetail: React.FC = observer(() => { // store hooks const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { workspaceProjectIds, getProjectById } = useProject(); // form info const { @@ -204,7 +204,7 @@ export const JiraGetImportDetail: React.FC = observer(() => { type="button" data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON} onClick={() => { - setTrackElement("Jira import detail page"); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE }); toggleCreateProjectModal(true); }} className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200" diff --git a/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx b/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx index 7f1b9c6c718..ae1bc87427f 100644 --- a/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx +++ b/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx @@ -3,11 +3,13 @@ import React, { FC } from "react"; import { observer } from "mobx-react"; import { LayersIcon, Plus } from "lucide-react"; // plane imports +import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { CustomMenu } from "@plane/ui"; // hooks -import { useEventTracker, useIssueDetail } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useIssueDetail } from "@/hooks/store"; type Props = { issueId: string; @@ -28,7 +30,6 @@ export const SubIssuesActionButton: FC = observer((props) => { setIssueCrudOperationState, issueCrudOperationState, } = useIssueDetail(issueServiceType); - const { setTrackElement } = useEventTracker(); // derived values const issue = getIssueById(issueId); @@ -52,13 +53,13 @@ export const SubIssuesActionButton: FC = observer((props) => { }; const handleCreateNew = () => { - setTrackElement("Issue detail nested sub-issue"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.create }); handleIssueCrudState("create", issueId, null); toggleCreateIssueModal(true); }; const handleAddExisting = () => { - setTrackElement("Issue detail nested sub-issue"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.add_existing }); handleIssueCrudState("existing", issueId, null); toggleSubIssuesModal(issue.id); }; diff --git a/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index 6166b158a26..d40fbf0d593 100644 --- a/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; // lucide icons import { Minimize2, Maximize2, Circle, Plus } from "lucide-react"; +import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants"; import { TIssue, ISearchIssueResponse, TIssueKanbanFilters, TIssueGroupByOptions } from "@plane/types"; // ui import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; @@ -12,8 +13,7 @@ import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; import { ExistingIssuesListModal } from "@/components/core"; import { CreateUpdateIssueModal } from "@/components/issues"; // constants -// hooks -import { useEventTracker } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; import { useIssueStoreType } from "@/hooks/use-issue-layout-store"; import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal"; // types @@ -56,7 +56,6 @@ export const HeaderGroupByCard: FC = observer((props) => { const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false); // hooks const storeType = useIssueStoreType(); - const { setTrackElement } = useEventTracker(); // router const { workspaceSlug, projectId, moduleId, cycleId } = useParams(); const pathname = usePathname(); @@ -167,7 +166,7 @@ export const HeaderGroupByCard: FC = observer((props) => { > { - setTrackElement("Kanban layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create }); setIsOpen(true); }} > @@ -175,7 +174,7 @@ export const HeaderGroupByCard: FC = observer((props) => { { - setTrackElement("Kanban layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing }); setOpenExistingIssueListModal(true); }} > @@ -186,7 +185,7 @@ export const HeaderGroupByCard: FC = observer((props) => {
{ - setTrackElement("Kanban layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create }); setIsOpen(true); }} > diff --git a/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx index 44bc95bff46..f107c28b65b 100644 --- a/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { CircleDashed, Plus } from "lucide-react"; // types +import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants"; import { TIssue, ISearchIssueResponse, TIssueGroupByOptions } from "@plane/types"; // ui import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; @@ -13,9 +14,7 @@ import { cn } from "@plane/utils"; import { ExistingIssuesListModal, MultipleSelectGroupAction } from "@/components/core"; import { CreateUpdateIssueModal } from "@/components/issues"; // constants -// helpers -// hooks -import { useEventTracker } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; import { useIssueStoreType } from "@/hooks/use-issue-layout-store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; // plane-web @@ -59,8 +58,6 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { // router const { workspaceSlug, projectId, moduleId, cycleId } = useParams(); const pathname = usePathname(); - // hooks - const { setTrackElement } = useEventTracker(); const storeType = useIssueStoreType(); // derived values const isDraftIssue = pathname.includes("draft-issue"); @@ -134,7 +131,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { > { - setTrackElement("List layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create }); setIsOpen(true); }} > @@ -142,7 +139,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { { - setTrackElement("List layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing }); setOpenExistingIssueListModal(true); }} > @@ -153,7 +150,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
{ - setTrackElement("List layout"); + captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create }); setIsOpen(true); }} > diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index 3a26c0a617f..e1f4bdc5d05 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -13,7 +13,7 @@ import { cn } from "@plane/utils"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useEventTracker, useProject, useProjectState } from "@/hooks/store"; +import { useProject, useProjectState } from "@/hooks/store"; // plane-web components import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns"; import { IQuickActionProps } from "../list/list-view-types"; @@ -40,8 +40,6 @@ export const AllIssueQuickActions: React.FC = observer((props const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false); // router const { workspaceSlug } = useParams(); - // store hooks - const { setTrackElement } = useEventTracker(); const { getStateById } = useProjectState(); const { getProjectIdentifierById } = useProject(); // derived values @@ -71,7 +69,6 @@ export const AllIssueQuickActions: React.FC = observer((props isArchivingAllowed, isDeletingAllowed: isEditingAllowed, isInArchivableGroup, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx index adb246b8a87..1c0884efee6 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx @@ -17,7 +17,7 @@ import { DeleteIssueModal } from "@/components/issues"; // helpers // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useEventTracker, useIssues, useUserPermissions } from "@/hooks/store"; +import { useIssues, useUserPermissions } from "@/hooks/store"; // types import { IQuickActionProps } from "../list/list-view-types"; // helper @@ -41,7 +41,6 @@ export const ArchivedIssueQuickActions: React.FC = observer(( // store hooks const { allowPermissions } = useUserPermissions(); - const { setTrackElement } = useEventTracker(); const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED); // derived values const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`; @@ -59,7 +58,6 @@ export const ArchivedIssueQuickActions: React.FC = observer(( isEditingAllowed, isDeletingAllowed: isEditingAllowed, isRestoringAllowed: !!isRestoringAllowed, - setTrackElement, setIssueToEdit: () => {}, setCreateUpdateIssueModal: () => {}, setDeleteIssueModal, diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 3734e10405b..8a5fc6c0024 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -19,7 +19,7 @@ import { cn } from "@plane/utils"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; +import { useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; // plane-web components import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns"; // types @@ -48,8 +48,6 @@ export const CycleIssueQuickActions: React.FC = observer((pro const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false); // router const { workspaceSlug, cycleId } = useParams(); - // store hooks - const { setTrackElement } = useEventTracker(); const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE); const { allowPermissions } = useUserPermissions(); const { getStateById } = useProjectState(); @@ -85,7 +83,6 @@ export const CycleIssueQuickActions: React.FC = observer((pro isArchivingAllowed, isDeletingAllowed, isInArchivableGroup, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx index ca4c720b7d2..00848ceb262 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx @@ -18,7 +18,7 @@ import { cn } from "@plane/utils"; import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useEventTracker, useUserPermissions } from "@/hooks/store"; +import { useUserPermissions } from "@/hooks/store"; // local imports import { IQuickActionProps } from "../list/list-view-types"; import { useDraftIssueMenuItems, MenuItemFactoryProps } from "./helper"; @@ -43,7 +43,6 @@ export const DraftIssueQuickActions: React.FC = observer((pro const [deleteIssueModal, setDeleteIssueModal] = useState(false); // store hooks const { allowPermissions } = useUserPermissions(); - const { setTrackElement } = useEventTracker(); // derived values const activeLayout = "Draft Issues"; // auth @@ -76,7 +75,6 @@ export const DraftIssueQuickActions: React.FC = observer((pro isEditingAllowed, isDeletingAllowed, isDraftIssue, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx index 3ebcc7e70fd..81c016ea8e4 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx @@ -57,7 +57,6 @@ export interface MenuItemFactoryProps { issueTypeDetail?: { is_active?: boolean }; isDraftIssue?: boolean; // Action handlers - setTrackElement: (element: string) => void; setIssueToEdit: (issue: TIssue | undefined) => void; setCreateUpdateIssueModal: (open: boolean) => void; setDeleteIssueModal: (open: boolean) => void; @@ -145,7 +144,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => { isRestoringAllowed = false, isInArchivableGroup = false, issueTypeDetail, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, @@ -161,7 +159,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => { action: customEditAction || (() => { - setTrackElement(activeLayout); setIssueToEdit(issue); setCreateUpdateIssueModal(true); }), @@ -174,7 +171,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => { title: t("common.actions.make_a_copy"), icon: Copy, action: () => { - setTrackElement(activeLayout); setCreateUpdateIssueModal(true); }, shouldRender: isEditingAllowed && (issueTypeDetail?.is_active ?? true), @@ -183,7 +179,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => { return createCopyMenuWithDuplication({ baseItem, activeLayout, - setTrackElement, setCreateUpdateIssueModal, setDuplicateWorkItemModal, }); @@ -244,7 +239,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => { title: t("common.actions.delete"), icon: Trash2, action: () => { - setTrackElement(activeLayout); setDeleteIssueModal(true); }, shouldRender: isDeletingAllowed, @@ -305,7 +299,6 @@ export const useCycleIssueMenuItems = (props: MenuItemFactoryProps): TContextMen ...props.issue, cycle_id: props.cycleId ?? null, }); - props.setTrackElement(props.activeLayout || ""); props.setCreateUpdateIssueModal(true); }; @@ -331,7 +324,6 @@ export const useModuleIssueMenuItems = (props: MenuItemFactoryProps): TContextMe ...props.issue, module_ids: props.moduleId ? [props.moduleId] : [], }); - props.setTrackElement(props.activeLayout || ""); props.setCreateUpdateIssueModal(true); }; diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 6901e249c04..6bd96b17af7 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -19,7 +19,7 @@ import { cn } from "@plane/utils"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useIssues, useEventTracker, useProjectState, useUserPermissions, useProject } from "@/hooks/store"; +import { useIssues, useProjectState, useUserPermissions, useProject } from "@/hooks/store"; // plane-web components import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns"; import { IQuickActionProps } from "../list/list-view-types"; @@ -48,7 +48,6 @@ export const ModuleIssueQuickActions: React.FC = observer((pr // router const { workspaceSlug, moduleId } = useParams(); // store hooks - const { setTrackElement } = useEventTracker(); const { issuesFilter } = useIssues(EIssuesStoreType.MODULE); const { allowPermissions } = useUserPermissions(); const { getStateById } = useProjectState(); @@ -84,7 +83,6 @@ export const ModuleIssueQuickActions: React.FC = observer((pr isArchivingAllowed, isDeletingAllowed, isInArchivableGroup, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 29177848b9b..2fb79bbabe7 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -19,7 +19,7 @@ import { cn } from "@plane/utils"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // hooks import { captureClick } from "@/helpers/event-tracker.helper"; -import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; +import { useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; // plane-web components import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns"; import { IQuickActionProps } from "../list/list-view-types"; @@ -49,7 +49,6 @@ export const ProjectIssueQuickActions: React.FC = observer((p const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false); // store hooks const { allowPermissions } = useUserPermissions(); - const { setTrackElement } = useEventTracker(); const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT); const { getStateById } = useProjectState(); const { getProjectIdentifierById } = useProject(); @@ -92,7 +91,6 @@ export const ProjectIssueQuickActions: React.FC = observer((p isDeletingAllowed, isInArchivableGroup, isDraftIssue, - setTrackElement, setIssueToEdit, setCreateUpdateIssueModal, setDeleteIssueModal, diff --git a/web/core/components/project/settings/features-list.tsx b/web/core/components/project/settings/features-list.tsx index faa41a0e1d7..b2b8c2c0bd5 100644 --- a/web/core/components/project/settings/features-list.tsx +++ b/web/core/components/project/settings/features-list.tsx @@ -2,12 +2,13 @@ import { FC } from "react"; import { observer } from "mobx-react"; +import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IProject } from "@plane/types"; import { ToggleSwitch, Tooltip, setPromiseToast } from "@plane/ui"; // hooks import { SettingsHeading } from "@/components/settings"; -import { useEventTracker, useProject, useUser } from "@/hooks/store"; +import { useProject, useUser } from "@/hooks/store"; // plane web components import { UpgradeBadge } from "@/plane-web/components/workspace"; // plane web constants @@ -23,7 +24,6 @@ export const ProjectFeaturesList: FC = observer((props) => { const { workspaceSlug, projectId, isAdmin } = props; // store hooks const { t } = useTranslation(); - const { captureEvent } = useEventTracker(); const { data: currentUser } = useUser(); const { getProjectById, updateProject } = useProject(); // derived values @@ -32,12 +32,6 @@ export const ProjectFeaturesList: FC = observer((props) => { const handleSubmit = async (featureKey: string, featureProperty: string) => { if (!workspaceSlug || !projectId || !currentProjectDetails) return; - // capturing event - captureEvent(`Toggle ${featureKey}`, { - enabled: !currentProjectDetails?.[featureProperty as keyof IProject], - element: "Project settings feature page", - }); - // making the request to update the project feature const settingsPayload = { [featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject], @@ -92,6 +86,7 @@ export const ProjectFeaturesList: FC = observer((props) => { onChange={() => handleSubmit(featureItemKey, featureItem.property)} disabled={!featureItem.isEnabled || !isAdmin} size="sm" + data-ph-element={PROJECT_TRACKER_ELEMENTS.TOGGLE_FEATURE} />
From 9ae2887384fed9c1a3214ac69ff8322d53fd847b Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 19:07:25 +0530 Subject: [PATCH 33/38] refactor: remove event tracker stores and hooks --- web/ce/store/event-tracker.store.ts | 11 -- web/core/hooks/store/use-event-tracker.ts | 11 -- web/core/store/event-tracker.store.ts | 131 ---------------------- web/ee/store/event-tracker.store.ts | 1 - 4 files changed, 154 deletions(-) delete mode 100644 web/ce/store/event-tracker.store.ts delete mode 100644 web/core/hooks/store/use-event-tracker.ts delete mode 100644 web/core/store/event-tracker.store.ts delete mode 100644 web/ee/store/event-tracker.store.ts diff --git a/web/ce/store/event-tracker.store.ts b/web/ce/store/event-tracker.store.ts deleted file mode 100644 index 4f5074dd94a..00000000000 --- a/web/ce/store/event-tracker.store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RootStore } from "@/plane-web/store/root.store"; -import { CoreEventTrackerStore, ICoreEventTrackerStore } from "@/store/event-tracker.store"; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface IEventTrackerStore extends ICoreEventTrackerStore {} - -export class EventTrackerStore extends CoreEventTrackerStore implements IEventTrackerStore { - constructor(_rootStore: RootStore) { - super(_rootStore); - } -} diff --git a/web/core/hooks/store/use-event-tracker.ts b/web/core/hooks/store/use-event-tracker.ts deleted file mode 100644 index 0f92aaced49..00000000000 --- a/web/core/hooks/store/use-event-tracker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from "react"; -// mobx store -import { StoreContext } from "@/lib/store-context"; -// types -import { IEventTrackerStore } from "@/plane-web/store/event-tracker.store"; - -export const useEventTracker = (): IEventTrackerStore => { - const context = useContext(StoreContext); - if (context === undefined) throw new Error("useEventTracker must be used within StoreProvider"); - return context.eventTracker; -}; diff --git a/web/core/store/event-tracker.store.ts b/web/core/store/event-tracker.store.ts deleted file mode 100644 index 08731c6f420..00000000000 --- a/web/core/store/event-tracker.store.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { action, computed, makeObservable, observable } from "mobx"; -import posthog from "posthog-js"; -// store -import { - EventProps, - IssueEventProps, - getIssueEventPayload, - getProjectStateEventPayload, - getPageEventPayload, -} from "@plane/constants"; -import { CoreRootStore } from "./root.store"; - -export interface ICoreEventTrackerStore { - // properties - trackElement: string | undefined; - // computed - getRequiredProperties: any; - // actions - resetSession: () => void; - setTrackElement: (element: string) => void; - captureEvent: (eventName: string, payload?: any) => void; - capturePageEvent: (props: EventProps) => void; - captureIssueEvent: (props: IssueEventProps) => void; - captureProjectStateEvent: (props: EventProps) => void; -} - -export abstract class CoreEventTrackerStore implements ICoreEventTrackerStore { - trackElement: string | undefined = undefined; - rootStore; - constructor(_rootStore: CoreRootStore) { - makeObservable(this, { - // properties - trackElement: observable, - // computed - getRequiredProperties: computed, - // actions - resetSession: action, - setTrackElement: action, - captureEvent: action, - }); - // store - this.rootStore = _rootStore; - } - - /** - * @description: Returns the necessary property for the event tracking - */ - get getRequiredProperties() { - const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace; - const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails; - return { - workspace_id: currentWorkspaceDetails?.id, - project_id: currentProjectDetails?.id, - }; - } - - /** - * @description: Set the trigger point of event. - * @param {string} element - */ - setTrackElement = (element?: string) => { - this.trackElement = element; - }; - - /** - * @description: Reset the session. - */ - resetSession = () => { - posthog?.reset(); - }; - - /** - * @description: Captures the event. - * @param {string} eventName - * @param {any} payload - */ - captureEvent = (eventName: string, payload?: any) => { - posthog?.capture(eventName, { - ...this.getRequiredProperties, - ...payload, - element: payload?.element ?? this.trackElement, - }); - this.setTrackElement(undefined); - }; - - /** - * @description: Captures the project pages related events. - * @param {EventProps} props - */ - capturePageEvent = (props: EventProps) => { - const { eventName, payload } = props; - const eventPayload: any = getPageEventPayload({ - ...this.getRequiredProperties, - ...payload, - element: payload.element ?? this.trackElement, - }); - posthog?.capture(eventName, eventPayload); - this.setTrackElement(undefined); - }; - - /** - * @description: Captures the issue related events. - * @param {IssueEventProps} props - */ - captureIssueEvent = (props: IssueEventProps) => { - const { eventName, payload } = props; - const eventPayload: any = { - ...getIssueEventPayload(props), - ...this.getRequiredProperties, - state_group: this.rootStore.state.getStateById(payload.state_id)?.group ?? "", - element: payload.element ?? this.trackElement, - }; - posthog?.capture(eventName, eventPayload); - this.setTrackElement(undefined); - }; - - /** - * @description: Captures the issue related events. - * @param {IssueEventProps} props - */ - captureProjectStateEvent = (props: EventProps) => { - const { eventName, payload } = props; - const eventPayload: any = getProjectStateEventPayload({ - ...this.getRequiredProperties, - ...payload, - element: payload.element ?? this.trackElement, - }); - posthog?.capture(eventName, eventPayload); - this.setTrackElement(undefined); - }; -} diff --git a/web/ee/store/event-tracker.store.ts b/web/ee/store/event-tracker.store.ts deleted file mode 100644 index 68f6a3a741c..00000000000 --- a/web/ee/store/event-tracker.store.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "ce/store/event-tracker.store"; From 874e11b66dd9c3197fb490a8eb36f8d2004d7f36 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 19:42:29 +0530 Subject: [PATCH 34/38] refactor: remove event tracker store --- web/core/store/root.store.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/core/store/root.store.ts b/web/core/store/root.store.ts index e2ef10ae36d..677e3037655 100644 --- a/web/core/store/root.store.ts +++ b/web/core/store/root.store.ts @@ -4,7 +4,6 @@ import { FALLBACK_LANGUAGE, LANGUAGE_STORAGE_KEY } from "@plane/i18n"; // plane web store import { AnalyticsStore, IAnalyticsStore } from "@/plane-web/store/analytics.store"; import { CommandPaletteStore, ICommandPaletteStore } from "@/plane-web/store/command-palette.store"; -import { EventTrackerStore, IEventTrackerStore } from "@/plane-web/store/event-tracker.store"; import { RootStore } from "@/plane-web/store/root.store"; import { IStateStore, StateStore } from "@/plane-web/store/state.store"; // stores @@ -55,7 +54,6 @@ export class CoreRootStore { router: IRouterStore; commandPalette: ICommandPaletteStore; theme: IThemeStore; - eventTracker: IEventTrackerStore; instance: IInstanceStore; user: IUserStore; projectInbox: IProjectInboxStore; @@ -86,7 +84,6 @@ export class CoreRootStore { this.state = new StateStore(this as unknown as RootStore); this.label = new LabelStore(this); this.dashboard = new DashboardStore(this); - this.eventTracker = new EventTrackerStore(this as unknown as RootStore); this.multipleSelect = new MultipleSelectStore(); this.projectInbox = new ProjectInboxStore(this); this.projectPages = new ProjectPageStore(this as unknown as RootStore); @@ -120,7 +117,6 @@ export class CoreRootStore { this.state = new StateStore(this as unknown as RootStore); this.label = new LabelStore(this); this.dashboard = new DashboardStore(this); - this.eventTracker = new EventTrackerStore(this as unknown as RootStore); this.projectInbox = new ProjectInboxStore(this); this.projectPages = new ProjectPageStore(this as unknown as RootStore); this.multipleSelect = new MultipleSelectStore(); From b07cba117952400d74d14344029461976e3844e9 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 19:56:23 +0530 Subject: [PATCH 35/38] fix: build errors --- web/core/hooks/store/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/core/hooks/store/index.ts b/web/core/hooks/store/index.ts index dfc104542a1..ddd8735b4de 100644 --- a/web/core/hooks/store/index.ts +++ b/web/core/hooks/store/index.ts @@ -7,7 +7,6 @@ export * from "./use-cycle"; export * from "./use-cycle-filter"; export * from "./use-dashboard"; export * from "./use-editor-asset"; -export * from "./use-event-tracker"; export * from "./use-global-view"; export * from "./use-inbox-issues"; export * from "./use-instance"; From aac6867d3db759ddb09ed50fbe3e4a9042ed8920 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 1 Jul 2025 21:14:05 +0530 Subject: [PATCH 36/38] clean up event tracker payloads --- packages/constants/src/event-tracker/core.ts | 94 -------------------- 1 file changed, 94 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index c7ccc5bbe59..dd40d345933 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -1,94 +1,3 @@ -export type IssueEventProps = { - eventName: string; - payload: any; - updates?: any; - path?: string; -}; - -export type EventProps = { - eventName: string; - payload: any; - updates?: any; - path?: string; -}; - -export const getPageEventPayload = (payload: any) => ({ - workspace_id: payload.workspace_id, - project_id: payload.project, - created_at: payload.created_at, - updated_at: payload.updated_at, - access: payload.access === 0 ? "Public" : "Private", - is_locked: payload.is_locked, - archived_at: payload.archived_at, - created_by: payload.created_by, - state: payload.state, - element: payload.element, -}); - -export const getIssueEventPayload = (props: IssueEventProps) => { - const { eventName, payload, updates, path } = props; - let eventPayload: any = { - issue_id: payload.id, - estimate_point: payload.estimate_point, - link_count: payload.link_count, - target_date: payload.target_date, - is_draft: payload.is_draft, - label_ids: payload.label_ids, - assignee_ids: payload.assignee_ids, - created_at: payload.created_at, - updated_at: payload.updated_at, - sequence_id: payload.sequence_id, - module_ids: payload.module_ids, - sub_issues_count: payload.sub_issues_count, - parent_id: payload.parent_id, - project_id: payload.project_id, - workspace_id: payload.workspace_id, - priority: payload.priority, - state_id: payload.state_id, - start_date: payload.start_date, - attachment_count: payload.attachment_count, - cycle_id: payload.cycle_id, - module_id: payload.module_id, - archived_at: payload.archived_at, - state: payload.state, - view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "", - }; - - if (eventName === WORK_ITEM_TRACKER_EVENTS.update) { - eventPayload = { - ...eventPayload, - ...updates, - updated_from: props.path?.includes("workspace-views") - ? "All views" - : props.path?.includes("cycles") - ? "Cycle" - : props.path?.includes("modules") - ? "Module" - : props.path?.includes("views") - ? "Project view" - : props.path?.includes("inbox") - ? "Inbox" - : props.path?.includes("draft") - ? "Draft" - : "Project", - }; - } - return eventPayload; -}; - -export const getProjectStateEventPayload = (payload: any) => ({ - workspace_id: payload.workspace_id, - project_id: payload.id, - state_id: payload.id, - created_at: payload.created_at, - updated_at: payload.updated_at, - group: payload.group, - color: payload.color, - default: payload.default, - state: payload.state, - element: payload.element, -}); - // Dashboard Events export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; export const HEADER_GITHUB_ICON = "header_github_icon"; @@ -118,7 +27,6 @@ export const PROJECT_TRACKER_EVENTS = { update: "project_updated", delete: "project_deleted", }; - export const PROJECT_TRACKER_ELEMENTS = { EXTENDED_SIDEBAR_ADD_BUTTON: "extended_sidebar_add_project_button", SIDEBAR_CREATE_PROJECT_BUTTON: "sidebar_create_project_button", @@ -200,7 +108,6 @@ export const WORK_ITEM_TRACKER_EVENTS = { create: "draft_work_item_created", }, }; - export const WORK_ITEM_TRACKER_ELEMENTS = { HEADER_ADD_BUTTON: { WORK_ITEMS: "work_items_header_add_work_item_button", @@ -261,7 +168,6 @@ export const PROJECT_PAGE_TRACKER_EVENTS = { unfavorite: "project_page_unfavorited", move: "project_page_moved", }; - export const PROJECT_PAGE_TRACKER_ELEMENTS = { COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_page_button", EMPTY_STATE_CREATE_BUTTON: "empty_state_create_page_button", From 559e04d92228c427a052a9dae6466e6ac4c0dba6 Mon Sep 17 00:00:00 2001 From: gakshita Date: Wed, 2 Jul 2025 15:38:22 +0530 Subject: [PATCH 37/38] chore: workspace-settings-events --- packages/constants/src/event-tracker/core.ts | 30 +++++++++++++++++ .../(workspace)/webhooks/[webhookId]/page.tsx | 16 +++++++++- .../settings/(workspace)/webhooks/page.tsx | 17 ++++++++-- .../billing/comparison/plan-detail.tsx | 19 ++++++++++- web/core/components/exporter/export-form.tsx | 28 ++++++++++++++-- .../web-hooks/create-webhook-modal.tsx | 15 +++++++++ .../web-hooks/delete-webhook-modal.tsx | 21 ++++++++++-- .../web-hooks/form/delete-section.tsx | 7 +++- web/core/components/web-hooks/form/form.tsx | 7 +++- web/core/components/web-hooks/form/toggle.tsx | 8 ++++- .../web-hooks/webhooks-list-item.tsx | 32 +++++++++++++++++-- 11 files changed, 185 insertions(+), 15 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index dd40d345933..20c866fa319 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -1,3 +1,5 @@ +import { EProductSubscriptionEnum } from "@plane/types"; + // Dashboard Events export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; export const HEADER_GITHUB_ICON = "header_github_icon"; @@ -268,3 +270,31 @@ export const SIDEBAR_TRACKER_ELEMENTS = { USER_MENU_ITEM: "sidenav_user_menu_item", CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button", }; + +export const WORKSPACE_SETTINGS_TRACKER_EVENTS = { + // Billing + upgrade_plan_redirected: "upgrade_plan_redirected", + // Exports + csv_exported: "csv_exported", + // Webhooks + webhook_created: "webhook_created", + webhook_deleted: "webhook_deleted", + webhook_toggled: "webhook_toggled", + webhook_details_page_toggled: "webhook_details_page_toggled", + webhook_updated: "webhook_updated", +}; +export const WORKSPACE_SETTINGS_TRACKER_ELEMENTS = { + // Billing + BILLING_UPGRADE_BUTTON: (subscriptionType: EProductSubscriptionEnum) => `biling_upgrade_${subscriptionType}_button`, + BILING_TALK_TO_SALES_BUTTON: "biling_talk_to_sales_button", + // Exports + EXPORT_BUTTON: "export_button", + // Webhooks + HEADER_ADD_WEBHOOK_BUTTON: "header_add_webhook_button", + EMPTY_STATE_ADD_WEBHOOK_BUTTON: "empty_state_add_webhook_button", + LIST_ITEM_DELETE_BUTTON: "list_item_delete_button", + WEBHOOK_LIST_ITEM_TOGGLE_SWITCH: "webhook_list_item_toggle_switch", + WEBHOOK_DETAILS_PAGE_TOGGLE_SWITCH: "webhook_details_page_toggle_switch", + WEBHOOK_DELETE_BUTTON: "webhook_delete_button", + WEBHOOK_UPDATE_BUTTON: "webhook_update_button", +}; diff --git a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx index a775ff3b1fb..c6a7d3f5ac8 100644 --- a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -14,6 +14,7 @@ import { PageHead } from "@/components/core"; import { SettingsContentWrapper } from "@/components/settings"; import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "@/components/web-hooks"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; const WebhookDetailsPage = observer(() => { @@ -55,6 +56,12 @@ const WebhookDetailsPage = observer(() => { }; await updateWebhook(workspaceSlug.toString(), formData.id, payload) .then(() => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { + webhook: formData.id, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -62,6 +69,13 @@ const WebhookDetailsPage = observer(() => { }); }) .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { + webhook: formData.id, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", diff --git a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx index 19f18717e7c..511ab96d9a5 100644 --- a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // plane imports -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // components import { NotAuthorizedView } from "@/components/auth-screens"; @@ -15,6 +15,7 @@ import { SettingsContentWrapper, SettingsHeading } from "@/components/settings"; import { WebhookSettingsLoader } from "@/components/ui"; import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks"; // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; @@ -71,7 +72,12 @@ const WebhooksListPage = observer(() => { description={t("workspace_settings.settings.webhooks.description")} button={{ label: t("workspace_settings.settings.webhooks.add_webhook"), - onClick: () => setShowCreateWebhookModal(true), + onClick: () => { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.HEADER_ADD_WEBHOOK_BUTTON, + }); + setShowCreateWebhookModal(true); + }, }} /> {Object.keys(webhooks).length > 0 ? ( @@ -89,7 +95,12 @@ const WebhooksListPage = observer(() => { size="md" primaryButton={{ text: t("workspace_settings.settings.webhooks.add_webhook"), - onClick: () => setShowCreateWebhookModal(true), + onClick: () => { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_WEBHOOK_BUTTON, + }); + setShowCreateWebhookModal(true); + }, }} />
diff --git a/web/ce/components/workspace/billing/comparison/plan-detail.tsx b/web/ce/components/workspace/billing/comparison/plan-detail.tsx index 38715972b43..45fcb452256 100644 --- a/web/ce/components/workspace/billing/comparison/plan-detail.tsx +++ b/web/ce/components/workspace/billing/comparison/plan-detail.tsx @@ -5,6 +5,8 @@ import { SUBSCRIPTION_REDIRECTION_URLS, SUBSCRIPTION_WITH_BILLING_FREQUENCY, TALK_TO_SALES_URL, + WORKSPACE_SETTINGS_TRACKER_ELEMENTS, + WORKSPACE_SETTINGS_TRACKER_EVENTS, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EProductSubscriptionEnum, TBillingFrequency } from "@plane/types"; @@ -16,6 +18,7 @@ import { DiscountInfo } from "@/components/license/modal/card/discount-info"; import { getUpgradeButtonStyle } from "@/components/workspace/billing/subscription"; import { TPlanDetail } from "@/constants/plans"; // local imports +import { captureSuccess } from "@/helpers/event-tracker.helper"; import { PlanFrequencyToggle } from "./frequency-toggle"; type TPlanDetailProps = { @@ -49,6 +52,12 @@ export const PlanDetail: FC = observer((props) => { const frequency = billingFrequency ?? "year"; // Get the redirection URL based on the subscription type and billing frequency const redirectUrl = SUBSCRIPTION_REDIRECTION_URLS[subscriptionType][frequency] ?? TALK_TO_SALES_URL; + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.upgrade_plan_redirected, + payload: { + subscriptionType, + }, + }); // Open the URL in a new tab window.open(redirectUrl, "_blank"); }; @@ -101,7 +110,15 @@ export const PlanDetail: FC = observer((props) => { {/* Subscription button */}
-
diff --git a/web/core/components/exporter/export-form.tsx b/web/core/components/exporter/export-form.tsx index b108d2e4c5e..e910ed6a3d2 100644 --- a/web/core/components/exporter/export-form.tsx +++ b/web/core/components/exporter/export-form.tsx @@ -1,9 +1,16 @@ import { useState } from "react"; import { intersection } from "lodash"; import { Controller, useForm } from "react-hook-form"; -import { EUserPermissions, EUserPermissionsLevel, EXPORTERS_LIST } from "@plane/constants"; +import { + EUserPermissions, + EUserPermissionsLevel, + EXPORTERS_LIST, + WORKSPACE_SETTINGS_TRACKER_EVENTS, + WORKSPACE_SETTINGS_TRACKER_ELEMENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Button, CustomSearchSelect, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useProject, useUser, useUserPermissions } from "@/hooks/store"; import { ProjectExportService } from "@/services/project/project-export.service"; @@ -73,6 +80,12 @@ export const ExportForm = (props: Props) => { .then(() => { mutateServices(); setExportLoading(false); + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.csv_exported, + payload: { + provider: formData.provider.provider, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.exports.modal.toasts.success.title"), @@ -90,6 +103,12 @@ export const ExportForm = (props: Props) => { }) .catch(() => { setExportLoading(false); + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.csv_exported, + payload: { + provider: formData.provider.provider, + }, + }); setToast({ type: TOAST_TYPE.ERROR, title: t("error"), @@ -163,7 +182,12 @@ export const ExportForm = (props: Props) => {
-
diff --git a/web/core/components/web-hooks/create-webhook-modal.tsx b/web/core/components/web-hooks/create-webhook-modal.tsx index 838916a6935..a7cb43ab74d 100644 --- a/web/core/components/web-hooks/create-webhook-modal.tsx +++ b/web/core/components/web-hooks/create-webhook-modal.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { useParams } from "next/navigation"; // types +import { WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWebhook, IWorkspace, TWebhookEventTypes } from "@plane/types"; // ui @@ -10,6 +11,7 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p // helpers import { csvDownload } from "@plane/utils"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import useKeypress from "@/hooks/use-keypress"; // components import { WebhookForm } from "./form"; @@ -67,6 +69,12 @@ export const CreateWebhookModal: React.FC = (props) => { await createWebhook(workspaceSlug.toString(), payload) .then(({ webHook, secretKey }) => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_created, + payload: { + webhook: formData?.url, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.webhooks.toasts.created.title"), @@ -79,6 +87,13 @@ export const CreateWebhookModal: React.FC = (props) => { csvDownload(csvData, `webhook-secret-key-${Date.now()}`); }) .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_created, + payload: { + webhook: formData?.url, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: t("workspace_settings.settings.webhooks.toasts.not_created.title"), diff --git a/web/core/components/web-hooks/delete-webhook-modal.tsx b/web/core/components/web-hooks/delete-webhook-modal.tsx index 615c50f6809..6e32d9f66a6 100644 --- a/web/core/components/web-hooks/delete-webhook-modal.tsx +++ b/web/core/components/web-hooks/delete-webhook-modal.tsx @@ -3,8 +3,10 @@ import React, { FC, useState } from "react"; import { useParams } from "next/navigation"; // ui +import { WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -35,6 +37,12 @@ export const DeleteWebhookModal: FC = (props) => { removeWebhook(workspaceSlug.toString(), webhookId.toString()) .then(() => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_deleted, + payload: { + webhook: webhookId, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -42,13 +50,20 @@ export const DeleteWebhookModal: FC = (props) => { }); router.replace(`/${workspaceSlug}/settings/webhooks/`); }) - .catch((error) => + .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_deleted, + payload: { + webhook: webhookId, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Something went wrong. Please try again.", - }) - ) + }); + }) .finally(() => setIsDeleting(false)); }; diff --git a/web/core/components/web-hooks/form/delete-section.tsx b/web/core/components/web-hooks/form/delete-section.tsx index 1029d792f6d..8201804a13f 100644 --- a/web/core/components/web-hooks/form/delete-section.tsx +++ b/web/core/components/web-hooks/form/delete-section.tsx @@ -2,6 +2,7 @@ import { ChevronDown, ChevronUp } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { Button } from "@plane/ui"; type Props = { @@ -36,7 +37,11 @@ export const WebhookDeleteSection: React.FC = (props) => { webhook.
-
diff --git a/web/core/components/web-hooks/form/form.tsx b/web/core/components/web-hooks/form/form.tsx index 5ecd7ff8e17..804b708e8c3 100644 --- a/web/core/components/web-hooks/form/form.tsx +++ b/web/core/components/web-hooks/form/form.tsx @@ -3,6 +3,7 @@ import React, { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWebhook, TWebhookEventTypes } from "@plane/types"; // hooks @@ -93,7 +94,11 @@ export const WebhookForm: FC = observer((props) => { {data ? (
-
diff --git a/web/core/components/web-hooks/form/toggle.tsx b/web/core/components/web-hooks/form/toggle.tsx index ca240435b55..76dfca3ed9c 100644 --- a/web/core/components/web-hooks/form/toggle.tsx +++ b/web/core/components/web-hooks/form/toggle.tsx @@ -1,10 +1,13 @@ "use client"; import { Control, Controller } from "react-hook-form"; +// constants +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // ui import { ToggleSwitch } from "@plane/ui"; -// types +// hooks +import { captureClick } from "@/helpers/event-tracker.helper"; interface IWebHookToggle { control: Control; @@ -20,6 +23,9 @@ export const WebhookToggle = ({ control }: IWebHookToggle) => ( { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_DETAILS_PAGE_TOGGLE_SWITCH, + }); onChange(val); }} size="sm" diff --git a/web/core/components/web-hooks/webhooks-list-item.tsx b/web/core/components/web-hooks/webhooks-list-item.tsx index 0c5d0ea8ea1..343f45f8cef 100644 --- a/web/core/components/web-hooks/webhooks-list-item.tsx +++ b/web/core/components/web-hooks/webhooks-list-item.tsx @@ -3,9 +3,11 @@ import { FC } from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // hooks import { ToggleSwitch } from "@plane/ui"; +import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store"; // ui // types @@ -23,8 +25,34 @@ export const WebhooksListItem: FC = (props) => { const handleToggle = () => { if (!workspaceSlug || !webhook.id) return; - - updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }); + try { + updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }); + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, + }, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "SUCCESS", + payload: { + webhook: webhook.url, + }, + }, + }); + } catch (error) { + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, + }, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "ERROR", + payload: { + webhook: webhook.url, + }, + }, + }); + } }; return ( From 34766091cf53a79ec93b226de500559a04583753 Mon Sep 17 00:00:00 2001 From: gakshita Date: Wed, 2 Jul 2025 16:09:38 +0530 Subject: [PATCH 38/38] fix: refactor --- packages/constants/src/event-tracker/core.ts | 4 +- .../billing/comparison/plan-detail.tsx | 2 +- web/core/components/exporter/export-form.tsx | 3 +- .../web-hooks/webhooks-list-item.tsx | 51 ++++++++++--------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index 20c866fa319..83650319f2d 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -285,8 +285,8 @@ export const WORKSPACE_SETTINGS_TRACKER_EVENTS = { }; export const WORKSPACE_SETTINGS_TRACKER_ELEMENTS = { // Billing - BILLING_UPGRADE_BUTTON: (subscriptionType: EProductSubscriptionEnum) => `biling_upgrade_${subscriptionType}_button`, - BILING_TALK_TO_SALES_BUTTON: "biling_talk_to_sales_button", + BILLING_UPGRADE_BUTTON: (subscriptionType: EProductSubscriptionEnum) => `billing_upgrade_${subscriptionType}_button`, + BILLING_TALK_TO_SALES_BUTTON: "billing_talk_to_sales_button", // Exports EXPORT_BUTTON: "export_button", // Webhooks diff --git a/web/ce/components/workspace/billing/comparison/plan-detail.tsx b/web/ce/components/workspace/billing/comparison/plan-detail.tsx index 45fcb452256..2eeb134d529 100644 --- a/web/ce/components/workspace/billing/comparison/plan-detail.tsx +++ b/web/ce/components/workspace/billing/comparison/plan-detail.tsx @@ -116,7 +116,7 @@ export const PlanDetail: FC = observer((props) => { data-ph-element={ isSubscriptionActive ? WORKSPACE_SETTINGS_TRACKER_ELEMENTS.BILLING_UPGRADE_BUTTON(subscriptionType) - : WORKSPACE_SETTINGS_TRACKER_ELEMENTS.BILING_TALK_TO_SALES_BUTTON + : WORKSPACE_SETTINGS_TRACKER_ELEMENTS.BILLING_TALK_TO_SALES_BUTTON } > {isSubscriptionActive ? `Upgrade to ${subscriptionName}` : t("common.upgrade_cta.talk_to_sales")} diff --git a/web/core/components/exporter/export-form.tsx b/web/core/components/exporter/export-form.tsx index e910ed6a3d2..0b097aeb667 100644 --- a/web/core/components/exporter/export-form.tsx +++ b/web/core/components/exporter/export-form.tsx @@ -101,13 +101,14 @@ export const ExportForm = (props: Props) => { }), }); }) - .catch(() => { + .catch((error) => { setExportLoading(false); captureError({ eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.csv_exported, payload: { provider: formData.provider.provider, }, + error: error as Error, }); setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/core/components/web-hooks/webhooks-list-item.tsx b/web/core/components/web-hooks/webhooks-list-item.tsx index 343f45f8cef..5419207d89d 100644 --- a/web/core/components/web-hooks/webhooks-list-item.tsx +++ b/web/core/components/web-hooks/webhooks-list-item.tsx @@ -25,34 +25,35 @@ export const WebhooksListItem: FC = (props) => { const handleToggle = () => { if (!workspaceSlug || !webhook.id) return; - try { - updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }); - captureElementAndEvent({ - element: { - elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, - }, - event: { - eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, - state: "SUCCESS", - payload: { - webhook: webhook.url, + updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }) + .then(() => { + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, }, - }, - }); - } catch (error) { - captureElementAndEvent({ - element: { - elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, - }, - event: { - eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, - state: "ERROR", - payload: { - webhook: webhook.url, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "SUCCESS", + payload: { + webhook: webhook.url, + }, + }, + }); + }) + .catch(() => { + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, + }, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "ERROR", + payload: { + webhook: webhook.url, + }, }, - }, + }); }); - } }; return (