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 c320e3c15763edf56220bba47f615f60f3019aee Mon Sep 17 00:00:00 2001 From: gakshita Date: Wed, 2 Jul 2025 14:37:59 +0530 Subject: [PATCH 37/38] chore: profile settings events --- packages/constants/src/event-tracker/core.ts | 35 +++++++++++++++ .../settings/account/api-tokens/page.tsx | 23 ++++++++-- .../account/deactivate-account-modal.tsx | 14 ++++-- .../api-token/delete-token-modal.tsx | 17 +++++++ .../api-token/modal/create-token-modal.tsx | 12 +++++ .../components/api-token/token-list-item.tsx | 4 +- .../core/theme/custom-theme-selector.tsx | 14 ++++++ web/core/components/profile/form.tsx | 18 +++++++- .../notification/email-notification-form.tsx | 26 +++++++++++ .../profile/preferences/language-timezone.tsx | 44 +++++++++++++++++++ .../profile/start-of-week-preference.tsx | 28 +++++++++++- 11 files changed, 225 insertions(+), 10 deletions(-) diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index dd40d345933..5248a49af8b 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -268,3 +268,38 @@ export const SIDEBAR_TRACKER_ELEMENTS = { USER_MENU_ITEM: "sidenav_user_menu_item", CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button", }; + +export const PROFILE_SETTINGS_TRACKER_EVENTS = { + // Account + deactivate_account: "deactivate_account", + update_profile: "update_profile", + // Preferences + first_day_updated: "first_day_updated", + language_updated: "language_updated", + timezone_updated: "timezone_updated", + theme_updated: "theme_updated", + // Notifications + notifications_updated: "notifications_updated", + // PAT + pat_created: "pat_created", + pat_deleted: "pat_deleted", +}; +export const PROFILE_SETTINGS_TRACKER_ELEMENTS = { + // Account + SAVE_CHANGES_BUTTON: "save_changes_button", + DEACTIVATE_ACCOUNT_BUTTON: "deactivate_account_button", + // Preferences + THEME_DROPDOWN: "preferences_theme_dropdown", + FIRST_DAY_OF_WEEK_DROPDOWN: "preferences_first_day_of_week_dropdown", + LANGUAGE_DROPDOWN: "preferences_language_dropdown", + TIMEZONE_DROPDOWN: "preferences_timezone_dropdown", + // Notifications + PROPERTY_CHANGES_TOGGLE: "notifications_property_changes_toggle", + STATE_CHANGES_TOGGLE: "notifications_state_changes_toggle", + COMMENTS_TOGGLE: "notifications_comments_toggle", + MENTIONS_TOGGLE: "notifications_mentions_toggle", + // PAT + HEADER_ADD_PAT_BUTTON: "header_add_pat_button", + EMPTY_STATE_ADD_PAT_BUTTON: "empty_state_add_pat_button", + LIST_ITEM_DELETE_ICON: "list_item_delete_icon", +}; diff --git a/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx b/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx index 9a1883255db..ecbc2f9aa58 100644 --- a/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx @@ -4,6 +4,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; // plane imports +import { PROFILE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // component import { APITokenService } from "@plane/services"; @@ -14,6 +15,7 @@ import { SettingsHeading } from "@/components/settings"; import { APITokenSettingsLoader } from "@/components/ui"; import { API_TOKENS_LIST } from "@/constants/fetch-keys"; // store hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; // services @@ -53,7 +55,12 @@ const ApiTokensPage = observer(() => { description={t("account_settings.api_tokens.description")} button={{ label: t("workspace_settings.settings.api_tokens.add_token"), - onClick: () => setIsCreateTokenModalOpen(true), + onClick: () => { + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.HEADER_ADD_PAT_BUTTON, + }); + setIsCreateTokenModalOpen(true); + }, }} />
@@ -69,7 +76,12 @@ const ApiTokensPage = observer(() => { description={t("account_settings.api_tokens.description")} button={{ label: t("workspace_settings.settings.api_tokens.add_token"), - onClick: () => setIsCreateTokenModalOpen(true), + onClick: () => { + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.HEADER_ADD_PAT_BUTTON, + }); + setIsCreateTokenModalOpen(true); + }, }} />
@@ -81,7 +93,12 @@ const ApiTokensPage = observer(() => { size="md" primaryButton={{ text: t("workspace_settings.settings.api_tokens.add_token"), - onClick: () => setIsCreateTokenModalOpen(true), + onClick: () => { + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_PAT_BUTTON, + }); + setIsCreateTokenModalOpen(true); + }, }} />
diff --git a/web/core/components/account/deactivate-account-modal.tsx b/web/core/components/account/deactivate-account-modal.tsx index 1132a8d74cb..684416184b2 100644 --- a/web/core/components/account/deactivate-account-modal.tsx +++ b/web/core/components/account/deactivate-account-modal.tsx @@ -3,10 +3,12 @@ import React, { useState } from "react"; import { Trash2 } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; +import { PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUser } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -35,6 +37,9 @@ export const DeactivateAccountModal: React.FC = (props) => { await deactivateAccount() .then(() => { + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.deactivate_account, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -44,13 +49,16 @@ export const DeactivateAccountModal: React.FC = (props) => { router.push("/"); handleClose(); }) - .catch((err: any) => + .catch((err: any) => { + captureError({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.deactivate_account, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: err?.error, - }) - ) + }); + }) .finally(() => setIsDeactivating(false)); }; diff --git a/web/core/components/api-token/delete-token-modal.tsx b/web/core/components/api-token/delete-token-modal.tsx index eed0ecdb90c..f139a278317 100644 --- a/web/core/components/api-token/delete-token-modal.tsx +++ b/web/core/components/api-token/delete-token-modal.tsx @@ -3,6 +3,7 @@ import { useState, FC } from "react"; import { mutate } from "swr"; // types +import { PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { APITokenService } from "@plane/services"; import { IApiToken } from "@plane/types"; @@ -10,6 +11,7 @@ import { IApiToken } from "@plane/types"; import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // fetch-keys import { API_TOKENS_LIST } from "@/constants/fetch-keys"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; type Props = { isOpen: boolean; @@ -48,6 +50,12 @@ export const DeleteApiTokenModal: FC = (props) => { (prevData) => (prevData ?? []).filter((token) => token.id !== tokenId), false ); + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.pat_deleted, + payload: { + token: tokenId, + }, + }); handleClose(); }) @@ -58,6 +66,15 @@ export const DeleteApiTokenModal: FC = (props) => { message: err?.message ?? t("workspace_settings.settings.api_tokens.delete.error.message"), }) ) + .catch((err) => { + captureError({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.pat_deleted, + payload: { + token: tokenId, + }, + error: err as Error, + }); + }) .finally(() => setDeleteLoading(false)); }; diff --git a/web/core/components/api-token/modal/create-token-modal.tsx b/web/core/components/api-token/modal/create-token-modal.tsx index 94d72c56d17..d7d6e14d376 100644 --- a/web/core/components/api-token/modal/create-token-modal.tsx +++ b/web/core/components/api-token/modal/create-token-modal.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { mutate } from "swr"; // types +import { PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { APITokenService } from "@plane/services"; import { IApiToken } from "@plane/types"; // ui @@ -12,6 +13,7 @@ import { renderFormattedDate, csvDownload } from "@plane/utils"; import { CreateApiTokenForm, GeneratedTokenDetails } from "@/components/api-token"; // fetch-keys import { API_TOKENS_LIST } from "@/constants/fetch-keys"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; // helpers // services @@ -66,6 +68,12 @@ export const CreateApiTokenModal: React.FC = (props) => { }, false ); + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.pat_created, + payload: { + token: res.id, + }, + }); }) .catch((err) => { setToast({ @@ -74,6 +82,10 @@ export const CreateApiTokenModal: React.FC = (props) => { message: err.message || err.detail, }); + captureError({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.pat_created, + }); + throw err; }); }; diff --git a/web/core/components/api-token/token-list-item.tsx b/web/core/components/api-token/token-list-item.tsx index 37b3549608f..f5fe9fbd80c 100644 --- a/web/core/components/api-token/token-list-item.tsx +++ b/web/core/components/api-token/token-list-item.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { XCircle } from "lucide-react"; +import { PROFILE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { IApiToken } from "@plane/types"; // components import { Tooltip } from "@plane/ui"; @@ -31,6 +32,7 @@ export const ApiTokenListItem: React.FC = (props) => { @@ -60,4 +62,4 @@ export const ApiTokenListItem: React.FC = (props) => {
); -}; \ No newline at end of file +}; diff --git a/web/core/components/core/theme/custom-theme-selector.tsx b/web/core/components/core/theme/custom-theme-selector.tsx index d66fbe82f83..b0d192d3258 100644 --- a/web/core/components/core/theme/custom-theme-selector.tsx +++ b/web/core/components/core/theme/custom-theme-selector.tsx @@ -4,11 +4,13 @@ import { useMemo } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; // types +import { PROFILE_SETTINGS_TRACKER_ELEMENTS, PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IUserTheme } from "@plane/types"; // ui import { Button, InputColorPicker, setPromiseToast } from "@plane/ui"; // hooks +import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; import { useUserProfile } from "@/hooks/store"; type TCustomThemeSelector = { @@ -70,6 +72,18 @@ export const CustomThemeSelector: React.FC = observer((pro applyThemeChange(payload); const updateCurrentUserThemePromise = updateUserTheme(payload); + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.THEME_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.theme_updated, + payload: { + theme: payload.theme, + }, + state: "SUCCESS", + }, + }); setPromiseToast(updateCurrentUserThemePromise, { loading: t("updating_theme"), success: { diff --git a/web/core/components/profile/form.tsx b/web/core/components/profile/form.tsx index 0cd08d75d01..2f7a624072f 100644 --- a/web/core/components/profile/form.tsx +++ b/web/core/components/profile/form.tsx @@ -6,6 +6,7 @@ import { Controller, useForm } from "react-hook-form"; import { ChevronDown, CircleUserRound, InfoIcon } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // plane imports +import { PROFILE_SETTINGS_TRACKER_ELEMENTS, PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { IUser, TUserProfile } from "@plane/types"; import { Button, Input, TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; @@ -16,6 +17,7 @@ import { DeactivateAccountModal } from "@/components/account"; import { ImagePickerPopover, UserImageUploadModal } from "@/components/core"; // helpers // hooks +import { captureSuccess } from "@/helpers/event-tracker.helper"; import { useUser, useUserProfile } from "@/hooks/store"; type TUserProfileForm = { @@ -124,6 +126,9 @@ export const ProfileForm = observer((props: TProfileFormProps) => { const promises = [updateCurrentUserDetail, updateCurrentUserProfile]; const updateUserAndProfile = Promise.all(promises); + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.update_profile, + }); setPromiseToast(updateUserAndProfile, { loading: "Updating...", success: { @@ -344,7 +349,12 @@ export const ProfileForm = observer((props: TProfileFormProps) => {
-
@@ -371,7 +381,11 @@ export const ProfileForm = observer((props: TProfileFormProps) => {
{t("deactivate_account_description")}
-
diff --git a/web/core/components/profile/notification/email-notification-form.tsx b/web/core/components/profile/notification/email-notification-form.tsx index 9aab9a92fea..d0ec45ae5bf 100644 --- a/web/core/components/profile/notification/email-notification-form.tsx +++ b/web/core/components/profile/notification/email-notification-form.tsx @@ -2,11 +2,13 @@ import React, { FC, useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; +import { PROFILE_SETTINGS_TRACKER_ELEMENTS, PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IUserEmailNotificationSettings } from "@plane/types"; // ui import { ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; // services +import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { UserService } from "@/services/user.service"; // types interface IEmailNotificationFormProps { @@ -31,6 +33,12 @@ export const EmailNotificationForm: FC = (props) => await userService.updateCurrentUserEmailNotificationSettings({ [key]: value, }); + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.notifications_updated, + payload: { + [key]: value, + }, + }); setToast({ title: t("success"), type: TOAST_TYPE.SUCCESS, @@ -38,6 +46,12 @@ export const EmailNotificationForm: FC = (props) => }); } catch (err) { console.error(err); + captureError({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.notifications_updated, + payload: { + [key]: value, + }, + }); setToast({ title: t("error"), type: TOAST_TYPE.ERROR, @@ -68,6 +82,9 @@ export const EmailNotificationForm: FC = (props) => value={value} onChange={(newValue) => { onChange(newValue); + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.PROPERTY_CHANGES_TOGGLE, + }); handleSettingChange("property_change", newValue); }} size="sm" @@ -90,6 +107,9 @@ export const EmailNotificationForm: FC = (props) => value={value} onChange={(newValue) => { onChange(newValue); + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.STATE_CHANGES_TOGGLE, + }); handleSettingChange("state_change", newValue); }} size="sm" @@ -134,6 +154,9 @@ export const EmailNotificationForm: FC = (props) => value={value} onChange={(newValue) => { onChange(newValue); + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.COMMENTS_TOGGLE, + }); handleSettingChange("comment", newValue); }} size="sm" @@ -156,6 +179,9 @@ export const EmailNotificationForm: FC = (props) => value={value} onChange={(newValue) => { onChange(newValue); + captureClick({ + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.MENTIONS_TOGGLE, + }); handleSettingChange("mention", newValue); }} size="sm" diff --git a/web/core/components/profile/preferences/language-timezone.tsx b/web/core/components/profile/preferences/language-timezone.tsx index 9a40cb880c9..8d171ee36c8 100644 --- a/web/core/components/profile/preferences/language-timezone.tsx +++ b/web/core/components/profile/preferences/language-timezone.tsx @@ -1,7 +1,9 @@ import { observer } from "mobx-react"; +import { PROFILE_SETTINGS_TRACKER_ELEMENTS, PROFILE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { SUPPORTED_LANGUAGES, useTranslation } from "@plane/i18n"; import { CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; import { TimezoneSelect } from "@/components/global"; +import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; import { useUser, useUserProfile } from "@/hooks/store"; export const LanguageTimezone = observer(() => { @@ -17,6 +19,18 @@ export const LanguageTimezone = observer(() => { const handleTimezoneChange = (value: string) => { updateCurrentUser({ user_timezone: value }) .then(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.TIMEZONE_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.timezone_updated, + payload: { + timezone: value, + }, + state: "SUCCESS", + }, + }); setToast({ title: "Success!", message: "Timezone updated successfully", @@ -24,6 +38,15 @@ export const LanguageTimezone = observer(() => { }); }) .catch(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.TIMEZONE_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.timezone_updated, + state: "ERROR", + }, + }); setToast({ title: "Error!", message: "Failed to update timezone", @@ -34,6 +57,18 @@ export const LanguageTimezone = observer(() => { const handleLanguageChange = (value: string) => { updateUserProfile({ language: value }) .then(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.LANGUAGE_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.language_updated, + payload: { + language: value, + }, + state: "SUCCESS", + }, + }); setToast({ title: "Success!", message: "Language updated successfully", @@ -41,6 +76,15 @@ export const LanguageTimezone = observer(() => { }); }) .catch(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.LANGUAGE_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.language_updated, + state: "ERROR", + }, + }); setToast({ title: "Error!", message: "Failed to update language", diff --git a/web/core/components/profile/start-of-week-preference.tsx b/web/core/components/profile/start-of-week-preference.tsx index 06ed171035b..580126d97a4 100644 --- a/web/core/components/profile/start-of-week-preference.tsx +++ b/web/core/components/profile/start-of-week-preference.tsx @@ -3,10 +3,15 @@ import React from "react"; import { observer } from "mobx-react"; // plane imports -import { START_OF_THE_WEEK_OPTIONS } from "@plane/constants"; +import { + PROFILE_SETTINGS_TRACKER_ELEMENTS, + PROFILE_SETTINGS_TRACKER_EVENTS, + START_OF_THE_WEEK_OPTIONS, +} from "@plane/constants"; import { EStartOfTheWeek } from "@plane/types"; import { CustomSelect, setToast, TOAST_TYPE } from "@plane/ui"; // hooks +import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; import { useUserProfile } from "@/hooks/store"; import { PreferencesSection } from "../preferences/section"; @@ -29,6 +34,18 @@ export const StartOfWeekPreference = observer((props: { option: { title: string; onChange={(val: number) => { updateUserProfile({ start_of_the_week: val }) .then(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.FIRST_DAY_OF_WEEK_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.first_day_updated, + payload: { + start_of_the_week: val, + }, + state: "SUCCESS", + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success", @@ -36,6 +53,15 @@ export const StartOfWeekPreference = observer((props: { option: { title: string; }); }) .catch(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.FIRST_DAY_OF_WEEK_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.first_day_updated, + state: "ERROR", + }, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Update failed", message: "Please try again later." }); }); }} From 444c5d6aef06b9d5916d391c64512dbbf8bb345e Mon Sep 17 00:00:00 2001 From: gakshita Date: Wed, 2 Jul 2025 16:14:38 +0530 Subject: [PATCH 38/38] fix: refactor --- .../core/theme/custom-theme-selector.tsx | 41 +- web/core/components/profile/form.tsx | 16 +- yarn.lock | 581 ------------------ 3 files changed, 41 insertions(+), 597 deletions(-) diff --git a/web/core/components/core/theme/custom-theme-selector.tsx b/web/core/components/core/theme/custom-theme-selector.tsx index b0d192d3258..3cc5469afe5 100644 --- a/web/core/components/core/theme/custom-theme-selector.tsx +++ b/web/core/components/core/theme/custom-theme-selector.tsx @@ -72,18 +72,6 @@ export const CustomThemeSelector: React.FC = observer((pro applyThemeChange(payload); const updateCurrentUserThemePromise = updateUserTheme(payload); - captureElementAndEvent({ - element: { - elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.THEME_DROPDOWN, - }, - event: { - eventName: PROFILE_SETTINGS_TRACKER_EVENTS.theme_updated, - payload: { - theme: payload.theme, - }, - state: "SUCCESS", - }, - }); setPromiseToast(updateCurrentUserThemePromise, { loading: t("updating_theme"), success: { @@ -95,6 +83,35 @@ export const CustomThemeSelector: React.FC = observer((pro message: () => t("failed_to_update_the_theme"), }, }); + updateCurrentUserThemePromise + .then(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.THEME_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.theme_updated, + payload: { + theme: payload.theme, + }, + state: "SUCCESS", + }, + }); + }) + .catch(() => { + captureElementAndEvent({ + element: { + elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.THEME_DROPDOWN, + }, + event: { + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.theme_updated, + payload: { + theme: payload.theme, + }, + state: "ERROR", + }, + }); + }); return; }; diff --git a/web/core/components/profile/form.tsx b/web/core/components/profile/form.tsx index 2f7a624072f..ffc74e5fc05 100644 --- a/web/core/components/profile/form.tsx +++ b/web/core/components/profile/form.tsx @@ -17,7 +17,7 @@ import { DeactivateAccountModal } from "@/components/account"; import { ImagePickerPopover, UserImageUploadModal } from "@/components/core"; // helpers // hooks -import { captureSuccess } from "@/helpers/event-tracker.helper"; +import { captureSuccess, captureError } from "@/helpers/event-tracker.helper"; import { useUser, useUserProfile } from "@/hooks/store"; type TUserProfileForm = { @@ -126,9 +126,6 @@ export const ProfileForm = observer((props: TProfileFormProps) => { const promises = [updateCurrentUserDetail, updateCurrentUserProfile]; const updateUserAndProfile = Promise.all(promises); - captureSuccess({ - eventName: PROFILE_SETTINGS_TRACKER_EVENTS.update_profile, - }); setPromiseToast(updateUserAndProfile, { loading: "Updating...", success: { @@ -140,6 +137,17 @@ export const ProfileForm = observer((props: TProfileFormProps) => { message: () => `There was some error in updating your profile. Please try again.`, }, }); + updateUserAndProfile + .then(() => { + captureSuccess({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.update_profile, + }); + }) + .catch(() => { + captureError({ + eventName: PROFILE_SETTINGS_TRACKER_EVENTS.update_profile, + }); + }); }; return ( diff --git a/yarn.lock b/yarn.lock index 2b9ebf4891d..fcc75cf0d4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -148,7 +148,6 @@ "@babel/helpers@7.26.10", "@babel/helpers@^7.27.4": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== dependencies: "@babel/template" "^7.26.9" @@ -162,586 +161,6 @@ "@babel/types" "^7.27.3" "@babel/runtime@7.26.10", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.26.10" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== - dependencies: - "@babel/types" "^7.26.10" - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" - integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" - integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" - integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" - integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" - integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-import-assertions@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" - integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-attributes@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" - integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-jsx@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" - integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-typescript@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" - integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" - integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-async-generator-functions@^7.26.8": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz#5e3991135e3b9c6eaaf5eff56d1ae5a11df45ff8" - integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-remap-async-to-generator" "^7.25.9" - "@babel/traverse" "^7.26.8" - -"@babel/plugin-transform-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" - integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" - -"@babel/plugin-transform-block-scoped-functions@^7.26.5": - version "7.26.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" - integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-block-scoping@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" - integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" - integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-static-block@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" - integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-classes@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" - integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/traverse" "^7.25.9" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" - integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/template" "^7.25.9" - -"@babel/plugin-transform-destructuring@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" - integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dotall-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" - integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-keys@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" - integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" - integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dynamic-import@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" - integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-exponentiation-operator@^7.26.3": - version "7.26.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" - integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-export-namespace-from@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" - integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-for-of@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755" - integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-function-name@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" - integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-json-strings@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" - integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" - integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-logical-assignment-operators@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" - integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-member-expression-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" - integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-amd@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" - integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3": - version "7.26.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" - integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== - dependencies: - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-systemjs@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" - integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-modules-umd@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" - integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" - integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-new-target@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" - integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": - version "7.26.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" - integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-numeric-separator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" - integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-object-rest-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" - integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - -"@babel/plugin-transform-object-super@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" - integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - -"@babel/plugin-transform-optional-catch-binding@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" - integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" - integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-parameters@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" - integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-methods@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-property-in-object@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" - integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-property-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" - integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-regenerator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" - integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - regenerator-transform "^0.15.2" - -"@babel/plugin-transform-regexp-modifiers@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" - integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-reserved-words@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" - integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-shorthand-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" - integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" - integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-sticky-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" - integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-template-literals@^7.26.8": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz#966b15d153a991172a540a69ad5e1845ced990b5" - integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-typeof-symbol@^7.26.7": - version "7.26.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" - integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-typescript@^7.25.9": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950" - integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-syntax-typescript" "^7.25.9" - -"@babel/plugin-transform-unicode-escapes@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" - integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-property-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" - integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" - integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-sets-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" - integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/preset-env@^7.25.4": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.8.tgz#7af0090829b606d2046db99679004731e1dc364d" - integrity sha512-um7Sy+2THd697S4zJEfv/U5MHGJzkN2xhtsR3T/SWRbVSic62nbISh51VVfU9JiO/L/Z97QczHTaFVkOU8IzNg== - dependencies: - "@babel/compat-data" "^7.26.8" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.26.0" - "@babel/plugin-syntax-import-attributes" "^7.26.0" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.25.9" - "@babel/plugin-transform-async-generator-functions" "^7.26.8" - "@babel/plugin-transform-async-to-generator" "^7.25.9" - "@babel/plugin-transform-block-scoped-functions" "^7.26.5" - "@babel/plugin-transform-block-scoping" "^7.25.9" - "@babel/plugin-transform-class-properties" "^7.25.9" - "@babel/plugin-transform-class-static-block" "^7.26.0" - "@babel/plugin-transform-classes" "^7.25.9" - "@babel/plugin-transform-computed-properties" "^7.25.9" - "@babel/plugin-transform-destructuring" "^7.25.9" - "@babel/plugin-transform-dotall-regex" "^7.25.9" - "@babel/plugin-transform-duplicate-keys" "^7.25.9" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-dynamic-import" "^7.25.9" - "@babel/plugin-transform-exponentiation-operator" "^7.26.3" - "@babel/plugin-transform-export-namespace-from" "^7.25.9" - "@babel/plugin-transform-for-of" "^7.25.9" - "@babel/plugin-transform-function-name" "^7.25.9" - "@babel/plugin-transform-json-strings" "^7.25.9" - "@babel/plugin-transform-literals" "^7.25.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" - "@babel/plugin-transform-member-expression-literals" "^7.25.9" - "@babel/plugin-transform-modules-amd" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.26.3" - "@babel/plugin-transform-modules-systemjs" "^7.25.9" - "@babel/plugin-transform-modules-umd" "^7.25.9" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-new-target" "^7.25.9" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" - "@babel/plugin-transform-numeric-separator" "^7.25.9" - "@babel/plugin-transform-object-rest-spread" "^7.25.9" - "@babel/plugin-transform-object-super" "^7.25.9" - "@babel/plugin-transform-optional-catch-binding" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - "@babel/plugin-transform-private-methods" "^7.25.9" - "@babel/plugin-transform-private-property-in-object" "^7.25.9" - "@babel/plugin-transform-property-literals" "^7.25.9" - "@babel/plugin-transform-regenerator" "^7.25.9" - "@babel/plugin-transform-regexp-modifiers" "^7.26.0" - "@babel/plugin-transform-reserved-words" "^7.25.9" - "@babel/plugin-transform-shorthand-properties" "^7.25.9" - "@babel/plugin-transform-spread" "^7.25.9" - "@babel/plugin-transform-sticky-regex" "^7.25.9" - "@babel/plugin-transform-template-literals" "^7.26.8" - "@babel/plugin-transform-typeof-symbol" "^7.26.7" - "@babel/plugin-transform-unicode-escapes" "^7.25.9" - "@babel/plugin-transform-unicode-property-regex" "^7.25.9" - "@babel/plugin-transform-unicode-regex" "^7.25.9" - "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.11.0" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.40.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/preset-typescript@^7.24.7": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" - integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-syntax-jsx" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.25.9" - "@babel/plugin-transform-typescript" "^7.25.9" - -"@babel/runtime@7.26.10", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==