From c6fbbfd8ccbfc9aeda5ba732473344cc269d2209 Mon Sep 17 00:00:00 2001 From: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:54:06 +0530 Subject: [PATCH] [WEB-4405] chore: workspace settings events (#7312) * feat: event tracker helper * feat: track click events for `data-ph-element` * fix: handled click events * fix: handled name * chore: tracker element updates * chore: remove export * chore: tracker element type * chore: track element and event helper. * chore: minor improvements * chore: minor refactors * fix: workspace events * fix: added slug * fix: changes nomenclature * fix: nomenclature * chore: update event tracker helper types * fix: data id * 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 * Refactor module events (#7291) * chore: update event tracker helper types * refactor: cycle events * refactor: cycle events * refactor: cycle event tracker * refactor: module tracker event and element * chore: update tracker element * chore: revert unnecessary changes --------- Co-authored-by: Prateek Shourya * refactor: global views, product tour, notifications, onboarding, users and sidebar related events * chore: member tracker events (#7302) * chore: member-tracker-events * fix: constants * refactor: update event tracker constants * refactor: auth related event trackers (#7306) * Chore: state events (#7307) * chore: state events * fix: refactor * chore: project events (#7305) * chore: project-events * fix: refactor * fix: removed hardcoded values * fix: github redirection event * chore: project page tracker events (#7304) * added events for most page events * refactor: simplify lock button event handling in PageLockControl --------- Co-authored-by: Palanikannan M Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> * chore: minor cleanup and import fixes * refactor: added tracker elements for buttons (#7308) Co-authored-by: Prateek Shourya * fix: event type * refactor: posthog group event * chore: removed instances of event tracker (#7309) * refactor: remove event tracker stores and hooks * refactor: remove event tracker store * fix: build errors * clean up event tracker payloads * chore: workspace-settings-events * fix: refactor --------- Co-authored-by: Prateek Shourya Co-authored-by: Prateek Shourya Co-authored-by: Palanikannan M Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: Vamsi Krishna <46787868+vamsikrishnamathala@users.noreply.github.com> --- .../(workspace)/webhooks/[webhookId]/page.tsx | 16 ++++++++- .../settings/(workspace)/webhooks/page.tsx | 17 ++++++++-- .../billing/comparison/plan-detail.tsx | 19 ++++++++++- .../core/components/exporter/export-form.tsx | 31 +++++++++++++++-- .../web-hooks/create-webhook-modal.tsx | 15 +++++++++ .../web-hooks/delete-webhook-modal.tsx | 21 ++++++++++-- .../web-hooks/form/delete-section.tsx | 7 +++- .../core/components/web-hooks/form/form.tsx | 7 +++- .../core/components/web-hooks/form/toggle.tsx | 8 ++++- .../web-hooks/webhooks-list-item.tsx | 33 +++++++++++++++++-- packages/constants/src/event-tracker/core.ts | 30 +++++++++++++++++ 11 files changed, 188 insertions(+), 16 deletions(-) diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx index a775ff3b1f..c6a7d3f5ac 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -14,6 +14,7 @@ import { PageHead } from "@/components/core"; import { SettingsContentWrapper } from "@/components/settings"; import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "@/components/web-hooks"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; const WebhookDetailsPage = observer(() => { @@ -55,6 +56,12 @@ const WebhookDetailsPage = observer(() => { }; await updateWebhook(workspaceSlug.toString(), formData.id, payload) .then(() => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { + webhook: formData.id, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -62,6 +69,13 @@ const WebhookDetailsPage = observer(() => { }); }) .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { + webhook: formData.id, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx index 19f18717e7..511ab96d9a 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // plane imports -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // components import { NotAuthorizedView } from "@/components/auth-screens"; @@ -15,6 +15,7 @@ import { SettingsContentWrapper, SettingsHeading } from "@/components/settings"; import { WebhookSettingsLoader } from "@/components/ui"; import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks"; // hooks +import { captureClick } from "@/helpers/event-tracker.helper"; import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; @@ -71,7 +72,12 @@ const WebhooksListPage = observer(() => { description={t("workspace_settings.settings.webhooks.description")} button={{ label: t("workspace_settings.settings.webhooks.add_webhook"), - onClick: () => setShowCreateWebhookModal(true), + onClick: () => { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.HEADER_ADD_WEBHOOK_BUTTON, + }); + setShowCreateWebhookModal(true); + }, }} /> {Object.keys(webhooks).length > 0 ? ( @@ -89,7 +95,12 @@ const WebhooksListPage = observer(() => { size="md" primaryButton={{ text: t("workspace_settings.settings.webhooks.add_webhook"), - onClick: () => setShowCreateWebhookModal(true), + onClick: () => { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_WEBHOOK_BUTTON, + }); + setShowCreateWebhookModal(true); + }, }} /> diff --git a/apps/web/ce/components/workspace/billing/comparison/plan-detail.tsx b/apps/web/ce/components/workspace/billing/comparison/plan-detail.tsx index 38715972b4..2eeb134d52 100644 --- a/apps/web/ce/components/workspace/billing/comparison/plan-detail.tsx +++ b/apps/web/ce/components/workspace/billing/comparison/plan-detail.tsx @@ -5,6 +5,8 @@ import { SUBSCRIPTION_REDIRECTION_URLS, SUBSCRIPTION_WITH_BILLING_FREQUENCY, TALK_TO_SALES_URL, + WORKSPACE_SETTINGS_TRACKER_ELEMENTS, + WORKSPACE_SETTINGS_TRACKER_EVENTS, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EProductSubscriptionEnum, TBillingFrequency } from "@plane/types"; @@ -16,6 +18,7 @@ import { DiscountInfo } from "@/components/license/modal/card/discount-info"; import { getUpgradeButtonStyle } from "@/components/workspace/billing/subscription"; import { TPlanDetail } from "@/constants/plans"; // local imports +import { captureSuccess } from "@/helpers/event-tracker.helper"; import { PlanFrequencyToggle } from "./frequency-toggle"; type TPlanDetailProps = { @@ -49,6 +52,12 @@ export const PlanDetail: FC = observer((props) => { const frequency = billingFrequency ?? "year"; // Get the redirection URL based on the subscription type and billing frequency const redirectUrl = SUBSCRIPTION_REDIRECTION_URLS[subscriptionType][frequency] ?? TALK_TO_SALES_URL; + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.upgrade_plan_redirected, + payload: { + subscriptionType, + }, + }); // Open the URL in a new tab window.open(redirectUrl, "_blank"); }; @@ -101,7 +110,15 @@ export const PlanDetail: FC = observer((props) => { {/* Subscription button */}
-
diff --git a/apps/web/core/components/exporter/export-form.tsx b/apps/web/core/components/exporter/export-form.tsx index b108d2e4c5..0b097aeb66 100644 --- a/apps/web/core/components/exporter/export-form.tsx +++ b/apps/web/core/components/exporter/export-form.tsx @@ -1,9 +1,16 @@ import { useState } from "react"; import { intersection } from "lodash"; import { Controller, useForm } from "react-hook-form"; -import { EUserPermissions, EUserPermissionsLevel, EXPORTERS_LIST } from "@plane/constants"; +import { + EUserPermissions, + EUserPermissionsLevel, + EXPORTERS_LIST, + WORKSPACE_SETTINGS_TRACKER_EVENTS, + WORKSPACE_SETTINGS_TRACKER_ELEMENTS, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Button, CustomSearchSelect, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useProject, useUser, useUserPermissions } from "@/hooks/store"; import { ProjectExportService } from "@/services/project/project-export.service"; @@ -73,6 +80,12 @@ export const ExportForm = (props: Props) => { .then(() => { mutateServices(); setExportLoading(false); + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.csv_exported, + payload: { + provider: formData.provider.provider, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.exports.modal.toasts.success.title"), @@ -88,8 +101,15 @@ export const ExportForm = (props: Props) => { }), }); }) - .catch(() => { + .catch((error) => { setExportLoading(false); + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.csv_exported, + payload: { + provider: formData.provider.provider, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: t("error"), @@ -163,7 +183,12 @@ export const ExportForm = (props: Props) => {
-
diff --git a/apps/web/core/components/web-hooks/create-webhook-modal.tsx b/apps/web/core/components/web-hooks/create-webhook-modal.tsx index 838916a693..a7cb43ab74 100644 --- a/apps/web/core/components/web-hooks/create-webhook-modal.tsx +++ b/apps/web/core/components/web-hooks/create-webhook-modal.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { useParams } from "next/navigation"; // types +import { WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWebhook, IWorkspace, TWebhookEventTypes } from "@plane/types"; // ui @@ -10,6 +11,7 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p // helpers import { csvDownload } from "@plane/utils"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import useKeypress from "@/hooks/use-keypress"; // components import { WebhookForm } from "./form"; @@ -67,6 +69,12 @@ export const CreateWebhookModal: React.FC = (props) => { await createWebhook(workspaceSlug.toString(), payload) .then(({ webHook, secretKey }) => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_created, + payload: { + webhook: formData?.url, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: t("workspace_settings.settings.webhooks.toasts.created.title"), @@ -79,6 +87,13 @@ export const CreateWebhookModal: React.FC = (props) => { csvDownload(csvData, `webhook-secret-key-${Date.now()}`); }) .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_created, + payload: { + webhook: formData?.url, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: t("workspace_settings.settings.webhooks.toasts.not_created.title"), diff --git a/apps/web/core/components/web-hooks/delete-webhook-modal.tsx b/apps/web/core/components/web-hooks/delete-webhook-modal.tsx index 615c50f680..6e32d9f66a 100644 --- a/apps/web/core/components/web-hooks/delete-webhook-modal.tsx +++ b/apps/web/core/components/web-hooks/delete-webhook-modal.tsx @@ -3,8 +3,10 @@ import React, { FC, useState } from "react"; import { useParams } from "next/navigation"; // ui +import { WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // hooks +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -35,6 +37,12 @@ export const DeleteWebhookModal: FC = (props) => { removeWebhook(workspaceSlug.toString(), webhookId.toString()) .then(() => { + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_deleted, + payload: { + webhook: webhookId, + }, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -42,13 +50,20 @@ export const DeleteWebhookModal: FC = (props) => { }); router.replace(`/${workspaceSlug}/settings/webhooks/`); }) - .catch((error) => + .catch((error) => { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_deleted, + payload: { + webhook: webhookId, + }, + error: error as Error, + }); setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: error?.error ?? "Something went wrong. Please try again.", - }) - ) + }); + }) .finally(() => setIsDeleting(false)); }; diff --git a/apps/web/core/components/web-hooks/form/delete-section.tsx b/apps/web/core/components/web-hooks/form/delete-section.tsx index 1029d792f6..8201804a13 100644 --- a/apps/web/core/components/web-hooks/form/delete-section.tsx +++ b/apps/web/core/components/web-hooks/form/delete-section.tsx @@ -2,6 +2,7 @@ import { ChevronDown, ChevronUp } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { Button } from "@plane/ui"; type Props = { @@ -36,7 +37,11 @@ export const WebhookDeleteSection: React.FC = (props) => { webhook.
-
diff --git a/apps/web/core/components/web-hooks/form/form.tsx b/apps/web/core/components/web-hooks/form/form.tsx index 5ecd7ff8e1..804b708e8c 100644 --- a/apps/web/core/components/web-hooks/form/form.tsx +++ b/apps/web/core/components/web-hooks/form/form.tsx @@ -3,6 +3,7 @@ import React, { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWebhook, TWebhookEventTypes } from "@plane/types"; // hooks @@ -93,7 +94,11 @@ export const WebhookForm: FC = observer((props) => { {data ? (
-
diff --git a/apps/web/core/components/web-hooks/form/toggle.tsx b/apps/web/core/components/web-hooks/form/toggle.tsx index ca240435b5..76dfca3ed9 100644 --- a/apps/web/core/components/web-hooks/form/toggle.tsx +++ b/apps/web/core/components/web-hooks/form/toggle.tsx @@ -1,10 +1,13 @@ "use client"; import { Control, Controller } from "react-hook-form"; +// constants +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // ui import { ToggleSwitch } from "@plane/ui"; -// types +// hooks +import { captureClick } from "@/helpers/event-tracker.helper"; interface IWebHookToggle { control: Control; @@ -20,6 +23,9 @@ export const WebhookToggle = ({ control }: IWebHookToggle) => ( { + captureClick({ + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_DETAILS_PAGE_TOGGLE_SWITCH, + }); onChange(val); }} size="sm" diff --git a/apps/web/core/components/web-hooks/webhooks-list-item.tsx b/apps/web/core/components/web-hooks/webhooks-list-item.tsx index 0c5d0ea8ea..5419207d89 100644 --- a/apps/web/core/components/web-hooks/webhooks-list-item.tsx +++ b/apps/web/core/components/web-hooks/webhooks-list-item.tsx @@ -3,9 +3,11 @@ import { FC } from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; +import { WORKSPACE_SETTINGS_TRACKER_ELEMENTS, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { IWebhook } from "@plane/types"; // hooks import { ToggleSwitch } from "@plane/ui"; +import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store"; // ui // types @@ -23,8 +25,35 @@ export const WebhooksListItem: FC = (props) => { const handleToggle = () => { if (!workspaceSlug || !webhook.id) return; - - updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }); + updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active }) + .then(() => { + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, + }, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "SUCCESS", + payload: { + webhook: webhook.url, + }, + }, + }); + }) + .catch(() => { + captureElementAndEvent({ + element: { + elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_LIST_ITEM_TOGGLE_SWITCH, + }, + event: { + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_toggled, + state: "ERROR", + payload: { + webhook: webhook.url, + }, + }, + }); + }); }; return ( diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index dd40d34593..83650319f2 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -1,3 +1,5 @@ +import { EProductSubscriptionEnum } from "@plane/types"; + // Dashboard Events export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; export const HEADER_GITHUB_ICON = "header_github_icon"; @@ -268,3 +270,31 @@ export const SIDEBAR_TRACKER_ELEMENTS = { USER_MENU_ITEM: "sidenav_user_menu_item", CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button", }; + +export const WORKSPACE_SETTINGS_TRACKER_EVENTS = { + // Billing + upgrade_plan_redirected: "upgrade_plan_redirected", + // Exports + csv_exported: "csv_exported", + // Webhooks + webhook_created: "webhook_created", + webhook_deleted: "webhook_deleted", + webhook_toggled: "webhook_toggled", + webhook_details_page_toggled: "webhook_details_page_toggled", + webhook_updated: "webhook_updated", +}; +export const WORKSPACE_SETTINGS_TRACKER_ELEMENTS = { + // Billing + BILLING_UPGRADE_BUTTON: (subscriptionType: EProductSubscriptionEnum) => `billing_upgrade_${subscriptionType}_button`, + BILLING_TALK_TO_SALES_BUTTON: "billing_talk_to_sales_button", + // Exports + EXPORT_BUTTON: "export_button", + // Webhooks + HEADER_ADD_WEBHOOK_BUTTON: "header_add_webhook_button", + EMPTY_STATE_ADD_WEBHOOK_BUTTON: "empty_state_add_webhook_button", + LIST_ITEM_DELETE_BUTTON: "list_item_delete_button", + WEBHOOK_LIST_ITEM_TOGGLE_SWITCH: "webhook_list_item_toggle_switch", + WEBHOOK_DETAILS_PAGE_TOGGLE_SWITCH: "webhook_details_page_toggle_switch", + WEBHOOK_DELETE_BUTTON: "webhook_delete_button", + WEBHOOK_UPDATE_BUTTON: "webhook_update_button", +};