diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index 85f2ea6e2ba..dd40d345933 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -1,163 +1,46 @@ -export type IssueEventProps = { - eventName: string; - payload: any; - updates?: any; - path?: string; -}; - -export type EventProps = { - eventName: string; - payload: any; - updates?: any; - 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, - 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 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, - module_id: payload.id, - created_at: payload.created_at, - updated_at: payload.updated_at, - start_date: payload.start_date, - target_date: payload.target_date, - module_status: payload.status, - lead_id: payload.lead, - changed_properties: payload.changed_properties, - member_ids: payload.members, - state: payload.state, - element: payload.element, -}); - -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"; + // Groups export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics"; +// Command palette tracker +export const COMMAND_PALETTE_TRACKER_ELEMENTS = { + COMMAND_PALETTE_SHORTCUT_KEY: "command_palette_shortcut_key", +}; + export const WORKSPACE_TRACKER_EVENTS = { create: "workspace_created", update: "workspace_updated", delete: "workspace_deleted", }; +export const WORKSPACE_TRACKER_ELEMENTS = { + DELETE_WORKSPACE_BUTTON: "delete_workspace_button", + ONBOARDING_CREATE_WORKSPACE_BUTTON: "onboarding_create_workspace_button", + CREATE_WORKSPACE_BUTTON: "create_workspace_button", + UPDATE_WORKSPACE_BUTTON: "update_workspace_button", +}; export const PROJECT_TRACKER_EVENTS = { create: "project_created", 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", + 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", + CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE: "create_project_jira_import_detail_page", + TOGGLE_FEATURE: "toggle_project_feature", +}; export const CYCLE_TRACKER_EVENTS = { create: "cycle_created", @@ -165,7 +48,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", @@ -173,32 +67,120 @@ export const MODULE_TRACKER_EVENTS = { delete: "module_deleted", favorite: "module_favorited", unfavorite: "module_unfavorited", + archive: "module_archived", + restore: "module_restored", link: { create: "module_link_created", update: "module_link_updated", delete: "module_link_deleted", }, }; +export const MODULE_TRACKER_ELEMENTS = { + RIGHT_HEADER_ADD_BUTTON: "right_header_add_module_button", + EMPTY_STATE_ADD_BUTTON: "empty_state_add_module_button", + COMMAND_PALETTE_ADD_ITEM: "command_palette_add_module_item", + RIGHT_SIDEBAR: "module_right_sidebar", + QUICK_ACTIONS: "module_quick_actions", + CONTEXT_MENU: "module_context_menu", + LIST_ITEM: "module_list_item", + CARD_ITEM: "module_card_item", +} as const; export const WORK_ITEM_TRACKER_EVENTS = { create: "work_item_created", + add_existing: "work_item_add_existing", update: "work_item_updated", delete: "work_item_deleted", archive: "work_item_archived", restore: "work_item_restored", + attachment: { + add: "work_item_attachment_added", + remove: "work_item_attachment_removed", + }, + sub_issue: { + update: "sub_issue_updated", + remove: "sub_issue_removed", + delete: "sub_issue_deleted", + create: "sub_issue_created", + add_existing: "sub_issue_add_existing", + }, + draft: { + create: "draft_work_item_created", + }, }; +export const WORK_ITEM_TRACKER_ELEMENTS = { + HEADER_ADD_BUTTON: { + WORK_ITEMS: "work_items_header_add_work_item_button", + PROJECT_VIEW: "project_view_header_add_work_item_button", + CYCLE: "cycle_header_add_work_item_button", + MODULE: "module_header_add_work_item_button", + }, + COMMAND_PALETTE_ADD_BUTTON: "command_palette_add_work_item_button", + EMPTY_STATE_ADD_BUTTON: { + WORK_ITEMS: "work_items_empty_state_add_work_item_button", + PROJECT_VIEW: "project_view_empty_state_add_work_item_button", + CYCLE: "cycle_empty_state_add_work_item_button", + MODULE: "module_empty_state_add_work_item_button", + GLOBAL_VIEW: "global_view_empty_state_add_work_item_button", + }, + QUICK_ACTIONS: { + WORK_ITEMS: "work_items_quick_actions", + PROJECT_VIEW: "project_view_work_items_quick_actions", + CYCLE: "cycle_work_items_quick_actions", + MODULE: "module_work_items_quick_actions", + GLOBAL_VIEW: "global_view_work_items_quick_actions", + ARCHIVED: "archived_work_items_quick_actions", + DRAFT: "draft_work_items_quick_actions", + }, + CONTEXT_MENU: { + WORK_ITEMS: "work_items_context_menu", + PROJECT_VIEW: "project_view_context_menu", + CYCLE: "cycle_context_menu", + MODULE: "module_context_menu", + GLOBAL_VIEW: "global_view_context_menu", + ARCHIVED: "archived_context_menu", + DRAFT: "draft_context_menu", + }, +} as const; export const STATE_TRACKER_EVENTS = { create: "state_created", update: "state_updated", delete: "state_deleted", }; +export const STATE_TRACKER_ELEMENTS = { + STATE_GROUP_ADD_BUTTON: "state_group_add_button", + STATE_LIST_DELETE_BUTTON: "state_list_delete_button", + STATE_LIST_EDIT_BUTTON: "state_list_edit_button", +}; export const PROJECT_PAGE_TRACKER_EVENTS = { create: "project_page_created", update: "project_page_updated", delete: "project_page_deleted", + archive: "project_page_archived", + restore: "project_page_restored", + lock: "project_page_locked", + unlock: "project_page_unlocked", + access_update: "project_page_access_updated", + duplicate: "project_page_duplicated", + favorite: "project_page_favorited", + 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", + COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_page_button", + CONTEXT_MENU: "page_context_menu", + QUICK_ACTIONS: "page_quick_actions", + LIST_ITEM: "page_list_item", + FAVORITE_BUTTON: "page_favorite_button", + ARCHIVE_BUTTON: "page_archive_button", + LOCK_BUTTON: "page_lock_button", + ACCESS_TOGGLE: "page_access_toggle", + DUPLICATE_BUTTON: "page_duplicate_button", +} as const; export const MEMBER_TRACKER_EVENTS = { invite: "member_invited", @@ -211,48 +193,78 @@ 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", + ONBOARDING_JOIN_WORKSPACE: "workspace_join_continue_to_workspace_button", + ONBOARDING_INVITE_MEMBER: "invite_member_continue_button", + SIDEBAR_PROJECT_QUICK_ACTIONS: "sidebar_project_quick_actions", + PROJECT_MEMBER_TABLE_CONTEXT_MENU: "project_member_table_context_menu", + WORKSPACE_MEMBER_TABLE_CONTEXT_MENU: "workspace_member_table_context_menu", + WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU: "workspace_invitations_list_context_menu", +} 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 PRODUCT_TOUR_TRACKER_EVENTS = { - start: "product_tour_started", - complete: "product_tour_completed", - skip: "product_tour_skipped", +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_TOUR_TRACKER_EVENTS = { +export const GLOBAL_VIEW_TRACKER_EVENTS = { create: "global_view_created", update: "global_view_updated", delete: "global_view_deleted", open: "global_view_opened", }; +export const GLOBAL_VIEW_TRACKER_ELEMENTS = { + RIGHT_HEADER_ADD_BUTTON: "global_view_right_header_add_button", + HEADER_SAVE_VIEW_BUTTON: "global_view_header_save_view_button", + QUICK_ACTIONS: "global_view_quick_actions", + LIST_ITEM: "global_view_list_item", +}; + +export const PRODUCT_TOUR_TRACKER_EVENTS = { + complete: "product_tour_completed", +}; +export const PRODUCT_TOUR_TRACKER_ELEMENTS = { + START_BUTTON: "product_tour_start_button", + SKIP_BUTTON: "product_tour_skip_button", + CREATE_PROJECT_BUTTON: "product_tour_create_project_button", +}; 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_UNARCHIVE_BUTTON: "archive_unarchive_button", + MARK_READ_UNREAD_BUTTON: "mark_read_unread_button", +}; export const USER_TRACKER_EVENTS = { add_details: "user_details_added", onboarding_complete: "user_onboarding_completed", }; - -export const ONBOARDING_TRACKER_EVENTS = { - root: "onboarding", - step_1: "onboarding_step_1", - step_2: "onboarding_step_2", +export const ONBOARDING_TRACKER_ELEMENTS = { + PROFILE_SETUP_FORM: "onboarding_profile_setup_form", }; -export const SIDEBAR_TRACKER_EVENTS = { - click: "sidenav_clicked", +export const SIDEBAR_TRACKER_ELEMENTS = { + USER_MENU_ITEM: "sidenav_user_menu_item", + CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button", }; diff --git a/space/core/components/account/auth-forms/unique-code.tsx b/space/core/components/account/auth-forms/unique-code.tsx index 750b52ccda1..abf233a297d 100644 --- a/space/core/components/account/auth-forms/unique-code.tsx +++ b/space/core/components/account/auth-forms/unique-code.tsx @@ -34,8 +34,6 @@ const defaultValues: TUniqueCodeFormValues = { export const AuthUniqueCodeForm: React.FC = (props) => { const { mode, email, nextPath, handleEmailClear, generateEmailUniqueCode } = props; - // hooks - // const { captureEvent } = useEventTracker(); // derived values const defaultResetTimerValue = 5; // states diff --git a/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx b/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx index 6100bc8d506..829ebdec7f6 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,7 +12,8 @@ import AnalyticsFilterActions from "@/components/analytics/analytics-filter-acti import { PageHead } from "@/components/core"; import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; // hooks -import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useCommandPalette, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs"; @@ -36,7 +37,6 @@ const AnalyticsPage = observer((props: Props) => { // store hooks const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { workspaceProjectIds, loader } = useProject(); const { currentWorkspace } = useWorkspace(); const { allowPermissions } = useUserPermissions(); @@ -101,8 +101,8 @@ const AnalyticsPage = observer((props: Props) => { title={t("workspace_analytics.empty_state.general.primary_button.comic.title")} description={t("workspace_analytics.empty_state.general.primary_button.comic.description")} 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/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx index 7169a3d9191..242b5902e94 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx @@ -12,6 +12,7 @@ import { EUserPermissions, EUserPermissionsLevel, EProjectFeatureKey, + WORK_ITEM_TRACKER_ELEMENTS, } from "@plane/constants"; // types import { @@ -30,11 +31,9 @@ import { SwitcherIcon, SwitcherLabel } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // constants import { ViewQuickActions } from "@/components/views"; -// helpers // hooks import { useCommandPalette, - useEventTracker, useIssues, useLabel, useMember, @@ -57,7 +56,6 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { const { issuesFilter: { issueFilters, updateFilters }, } = useIssues(EIssuesStoreType.PROJECT_VIEW); - const { setTrackElement } = useEventTracker(); const { toggleCreateIssueModal } = useCommandPalette(); const { allowPermissions } = useUserPermissions(); @@ -258,9 +256,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { {canUserCreateIssue ? (
diff --git a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx index 001c00dd27e..e05eb30f057 100644 --- a/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx @@ -5,12 +5,17 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Search } from "lucide-react"; // types -import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_EVENTS } from "@plane/constants"; +import { + EUserPermissions, + EUserPermissionsLevel, + MEMBER_TRACKER_ELEMENTS, + MEMBER_TRACKER_EVENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWorkspaceBulkInviteFormData } from "@plane/types"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; -import { cn, getUserRole } from "@plane/utils"; +import { cn } from "@plane/utils"; // components import { NotAuthorizedView } from "@/components/auth-screens"; import { CountChip } from "@/components/common"; @@ -19,7 +24,8 @@ import { SettingsContentWrapper } from "@/components/settings"; import { WorkspaceMembersList } from "@/components/workspace"; // helpers // hooks -import { useEventTracker, useMember, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useMember, useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web components import { BillingActionsButton } from "@/plane-web/components/workspace/billing"; import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members"; @@ -32,7 +38,6 @@ const WorkspaceMembersSettingsPage = observer(() => { const { workspaceSlug } = useParams(); // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); - const { captureEvent } = useEventTracker(); const { workspace: { workspaceMemberIds, inviteMembersToWorkspace }, } = useMember(); @@ -52,16 +57,11 @@ const WorkspaceMembersSettingsPage = observer(() => { return inviteMembersToWorkspace(workspaceSlug.toString(), data) .then(() => { setInviteModal(false); - captureEvent(MEMBER_TRACKER_EVENTS.invite, { - emails: [ - ...data.emails.map((email) => ({ - email: email.email, - role: getUserRole(email.role as unknown as EUserPermissions), - })), - ], - project_id: undefined, - state: "SUCCESS", - element: "Workspace settings member page", + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + emails: [...data.emails.map((email) => email.email)], + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -70,16 +70,12 @@ const WorkspaceMembersSettingsPage = observer(() => { }); }) .catch((err) => { - captureEvent(MEMBER_TRACKER_EVENTS.invite, { - emails: [ - ...data.emails.map((email) => ({ - email: email.email, - role: getUserRole(email.role as unknown as EUserPermissions), - })), - ], - project_id: undefined, - state: "FAILED", - element: "Workspace settings member page", + captureError({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + emails: [...data.emails.map((email) => email.email)], + }, + error: err, }); setToast({ type: TOAST_TYPE.ERROR, @@ -129,7 +125,12 @@ const WorkspaceMembersSettingsPage = observer(() => { />
{canPerformWorkspaceAdminActions && ( - )} diff --git a/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx b/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx index 65ea6270152..f94ca21416a 100644 --- a/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx @@ -2,6 +2,7 @@ import Image from "next/image"; import Link from "next/link"; import { useTheme } from "next-themes"; +import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { Button, getButtonStyling } from "@plane/ui"; import { cn } from "@plane/utils"; import { useCommandPalette } from "@/hooks/store"; @@ -27,7 +28,11 @@ const ProjectSettingsPage = () => { Learn more about projects - 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)/invitations/page.tsx b/web/app/(all)/invitations/page.tsx index 5e5f1958fe0..c3ba0e1527d 100644 --- a/web/app/(all)/invitations/page.tsx +++ b/web/app/(all)/invitations/page.tsx @@ -9,20 +9,21 @@ 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, GROUP_WORKSPACE_TRACKER_EVENT } 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 { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess, joinEventGroup } from "@/helpers/event-tracker.helper"; +import { useUser, useUserProfile, useWorkspace } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // services import { AuthenticationWrapper } from "@/lib/wrappers"; @@ -43,7 +44,6 @@ const UserInvitationsPage = observer(() => { const router = useAppRouter(); // store hooks const { t } = useTranslation(); - const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker(); const { data: currentUser } = useUser(); const { updateUserProfile } = useUserProfile(); @@ -85,15 +85,17 @@ const UserInvitationsPage = observer(() => { const firstInviteId = invitationsRespond[0]; 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", + if (redirectWorkspace?.id) { + joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, redirectWorkspace?.id, { + date: new Date().toDateString(), + workspace_id: redirectWorkspace?.id, + }); + } + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.accept, + payload: { + member_id: invitation?.id, + }, }); updateUserProfile({ last_workspace_id: redirectWorkspace?.id }) .then(() => { @@ -111,12 +113,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 +197,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/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/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/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index fac54955bfa..7460967a58f 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -10,6 +10,7 @@ import { EUserPermissionsLevel, SPACE_BASE_PATH, SPACE_BASE_URL, + WORK_ITEM_TRACKER_ELEMENTS, EProjectFeatureKey, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; @@ -21,7 +22,7 @@ import { CountChip } from "@/components/common"; import HeaderFilters from "@/components/issues/filters"; // helpers // hooks -import { useEventTracker, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store"; +import { useProject, useCommandPalette, useUserPermissions } from "@/hooks/store"; import { useIssues } from "@/hooks/store/use-issues"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -42,7 +43,6 @@ export const IssuesHeader = observer(() => { const { currentProjectDetails, loader } = useProject(); const { toggleCreateIssueModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const { isMobile } = usePlatformOS(); @@ -104,9 +104,9 @@ export const IssuesHeader = observer(() => { {canUserCreateIssue ? ( diff --git a/web/ce/helpers/command-palette.ts b/web/ce/helpers/command-palette.ts index 28576735f72..1cb04b623dd 100644 --- a/web/ce/helpers/command-palette.ts +++ b/web/ce/helpers/command-palette.ts @@ -1,6 +1,14 @@ // types +import { + CYCLE_TRACKER_ELEMENTS, + MODULE_TRACKER_ELEMENTS, + PROJECT_PAGE_TRACKER_ELEMENTS, + PROJECT_TRACKER_ELEMENTS, + WORK_ITEM_TRACKER_ELEMENTS, +} from "@plane/constants"; import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types"; // store +import { captureClick } from "@/helpers/event-tracker.helper"; import { store } from "@/lib/store-context"; export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => { @@ -10,7 +18,10 @@ export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => { c: { title: "Create a new work item", description: "Create a new work item in the current project", - action: () => toggleCreateIssueModal(true), + action: () => { + toggleCreateIssueModal(true); + captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON }); + }, }, }; }; @@ -22,7 +33,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 }); + }, }, }; }; @@ -40,17 +54,26 @@ export const getProjectShortcutsList: () => TCommandPaletteActionList = () => { d: { title: "Create a new page", description: "Create a new page in the current project", - action: () => toggleCreatePageModal({ isOpen: true }), + action: () => { + toggleCreatePageModal({ isOpen: true }); + captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON }); + }, }, m: { title: "Create a new module", description: "Create a new module in the current project", - action: () => toggleCreateModuleModal(true), + action: () => { + toggleCreateModuleModal(true); + captureClick({ elementName: MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM }); + }, }, q: { title: "Create a new cycle", description: "Create a new cycle in the current project", - action: () => toggleCreateCycleModal(true), + action: () => { + toggleCreateCycleModal(true); + captureClick({ elementName: CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM }); + }, }, v: { title: "Create a new view", 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/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 && (
-
)} - + {MENU_ITEMS.map((item) => { if (item.shouldRender === false) return null; @@ -176,6 +198,9 @@ export const ModuleQuickActions: React.FC = observer((props) => { onClick={(e) => { e.preventDefault(); e.stopPropagation(); + captureClick({ + elementName: MODULE_TRACKER_ELEMENTS.QUICK_ACTIONS, + }); item.action(); }} className={cn( diff --git a/web/core/components/onboarding/create-workspace.tsx b/web/core/components/onboarding/create-workspace.tsx index 157beec74a9..e37e3c311d9 100644 --- a/web/core/components/onboarding/create-workspace.tsx +++ b/web/core/components/onboarding/create-workspace.tsx @@ -5,10 +5,10 @@ import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; // constants import { - ONBOARDING_TRACKER_EVENTS, ORGANIZATION_SIZE, RESTRICTED_URLS, WORKSPACE_TRACKER_EVENTS, + WORKSPACE_TRACKER_ELEMENTS, } from "@plane/constants"; // types import { useTranslation } from "@plane/i18n"; @@ -16,7 +16,8 @@ import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types"; // ui import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // hooks -import { useEventTracker, useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store"; // services import { WorkspaceService } from "@/plane-web/services"; @@ -41,7 +42,6 @@ export const CreateWorkspace: React.FC = observer((props) => { const { updateUserProfile } = useUserProfile(); const { fetchCurrentUserSettings } = useUserSettings(); const { createWorkspace, fetchWorkspaces } = useWorkspace(); - const { captureWorkspaceEvent } = useEventTracker(); // form info const { handleSubmit, @@ -73,26 +73,18 @@ export const CreateWorkspace: React.FC = observer((props) => { title: t("workspace_creation.toast.success.title"), message: t("workspace_creation.toast.success.message"), }); - captureWorkspaceEvent({ + captureSuccess({ eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - ...workspaceResponse, - state: "SUCCESS", - first_time: true, - element: ONBOARDING_TRACKER_EVENTS.root, - }, + payload: { slug: formData.slug }, }); await fetchWorkspaces(); await completeStep(workspaceResponse.id); }) .catch(() => { - captureWorkspaceEvent({ + captureError({ eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - state: "FAILED", - first_time: true, - element: ONBOARDING_TRACKER_EVENTS.root, - }, + payload: { slug: formData.slug }, + error: new Error("Error creating workspace"), }); setToast({ type: TOAST_TYPE.ERROR, @@ -290,7 +282,14 @@ export const CreateWorkspace: React.FC = observer((props) => { )} - 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/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) => { diff --git a/web/core/components/project/card-list.tsx b/web/core/components/project/card-list.tsx index 606f533f195..d2c1f7dad89 100644 --- a/web/core/components/project/card-list.tsx +++ b/web/core/components/project/card-list.tsx @@ -1,15 +1,16 @@ import { observer } from "mobx-react"; import Image from "next/image"; // plane imports -import { EUserPermissionsLevel, EUserPermissions } from "@plane/constants"; +import { EUserPermissionsLevel, EUserPermissions, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { ContentWrapper } from "@plane/ui"; // components import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; import { ProjectCard } from "@/components/project"; import { ProjectsLoader } from "@/components/ui"; +import { captureClick } from "@/helpers/event-tracker.helper"; // hooks -import { useCommandPalette, useEventTracker, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store"; +import { useCommandPalette, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; // assets import AllFiltersImage from "@/public/empty-state/project/all-filters.svg"; @@ -26,7 +27,6 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => { const { t } = useTranslation(); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { loader, fetchStatus, @@ -65,8 +65,8 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => { title={t("workspace_projects.empty_state.general.primary_button.comic.title")} description={t("workspace_projects.empty_state.general.primary_button.comic.description")} onClick={() => { - setTrackElement("Project empty state"); toggleCreateProjectModal(true); + captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON }); }} disabled={!canPerformEmptyStateActions} /> diff --git a/web/core/components/project/delete-project-modal.tsx b/web/core/components/project/delete-project-modal.tsx index d5888161e82..7c48fb30ad1 100644 --- a/web/core/components/project/delete-project-modal.tsx +++ b/web/core/components/project/delete-project-modal.tsx @@ -12,7 +12,8 @@ import type { IProject } from "@plane/types"; import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // constants // hooks -import { useEventTracker, useProject } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useProject } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; type DeleteProjectModal = { @@ -29,7 +30,6 @@ const defaultValues = { export const DeleteProjectModal: React.FC = (props) => { const { isOpen, project, onClose } = props; // store hooks - const { captureProjectEvent } = useEventTracker(); const { deleteProject } = useProject(); // router const router = useAppRouter(); @@ -62,9 +62,11 @@ export const DeleteProjectModal: React.FC = (props) => { if (projectId && projectId.toString() === project.id) router.push(`/${workspaceSlug}/projects`); handleClose(); - captureProjectEvent({ + captureSuccess({ eventName: PROJECT_TRACKER_EVENTS.delete, - payload: { ...project, state: "SUCCESS", element: "Project general settings" }, + payload: { + id: project.id, + }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -73,9 +75,11 @@ export const DeleteProjectModal: React.FC = (props) => { }); }) .catch(() => { - captureProjectEvent({ + captureError({ eventName: PROJECT_TRACKER_EVENTS.delete, - payload: { ...project, state: "FAILED", element: "Project general settings" }, + payload: { + id: project.id, + }, }); setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/core/components/project/form.tsx b/web/core/components/project/form.tsx index 27c8ec840f7..26c96596f27 100644 --- a/web/core/components/project/form.tsx +++ b/web/core/components/project/form.tsx @@ -3,7 +3,7 @@ import { FC, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { Info, Lock } from "lucide-react"; -import { NETWORK_CHOICES, PROJECT_TRACKER_EVENTS } from "@plane/constants"; +import { NETWORK_CHOICES, PROJECT_TRACKER_ELEMENTS, PROJECT_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // plane types import { IProject, IWorkspace } from "@plane/types"; @@ -27,7 +27,8 @@ import { TimezoneSelect } from "@/components/global"; import { ProjectNetworkIcon } from "@/components/project"; // helpers // 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"; // services import { ProjectService } from "@/services/project"; @@ -46,7 +47,6 @@ export const ProjectDetailsForm: FC = (props) => { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); // store hooks - const { captureProjectEvent } = useEventTracker(); const { updateProject } = useProject(); const { isMobile } = usePlatformOS(); @@ -58,7 +58,7 @@ export const ProjectDetailsForm: FC = (props) => { setValue, setError, reset, - formState: { errors, dirtyFields }, + formState: { errors }, getValues, } = useForm({ defaultValues: { @@ -92,15 +92,10 @@ export const ProjectDetailsForm: FC = (props) => { if (!workspaceSlug || !project) return; return updateProject(workspaceSlug.toString(), project.id, payload) .then((res) => { - const changed_properties = Object.keys(dirtyFields); - - captureProjectEvent({ + captureSuccess({ eventName: PROJECT_TRACKER_EVENTS.update, payload: { - ...res, - changed_properties: changed_properties, - state: "SUCCESS", - element: "Project general settings", + id: projectId, }, }); setToast({ @@ -110,9 +105,11 @@ export const ProjectDetailsForm: FC = (props) => { }); }) .catch((error) => { - captureProjectEvent({ + captureError({ eventName: PROJECT_TRACKER_EVENTS.update, - payload: { ...payload, state: "FAILED", element: "Project general settings" }, + payload: { + id: projectId, + }, }); setToast({ type: TOAST_TYPE.ERROR, @@ -397,7 +394,13 @@ export const ProjectDetailsForm: FC = (props) => {
<> - 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/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/delete-project-section.tsx b/web/core/components/project/settings/delete-project-section.tsx index d425af53e42..540a77f0965 100644 --- a/web/core/components/project/settings/delete-project-section.tsx +++ b/web/core/components/project/settings/delete-project-section.tsx @@ -4,6 +4,7 @@ import React from "react"; import { ChevronRight, ChevronUp } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // types +import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { IProject } from "@plane/types"; // ui import { Button, Loader } from "@plane/ui"; @@ -43,7 +44,11 @@ export const DeleteProjectSection: React.FC = (props) =>
{projectDetails ? (
-
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} />
diff --git a/web/core/components/project/settings/member-columns.tsx b/web/core/components/project/settings/member-columns.tsx index ee0f7c61f79..74315bbd5f4 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 } from "@plane/constants"; +import { ROLE, EUserPermissions, MEMBER_TRACKER_ELEMENTS } from "@plane/constants"; import { EUserProjectRoles, 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/views/update-view-component.tsx b/web/core/components/views/update-view-component.tsx index 5b3f8c79bbf..0e7990b50e0 100644 --- a/web/core/components/views/update-view-component.tsx +++ b/web/core/components/views/update-view-component.tsx @@ -1,4 +1,5 @@ import { SetStateAction, useEffect, useState } from "react"; +import { GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants"; import { Button } from "@plane/ui"; import { LockedComponent } from "../icons/locked-component"; @@ -58,7 +59,13 @@ export const UpdateViewComponent = (props: Props) => { !areFiltersEqual && isAuthorizedUser && ( <> - {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..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 @@ -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 { captureError, 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(); @@ -31,10 +31,12 @@ export const NotificationItemArchiveOption: FC = try { const request = data.archived_at ? unArchiveNotification : archiveNotification; await request(workspaceSlug); - captureEvent(NOTIFICATION_TRACKER_EVENTS.archive, { - issue_id: data?.data?.issue?.id, - tab: currentNotificationTab, - state: "SUCCESS", + captureSuccess({ + eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : 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"), @@ -42,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 ( = observer((props) => { const { workspaceSlug, notification } = props; // hooks - const { captureEvent } = useEventTracker(); const { currentNotificationTab } = useWorkspaceNotifications(); const { asJson: data, markNotificationAsRead, markNotificationAsUnRead } = notification; const { t } = useTranslation(); @@ -31,10 +31,12 @@ export const NotificationItemReadOption: FC = obser try { const request = data.read_at ? markNotificationAsUnRead : markNotificationAsRead; await request(workspaceSlug); - captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read, { - issue_id: data?.data?.issue?.id, - tab: currentNotificationTab, - state: "SUCCESS", + captureSuccess({ + eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read, + payload: { + id: data?.data?.issue?.id, + tab: currentNotificationTab, + }, }); setToast({ title: data.read_at ? t("notification.toasts.unread") : t("notification.toasts.read"), @@ -42,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 ( diff --git a/web/core/components/workspace/create-workspace-form.tsx b/web/core/components/workspace/create-workspace-form.tsx index 0f51e74b26f..d7d54c66148 100644 --- a/web/core/components/workspace/create-workspace-form.tsx +++ b/web/core/components/workspace/create-workspace-form.tsx @@ -3,7 +3,12 @@ import { Dispatch, SetStateAction, useEffect, useState, FC } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; -import { ORGANIZATION_SIZE, RESTRICTED_URLS, WORKSPACE_TRACKER_EVENTS } from "@plane/constants"; +import { + ORGANIZATION_SIZE, + RESTRICTED_URLS, + WORKSPACE_TRACKER_ELEMENTS, + WORKSPACE_TRACKER_EVENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // constants // types @@ -11,7 +16,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 { captureError, captureSuccess } 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 +57,6 @@ export const CreateWorkspaceForm: FC = observer((props) => { // router const router = useAppRouter(); // store hooks - const { captureWorkspaceEvent } = useEventTracker(); const { createWorkspace } = useWorkspace(); // form info const { @@ -71,13 +76,9 @@ export const CreateWorkspaceForm: FC = observer((props) => { await createWorkspace(formData) .then(async (res) => { - captureWorkspaceEvent({ + captureSuccess({ eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - ...res, - state: "SUCCESS", - element: "Create workspace page", - }, + payload: { slug: formData.slug }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -88,12 +89,10 @@ export const CreateWorkspaceForm: FC = observer((props) => { if (onSubmit) await onSubmit(res); }) .catch(() => { - captureWorkspaceEvent({ + captureError({ eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { - state: "FAILED", - element: "Create workspace page", - }, + payload: { slug: formData.slug }, + error: new Error("Error creating workspace"), }); setToast({ type: TOAST_TYPE.ERROR, @@ -248,7 +247,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..14cffe5459d 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 { captureError, captureSuccess } 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,13 +64,9 @@ export const DeleteWorkspaceForm: React.FC = observer((props) => { await fetchCurrentUserSettings(); handleClose(); router.push(getWorkspaceRedirectionUrl()); - captureWorkspaceEvent({ + captureSuccess({ eventName: WORKSPACE_TRACKER_EVENTS.delete, - payload: { - ...data, - state: "SUCCESS", - element: "Workspace general settings page", - }, + payload: { slug: data.slug }, }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -84,13 +80,10 @@ 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({ + captureError({ eventName: WORKSPACE_TRACKER_EVENTS.delete, - payload: { - ...data, - state: "FAILED", - element: "Workspace general settings page", - }, + payload: { slug: data.slug }, + error: new Error("Error deleting workspace"), }); }); }; 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/settings/workspace-details.tsx b/web/core/components/workspace/settings/workspace-details.tsx index e9a83784b58..577f5acd0b1 100644 --- a/web/core/components/workspace/settings/workspace-details.tsx +++ b/web/core/components/workspace/settings/workspace-details.tsx @@ -5,7 +5,13 @@ import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { Pencil } from "lucide-react"; // constants -import { ORGANIZATION_SIZE, EUserPermissions, EUserPermissionsLevel, WORKSPACE_TRACKER_EVENTS } from "@plane/constants"; +import { + ORGANIZATION_SIZE, + EUserPermissions, + EUserPermissionsLevel, + WORKSPACE_TRACKER_EVENTS, + WORKSPACE_TRACKER_ELEMENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWorkspace } from "@plane/types"; import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui"; @@ -15,7 +21,8 @@ import { LogoSpinner } from "@/components/common"; import { WorkspaceImageUploadModal } from "@/components/core"; // helpers // hooks -import { useEventTracker, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; +import { useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web components import { DeleteWorkspaceSection } from "@/plane-web/components/workspace"; @@ -31,7 +38,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,13 +67,9 @@ export const WorkspaceDetails: FC = observer(() => { await updateWorkspace(currentWorkspace.slug, payload) .then((res) => { - captureWorkspaceEvent({ + captureSuccess({ eventName: WORKSPACE_TRACKER_EVENTS.update, - payload: { - ...res, - state: "SUCCESS", - element: "Workspace general settings page", - }, + payload: { slug: currentWorkspace.slug }, }); setToast({ title: "Success!", @@ -76,12 +78,10 @@ export const WorkspaceDetails: FC = observer(() => { }); }) .catch((err) => { - captureWorkspaceEvent({ + captureError({ eventName: WORKSPACE_TRACKER_EVENTS.update, - payload: { - state: "FAILED", - element: "Workspace general settings page", - }, + payload: { slug: currentWorkspace.slug }, + error: err, }); console.error(err); }); @@ -282,7 +282,12 @@ export const WorkspaceDetails: FC = observer(() => { {isAdmin && (
-
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")} 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(() => {