diff --git a/web/components/pages/dropdowns/quick-actions.tsx b/web/components/pages/dropdowns/quick-actions.tsx index 9c311797a..29626e0c4 100644 --- a/web/components/pages/dropdowns/quick-actions.tsx +++ b/web/components/pages/dropdowns/quick-actions.tsx @@ -4,10 +4,12 @@ import { ArchiveRestoreIcon, ExternalLink, Link, Lock, Trash2, UsersRound } from import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; // components import { DeletePageModal } from "@/components/pages"; +// constants +import { E_PAGES, PAGE_ARCHIVED, PAGE_RESTORED, PAGE_UPDATED } from "@/constants/event-tracker"; // helpers import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks -import { usePage } from "@/hooks/store"; +import { useEventTracker, usePage } from "@/hooks/store"; type Props = { pageId: string; @@ -32,6 +34,7 @@ export const PageQuickActions: React.FC = observer((props) => { canCurrentUserChangeAccess, canCurrentUserDeletePage, } = usePage(pageId); + const { setTrackElement, captureEvent } = useEventTracker(); const pageLink = `${workspaceSlug}/projects/${projectId}/pages/${pageId}`; const handleCopyText = () => @@ -48,7 +51,17 @@ export const PageQuickActions: React.FC = observer((props) => { const MENU_ITEMS: TContextMenuItem[] = [ { key: "make-public-private", - action: access === 0 ? makePrivate : makePublic, + action: () => { + { + access === 0 ? makePrivate() : makePublic(); + captureEvent(PAGE_UPDATED, { + page_id: pageId, + changed_property: "access", + change_details: access === 0 ? "private" : "public", + element: E_PAGES, + }); + } + }, title: access === 0 ? "Make private" : "Make public", icon: access === 0 ? Lock : UsersRound, shouldRender: canCurrentUserChangeAccess && !archived_at, @@ -69,14 +82,23 @@ export const PageQuickActions: React.FC = observer((props) => { }, { key: "archive-restore", - action: archived_at ? restore : archive, + action: () => { + archived_at ? restore() : archive(); + captureEvent(archived_at ? PAGE_RESTORED : PAGE_ARCHIVED, { + page_id: pageId, + element: E_PAGES, + }); + }, title: archived_at ? "Restore" : "Archive", icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon, shouldRender: canCurrentUserArchivePage, }, { key: "delete", - action: () => setDeletePageModal(true), + action: () => { + setTrackElement(E_PAGES); + setDeletePageModal(true); + }, title: "Delete", icon: Trash2, shouldRender: canCurrentUserDeletePage && !!archived_at, diff --git a/web/components/pages/header/root.tsx b/web/components/pages/header/root.tsx index 4f902415c..52f1d8385 100644 --- a/web/components/pages/header/root.tsx +++ b/web/components/pages/header/root.tsx @@ -11,10 +11,12 @@ import { PageSearchInput, PageTabNavigation, } from "@/components/pages"; +// constants +import { PAGES_SORT_UPDATED } from "@/constants/event-tracker"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useMember, useProjectPages } from "@/hooks/store"; +import { useEventTracker, useMember, useProjectPages } from "@/hooks/store"; type Props = { pageType: TPageNavigationTabs; @@ -29,6 +31,7 @@ export const PagesListHeaderRoot: React.FC = observer((props) => { const { workspace: { workspaceMemberIds }, } = useMember(); + const { captureEvent } = useEventTracker(); const handleRemoveFilter = useCallback( (key: keyof TPageFilterProps, value: string | null) => { @@ -57,6 +60,14 @@ export const PagesListHeaderRoot: React.FC = observer((props) => { onChange={(val) => { if (val.key) updateFilters("sortKey", val.key); if (val.order) updateFilters("sortBy", val.order); + captureEvent(PAGES_SORT_UPDATED, { + changed_property: val.order ? "sort_by" : "order_by", + change_details: val.order || val.key, + current_sort: { + order_by: filters.sortKey, + sort_by: filters.sortBy, + }, + }); }} /> } title="Filters" placement="bottom-end"> diff --git a/web/components/pages/list/block-item-action.tsx b/web/components/pages/list/block-item-action.tsx index f9fb57f0f..4bcf77f38 100644 --- a/web/components/pages/list/block-item-action.tsx +++ b/web/components/pages/list/block-item-action.tsx @@ -6,10 +6,12 @@ import { Avatar, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components import { FavoriteStar } from "@/components/core"; import { PageQuickActions } from "@/components/pages/dropdowns"; +// constants +import { E_PAGES, PAGE_FAVORITED, PAGE_UNFAVORITED } from "@/constants/event-tracker"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; // hooks -import { useMember, usePage } from "@/hooks/store"; +import { useEventTracker, useMember, usePage } from "@/hooks/store"; type Props = { workspaceSlug: string; @@ -24,6 +26,7 @@ export const BlockItemAction: FC = observer((props) => { // store hooks const { access, created_at, is_favorite, owned_by, addToFavorites, removeFromFavorites } = usePage(pageId); const { getUserDetails } = useMember(); + const { captureEvent } = useEventTracker(); // derived values const ownerDetails = owned_by ? getUserDetails(owned_by) : undefined; @@ -31,21 +34,29 @@ export const BlockItemAction: FC = observer((props) => { // handlers const handleFavorites = () => { if (is_favorite) - removeFromFavorites().then(() => + removeFromFavorites().then(() => { + captureEvent(PAGE_FAVORITED, { + page_id: pageId, + element: E_PAGES, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Page removed from favorites.", - }) - ); + }); + }); else - addToFavorites().then(() => + addToFavorites().then(() => { + captureEvent(PAGE_UNFAVORITED, { + page_id: pageId, + element: E_PAGES, + }); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Page added to favorites.", - }) - ); + }); + }); }; return ( <> diff --git a/web/components/pages/list/filters/root.tsx b/web/components/pages/list/filters/root.tsx index a9a152e0a..179e0b4fd 100644 --- a/web/components/pages/list/filters/root.tsx +++ b/web/components/pages/list/filters/root.tsx @@ -5,6 +5,10 @@ import { TPageFilterProps, TPageFilters } from "@plane/types"; // components import { FilterOption } from "@/components/issues"; import { FilterCreatedBy, FilterCreatedDate } from "@/components/pages"; +// constants +import { PAGES_FILTER_APPLIED, PAGES_FILTER_REMOVED } from "@/constants/event-tracker"; +// hooks +import { useEventTracker } from "@/hooks/store"; type Props = { filters: TPageFilters; @@ -16,11 +20,13 @@ export const PageFiltersSelection: React.FC = observer((props) => { const { filters, handleFiltersUpdate, memberIds } = props; // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + // store hooks + const { captureEvent } = useEventTracker(); const handleFilters = (key: keyof TPageFilterProps, value: boolean | string | string[]) => { - const newValues = filters.filters?.[key] ?? []; + if (typeof filters.filters?.[key] === "boolean" && typeof value === "boolean") return; - if (typeof newValues === "boolean" && typeof value === "boolean") return; + const newValues = Array.from((filters.filters?.[key] ?? []) as string[]); if (Array.isArray(newValues)) { if (Array.isArray(value)) @@ -32,6 +38,16 @@ export const PageFiltersSelection: React.FC = observer((props) => { if (newValues?.includes(value)) newValues.splice(newValues.indexOf(value), 1); else newValues.push(value); } + captureEvent( + ((filters.filters?.[key] ?? []) as string[]).length > newValues.length + ? PAGES_FILTER_REMOVED + : PAGES_FILTER_APPLIED, + { + filter_type: key, + filter_property: value, + current_filters: filters?.filters, + } + ); } handleFiltersUpdate("filters", { @@ -64,12 +80,17 @@ export const PageFiltersSelection: React.FC = observer((props) => {
+ onClick={() => { handleFiltersUpdate("filters", { ...filters.filters, favorites: !filters.filters?.favorites, - }) - } + }); + captureEvent(!filters.filters?.favorites ? PAGES_FILTER_REMOVED : PAGES_FILTER_APPLIED, { + filter_type: "favorites", + filter_property: filters.filters?.favorites, + current_filters: filters, + }); + }} title="Favorites" />
diff --git a/web/components/pages/list/tab-navigation.tsx b/web/components/pages/list/tab-navigation.tsx index fc28d50a2..ba0805a42 100644 --- a/web/components/pages/list/tab-navigation.tsx +++ b/web/components/pages/list/tab-navigation.tsx @@ -2,8 +2,12 @@ import { FC } from "react"; import Link from "next/link"; // types import { TPageNavigationTabs } from "@plane/types"; +// constants +import { PAGES_TAB_CHANGED, E_PAGES } from "@/constants/event-tracker"; // helpers import { cn } from "@/helpers/common.helper"; +// hooks +import { useEventTracker } from "@/hooks/store"; type TPageTabNavigation = { workspaceSlug: string; @@ -29,9 +33,15 @@ const pageTabs: { key: TPageNavigationTabs; label: string }[] = [ export const PageTabNavigation: FC = (props) => { const { workspaceSlug, projectId, pageType } = props; + // store hooks + const { captureEvent } = useEventTracker(); const handleTabClick = (e: React.MouseEvent, tabKey: TPageNavigationTabs) => { if (tabKey === pageType) e.preventDefault(); + captureEvent(PAGES_TAB_CHANGED, { + tab: tabKey, + element: E_PAGES, + }); }; return ( diff --git a/web/constants/event-tracker.ts b/web/constants/event-tracker.ts index 173fb7f34..c26bfae93 100644 --- a/web/constants/event-tracker.ts +++ b/web/constants/event-tracker.ts @@ -292,6 +292,10 @@ export const PAGE_LOCKED = "Page locked"; export const PAGE_UNLOCKED = "Page unlocked"; export const PAGE_DUPLICATED = "Page duplicated"; export const PAGE_RESTORED = "Page restored"; +export const PAGES_TAB_CHANGED = "Pages tab changed"; +export const PAGES_SORT_UPDATED = "Pages sort updated"; +export const PAGES_FILTER_APPLIED = "Pages filter applied"; +export const PAGES_FILTER_REMOVED = "Pages filter removed"; // AI Assistant Events export const AI_TRIGGERED = "AI triggered"; export const AI_RES_USED = "AI response used"; @@ -317,7 +321,7 @@ export const WEBHOOK_CREATED = "Webhook created"; export const WEBHOOK_UPDATED = "Webhook updated"; export const WEBHOOK_DELETED = "Webhook deleted"; export const WEBHOOK_ENABLED = "Webhook enabled"; -export const WEBHOOK_DISABLED = "Webhook diabled"; +export const WEBHOOK_DISABLED = "Webhook disabled"; export const WEBHOOK_KEY_REGEN = "Webhook secret key regenerated"; // API Token Events export const API_TOKEN_CREATED = "API token created";