diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx index bb070a22f..22720e4da 100644 --- a/web/components/headers/project-views.tsx +++ b/web/components/headers/project-views.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Plus } from "lucide-react"; // hooks -import { useApplication, useProject, useUser } from "hooks/store"; +import { useApplication, useEventTracker, useProject, useUser } from "hooks/store"; // components import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui"; import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; @@ -24,6 +24,7 @@ export const ProjectViewsHeader: React.FC = observer(() => { membership: { currentProjectRole }, } = useUser(); const { currentProjectDetails } = useProject(); + const { setTrackElement } = useEventTracker(); const canUserCreateIssue = currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); @@ -75,7 +76,10 @@ export const ProjectViewsHeader: React.FC = observer(() => { variant="primary" size="sm" prependIcon={} - onClick={() => toggleCreateViewModal(true)} + onClick={() => { + setTrackElement("Views page"); + toggleCreateViewModal(true); + }} > Create View diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 0768064ec..37bc717a9 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -2,14 +2,16 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import isEqual from "lodash/isEqual"; // hooks -import { useIssues, useLabel, useProjectState, useProjectView } from "hooks/store"; +import { useEventTracker, useIssues, useLabel, useProjectState, useProjectView } from "hooks/store"; // components import { AppliedFiltersList } from "components/issues"; // ui import { Button } from "@plane/ui"; // types import { IIssueFilterOptions } from "@plane/types"; +// constants import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; +import { VIEW_UPDATED } from "constants/event-tracker"; export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { // router @@ -26,6 +28,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { const { projectLabels } = useLabel(); const { projectStates } = useProjectState(); const { viewMap, updateView } = useProjectView(); + const { captureEvent } = useEventTracker(); // derived values const viewDetails = viewId ? viewMap[viewId.toString()] : null; const userFilters = issueFilters?.filters; @@ -87,6 +90,13 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { filters: { ...(appliedFilters ?? {}), }, + }).then((res) => { + captureEvent(VIEW_UPDATED, { + view_id: res.id, + filters: res.filters, + element: "View Navbar", + state: "SUCCESS", + }); }); }; diff --git a/web/components/views/delete-view-modal.tsx b/web/components/views/delete-view-modal.tsx index 5bd477352..4b8345072 100644 --- a/web/components/views/delete-view-modal.tsx +++ b/web/components/views/delete-view-modal.tsx @@ -4,12 +4,13 @@ import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; import { AlertTriangle } from "lucide-react"; // hooks -import { useProjectView } from "hooks/store"; +import { useProjectView, useEventTracker } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { Button } from "@plane/ui"; // types import { IProjectView } from "@plane/types"; +import { VIEW_DELETED } from "constants/event-tracker"; type Props = { data: IProjectView; @@ -26,6 +27,7 @@ export const DeleteProjectViewModal: React.FC = observer((props) => { const { workspaceSlug, projectId } = router.query; // store hooks const { deleteView } = useProjectView(); + const { captureEvent } = useEventTracker(); // toast alert const { setToastAlert } = useToast(); @@ -42,20 +44,29 @@ export const DeleteProjectViewModal: React.FC = observer((props) => { await deleteView(workspaceSlug.toString(), projectId.toString(), data.id) .then(() => { handleClose(); - + captureEvent(VIEW_DELETED, { + view_id: data.id, + element: "Views page", + state: "SUCCESS", + }); setToastAlert({ type: "success", title: "Success!", message: "View deleted successfully.", }); }) - .catch(() => + .catch(() => { + captureEvent(VIEW_DELETED, { + view_id: data.id, + element: "Views page", + state: "FAILED", + }); setToastAlert({ type: "error", title: "Error!", message: "View could not be deleted. Please try again.", - }) - ) + }); + }) .finally(() => { setIsDeleteLoading(false); }); diff --git a/web/components/views/modal.tsx b/web/components/views/modal.tsx index 43cea7d5c..dee435a06 100644 --- a/web/components/views/modal.tsx +++ b/web/components/views/modal.tsx @@ -1,13 +1,16 @@ import { FC, Fragment } from "react"; +import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; // hooks -import { useProjectView } from "hooks/store"; +import { useEventTracker, useProjectView } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { ProjectViewForm } from "components/views"; // types import { IProjectView } from "@plane/types"; +// constants +import { VIEW_CREATED, VIEW_UPDATED } from "constants/event-tracker"; type Props = { data?: IProjectView | null; @@ -20,8 +23,12 @@ type Props = { export const CreateUpdateProjectViewModal: FC = observer((props) => { const { data, isOpen, onClose, preLoadedData, workspaceSlug, projectId } = props; + // router + const router = useRouter(); + const { cycleId, moduleId, viewId } = router.query; // store hooks const { createView, updateView } = useProjectView(); + const { captureEvent, trackElement } = useEventTracker(); // toast alert const { setToastAlert } = useToast(); @@ -31,33 +38,64 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { const handleCreateView = async (payload: IProjectView) => { await createView(workspaceSlug, projectId, payload) - .then(() => { + .then((res) => { handleClose(); + captureEvent(VIEW_CREATED, { + view_id: res.id, + filters: res.filters, + element_id: cycleId ?? moduleId ?? viewId ?? projectId, + element: trackElement + ? trackElement + : cycleId + ? "Cycle issues page" + : moduleId + ? "Module issues page" + : viewId + ? "View issues page" + : "Project issues page", + state: "SUCCESS", + }); setToastAlert({ type: "success", title: "Success!", message: "View created successfully.", }); }) - .catch(() => + .catch(() => { + captureEvent(VIEW_CREATED, { + state: "FAILED", + }); setToastAlert({ type: "error", title: "Error!", message: "Something went wrong. Please try again.", - }) - ); + }); + }); }; const handleUpdateView = async (payload: IProjectView) => { await updateView(workspaceSlug, projectId, data?.id as string, payload) - .then(() => handleClose()) - .catch((err) => + .then((res) => { + captureEvent(VIEW_UPDATED, { + view_id: res.id, + filters: res.filters, + element: "Views page", + state: "SUCCESS", + }); + handleClose(); + }) + .catch((err) => { + captureEvent(VIEW_UPDATED, { + view_id: data?.id, + element: "Views page", + state: "FAILED", + }); setToastAlert({ type: "error", title: "Error!", message: err.detail ?? "Something went wrong. Please try again.", - }) - ); + }); + }); }; const handleFormSubmit = async (formData: IProjectView) => { diff --git a/web/components/views/view-list-item.tsx b/web/components/views/view-list-item.tsx index 8da507539..65f4e8163 100644 --- a/web/components/views/view-list-item.tsx +++ b/web/components/views/view-list-item.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react"; // hooks -import { useProjectView, useUser } from "hooks/store"; +import { useEventTracker, useProjectView, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views"; @@ -17,6 +17,7 @@ import { copyUrlToClipboard } from "helpers/string.helper"; import { IProjectView } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; +import { VIEW_FAVORITED, VIEW_UNFAVORITED } from "constants/event-tracker"; type Props = { view: IProjectView; @@ -37,17 +38,30 @@ export const ProjectViewListItem: React.FC = observer((props) => { membership: { currentProjectRole }, } = useUser(); const { addViewToFavorites, removeViewFromFavorites } = useProjectView(); + const { captureEvent } = useEventTracker(); const handleAddToFavorites = () => { if (!workspaceSlug || !projectId) return; - addViewToFavorites(workspaceSlug.toString(), projectId.toString(), view.id); + addViewToFavorites(workspaceSlug.toString(), projectId.toString(), view.id).then(() => { + captureEvent(VIEW_FAVORITED, { + view_id: view.id, + element: "Views page", + state: "SUCCESS", + }); + }); }; const handleRemoveFromFavorites = () => { if (!workspaceSlug || !projectId) return; - removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id); + removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id).then(() => { + captureEvent(VIEW_UNFAVORITED, { + view_id: view.id, + element: "Views page", + state: "SUCCESS", + }); + }); }; const handleCopyText = (e: React.MouseEvent) => { diff --git a/web/components/views/views-list.tsx b/web/components/views/views-list.tsx index 902193dba..27ca91eed 100644 --- a/web/components/views/views-list.tsx +++ b/web/components/views/views-list.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import { Search } from "lucide-react"; import { useTheme } from "next-themes"; // hooks -import { useApplication, useProjectView, useUser } from "hooks/store"; +import { useApplication, useEventTracker, useProjectView, useUser } from "hooks/store"; // components import { ProjectViewListItem } from "components/views"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; @@ -28,6 +28,7 @@ export const ProjectViewsList = observer(() => { currentUser, } = useUser(); const { projectViewIds, getViewById, loader } = useProjectView(); + const { setTrackElement } = useEventTracker(); if (loader || !projectViewIds) return ; @@ -73,7 +74,10 @@ export const ProjectViewsList = observer(() => { }} primaryButton={{ text: VIEW_EMPTY_STATE_DETAILS["project-views"].primaryButton.text, - onClick: () => toggleCreateViewModal(true), + onClick: () => { + setTrackElement("Views empty state"); + toggleCreateViewModal(true); + }, }} size="lg" disabled={!isEditingAllowed} diff --git a/web/constants/event-tracker.ts b/web/constants/event-tracker.ts index a0bf0b5bb..975528ed5 100644 --- a/web/constants/event-tracker.ts +++ b/web/constants/event-tracker.ts @@ -165,6 +165,12 @@ export const MODULE_UNFAVORITED = "Module unfavorited"; export const MODULE_LINK_CREATED = "Module link created"; export const MODULE_LINK_UPDATED = "Module link updated"; export const MODULE_LINK_DELETED = "Module link deleted"; +// Project View Events +export const VIEW_CREATED = "View created"; +export const VIEW_UPDATED = "View updated"; +export const VIEW_DELETED = "View deleted"; +export const VIEW_FAVORITED = "View favorited"; +export const VIEW_UNFAVORITED = "View unfavorited"; // Issue Events export const ISSUE_CREATED = "Issue created"; export const ISSUE_UPDATED = "Issue updated";