From 2448a609520f41918e819aa7a5331f40f584da2e Mon Sep 17 00:00:00 2001 From: LAKHAN BAHETI Date: Mon, 3 Jun 2024 17:06:55 +0530 Subject: [PATCH] added module events --- web/components/headers/modules-list.tsx | 4 +-- .../modules/archived-modules/modal.tsx | 8 ++++- .../modules/archived-modules/root.tsx | 10 +++++- web/components/modules/dropdowns/order-by.tsx | 10 +++--- .../modules/module-list-item-action.tsx | 6 ++-- web/components/modules/module-view-header.tsx | 36 +++++++++++++++++-- web/components/modules/quick-actions.tsx | 10 ++++-- web/constants/event-tracker.ts | 11 ++++++ .../projects/[projectId]/modules/index.tsx | 10 +++++- 9 files changed, 86 insertions(+), 19 deletions(-) diff --git a/web/components/headers/modules-list.tsx b/web/components/headers/modules-list.tsx index 0e1fd53fc..3943c2bf0 100644 --- a/web/components/headers/modules-list.tsx +++ b/web/components/headers/modules-list.tsx @@ -8,7 +8,7 @@ import { ModuleViewHeader } from "@/components/modules"; // constants import { EUserProjectRoles } from "@/constants/project"; // hooks -import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProject, useUser, } from "@/hooks/store"; export const ModulesListHeader: React.FC = observer(() => { // router @@ -16,7 +16,7 @@ export const ModulesListHeader: React.FC = observer(() => { const { workspaceSlug } = router.query; // store hooks const { toggleCreateModuleModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); + const { setTrackElement, captureEvent } = useEventTracker(); const { membership: { currentProjectRole }, } = useUser(); diff --git a/web/components/modules/archived-modules/modal.tsx b/web/components/modules/archived-modules/modal.tsx index abfb084f0..0b3380fe9 100644 --- a/web/components/modules/archived-modules/modal.tsx +++ b/web/components/modules/archived-modules/modal.tsx @@ -3,8 +3,10 @@ import { useRouter } from "next/router"; import { Dialog, Transition } from "@headlessui/react"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; +// constants +import { MODULE_ARCHIVED } from "@/constants/event-tracker"; // hooks -import { useModule } from "@/hooks/store"; +import { useEventTracker, useModule } from "@/hooks/store"; type Props = { workspaceSlug: string; @@ -23,6 +25,7 @@ export const ArchiveModuleModal: React.FC = (props) => { const [isArchiving, setIsArchiving] = useState(false); // store hooks const { getModuleNameById, archiveModule } = useModule(); + const { captureEvent } = useEventTracker(); const moduleName = getModuleNameById(moduleId); @@ -42,6 +45,9 @@ export const ArchiveModuleModal: React.FC = (props) => { }); onClose(); router.push(`/${workspaceSlug}/projects/${projectId}/modules`); + captureEvent(MODULE_ARCHIVED, { + module_id: moduleId, + }); }) .catch(() => setToast({ diff --git a/web/components/modules/archived-modules/root.tsx b/web/components/modules/archived-modules/root.tsx index 9abf4f87d..c54b96e21 100644 --- a/web/components/modules/archived-modules/root.tsx +++ b/web/components/modules/archived-modules/root.tsx @@ -10,10 +10,11 @@ import { ArchivedModulesView, ModuleAppliedFiltersList } from "@/components/modu import { CycleModuleListLayout } from "@/components/ui"; // constants import { EmptyStateType } from "@/constants/empty-state"; +import { MODULES_FILTER_REMOVED } from "@/constants/event-tracker"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useModule, useModuleFilter } from "@/hooks/store"; +import { useModule, useModuleFilter, useEventTracker } from "@/hooks/store"; export const ArchivedModuleLayoutRoot: React.FC = observer(() => { // router @@ -21,6 +22,7 @@ export const ArchivedModuleLayoutRoot: React.FC = observer(() => { const { workspaceSlug, projectId } = router.query; // hooks const { fetchArchivedModules, projectArchivedModuleIds, loader } = useModule(); + const { captureEvent } = useEventTracker(); // module filters hook const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useModuleFilter(); // derived values @@ -44,6 +46,12 @@ export const ArchivedModuleLayoutRoot: React.FC = observer(() => { if (!value) newValues = []; else newValues = newValues.filter((val) => val !== value); + captureEvent(MODULES_FILTER_REMOVED, { + filter_type: key, + filter_property: value, + current_filters: currentProjectArchivedFilters, + }); + updateFilters(projectId.toString(), { [key]: newValues }, "archived"); }, [currentProjectArchivedFilters, projectId, updateFilters] diff --git a/web/components/modules/dropdowns/order-by.tsx b/web/components/modules/dropdowns/order-by.tsx index cecda8e88..3d0c4b808 100644 --- a/web/components/modules/dropdowns/order-by.tsx +++ b/web/components/modules/dropdowns/order-by.tsx @@ -9,7 +9,7 @@ import { cn } from "@/helpers/common.helper"; // constants type Props = { - onChange: (value: TModuleOrderByOptions) => void; + onChange: (property: "order_by" | "sort_by", value: TModuleOrderByOptions) => void; value: TModuleOrderByOptions | undefined; }; @@ -39,8 +39,8 @@ export const ModuleOrderByDropdown: React.FC = (props) => { key={option.key} className="flex items-center justify-between gap-2" onClick={() => { - if (isDescending && !isManual) onChange(`-${option.key}` as TModuleOrderByOptions); - else onChange(option.key); + if (isDescending && !isManual) onChange("order_by", `-${option.key}` as TModuleOrderByOptions); + else onChange("order_by", option.key); }} > {option.label} @@ -53,7 +53,7 @@ export const ModuleOrderByDropdown: React.FC = (props) => { { - if (isDescending) onChange(value.slice(1) as TModuleOrderByOptions); + if (isDescending) onChange("sort_by", value.slice(1) as TModuleOrderByOptions); }} > Ascending @@ -62,7 +62,7 @@ export const ModuleOrderByDropdown: React.FC = (props) => { { - if (!isDescending) onChange(`-${value}` as TModuleOrderByOptions); + if (!isDescending) onChange("sort_by", `-${value}` as TModuleOrderByOptions); }} > Descending diff --git a/web/components/modules/module-list-item-action.tsx b/web/components/modules/module-list-item-action.tsx index 2a5a3cdd0..48c96603b 100644 --- a/web/components/modules/module-list-item-action.tsx +++ b/web/components/modules/module-list-item-action.tsx @@ -11,7 +11,7 @@ import { Tooltip, setPromiseToast } from "@plane/ui"; import { FavoriteStar } from "@/components/core"; import { ModuleQuickActions } from "@/components/modules"; // constants -import { MODULE_FAVORITED, MODULE_UNFAVORITED } from "@/constants/event-tracker"; +import { E_LIST_LAYOUT, MODULE_FAVORITED, MODULE_UNFAVORITED } from "@/constants/event-tracker"; import { MODULE_STATUS } from "@/constants/module"; import { EUserProjectRoles } from "@/constants/project"; // helpers @@ -59,7 +59,7 @@ export const ModuleListItemAction: FC = observer((props) => { () => { captureEvent(MODULE_FAVORITED, { module_id: moduleId, - element: "Grid layout", + element: E_LIST_LAYOUT, state: "SUCCESS", }); } @@ -90,7 +90,7 @@ export const ModuleListItemAction: FC = observer((props) => { ).then(() => { captureEvent(MODULE_UNFAVORITED, { module_id: moduleId, - element: "Grid layout", + element: E_LIST_LAYOUT, state: "SUCCESS", }); }); diff --git a/web/components/modules/module-view-header.tsx b/web/components/modules/module-view-header.tsx index 426bca68c..cff1a5d84 100644 --- a/web/components/modules/module-view-header.tsx +++ b/web/components/modules/module-view-header.tsx @@ -13,11 +13,18 @@ import { Tooltip } from "@plane/ui"; import { FiltersDropdown } from "@/components/issues"; import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules/dropdowns"; // constants +import { + E_MODULES, + MODULES_FILTER_APPLIED, + MODULES_FILTER_REMOVED, + MODULES_LAYOUT_CHANGED, + MODULES_SORT_UPDATED, +} from "@/constants/event-tracker"; import { MODULE_VIEW_LAYOUTS } from "@/constants/module"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useMember, useModuleFilter } from "@/hooks/store"; +import { useEventTracker, useMember, useModuleFilter } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -44,6 +51,7 @@ export const ModuleViewHeader: FC = observer(() => { updateFilters, updateSearchQuery, } = useModuleFilter(); + const { captureEvent } = useEventTracker(); // states const [isSearchOpen, setIsSearchOpen] = useState(searchQuery !== "" ? true : false); @@ -64,9 +72,15 @@ export const ModuleViewHeader: FC = observer(() => { else newValues.push(value); } + captureEvent((filters?.[key] ?? []).length > newValues.length ? MODULES_FILTER_REMOVED : MODULES_FILTER_APPLIED, { + filter_type: key, + filter_property: value, + current_filters: filters, + }); + updateFilters(projectId.toString(), { [key]: newValues }); }, - [filters, projectId, updateFilters] + [filters, projectId, updateFilters, captureEvent] ); const handleInputKeyDown = (e: React.KeyboardEvent) => { @@ -135,11 +149,19 @@ export const ModuleViewHeader: FC = observer(() => { { + onChange={(property, val) => { if (!projectId || val === displayFilters?.order_by) return; updateDisplayFilters(projectId.toString(), { order_by: val, }); + captureEvent(MODULES_SORT_UPDATED, { + changed_property: property, + change_details: val.replaceAll("-", ""), + current_sort: { + order_by: displayFilters?.order_by?.replaceAll("-", ""), + sort_by: displayFilters?.order_by?.startsWith("-") ? "desc" : "asc", + }, + }); }} /> { handleDisplayFiltersUpdate={(val) => { if (!projectId) return; updateDisplayFilters(projectId.toString(), val); + captureEvent(!val.favorites ? MODULES_FILTER_REMOVED : MODULES_FILTER_APPLIED, { + filter_type: "favorites", + filter_property: val.favorites, + current_filters: filters, + }); }} handleFiltersUpdate={handleFilters} memberIds={workspaceMemberIds ?? undefined} @@ -173,6 +200,9 @@ export const ModuleViewHeader: FC = observer(() => { onClick={() => { if (!projectId) return; updateDisplayFilters(projectId.toString(), { layout: layout.key }); + captureEvent(MODULES_LAYOUT_CHANGED, { + layout: layout.key, + }); }} > = observer((props) => { const [archiveModuleModal, setArchiveModuleModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // store hooks - const { setTrackElement } = useEventTracker(); + const { setTrackElement, captureEvent } = useEventTracker(); const { membership: { currentWorkspaceAllProjectsRole }, } = useUser(); @@ -58,7 +59,7 @@ export const ModuleQuickActions: React.FC = observer((props) => { const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank"); const handleEditModule = () => { - setTrackElement("Modules page list layout"); + setTrackElement(E_MODULES); setEditModal(true); }; @@ -73,6 +74,9 @@ export const ModuleQuickActions: React.FC = observer((props) => { message: "Your module can be found in project modules.", }); router.push(`/${workspaceSlug}/projects/${projectId}/archives/modules`); + captureEvent(MODULE_RESTORED, { + module_id: moduleId, + }); }) .catch(() => setToast({ @@ -83,7 +87,7 @@ export const ModuleQuickActions: React.FC = observer((props) => { ); const handleDeleteModule = () => { - setTrackElement("Modules page list layout"); + setTrackElement(E_MODULES); setDeleteModal(true); }; diff --git a/web/constants/event-tracker.ts b/web/constants/event-tracker.ts index 7edfccba5..e67985b59 100644 --- a/web/constants/event-tracker.ts +++ b/web/constants/event-tracker.ts @@ -163,6 +163,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"; +export const MODULES_LAYOUT_CHANGED = "Modules layout changed"; +export const MODULES_SORT_UPDATED = "Modules sort updated"; +export const MODULES_FILTER_APPLIED = "Modules filter applied"; +export const MODULES_FILTER_REMOVED = "Modules filter removed"; +export const MODULE_ARCHIVED = "Module archived"; +export const MODULE_RESTORED = "Module restored"; // Issue Events export const ISSUE_CREATED = "Issue created"; export const ISSUE_UPDATED = "Issue updated"; @@ -222,3 +228,8 @@ export const SNOOZED_NOTIFICATIONS = "Snoozed notifications viewed"; export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed"; // Groups export const GROUP_WORKSPACE = "Workspace_metrics"; + +// Elements +export const E_MODULES = "Modules Page"; +export const E_GRID_LAYOUT = "Grid Layout"; +export const E_LIST_LAYOUT = "List Layout"; \ No newline at end of file diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 08e621d0f..8e0cbc0b8 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -11,10 +11,11 @@ import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules" import ModulesListMobileHeader from "@/components/modules/moduels-list-mobile-header"; // constants import { EmptyStateType } from "@/constants/empty-state"; +import { MODULES_FILTER_REMOVED } from "@/constants/event-tracker"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useModuleFilter, useProject } from "@/hooks/store"; +import { useModuleFilter, useProject, useEventTracker } from "@/hooks/store"; // layouts import { AppLayout } from "@/layouts/app-layout"; // types @@ -27,6 +28,7 @@ const ProjectModulesPage: NextPageWithLayout = observer(() => { const { getProjectById, currentProjectDetails } = useProject(); const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } = useModuleFilter(); + const { captureEvent } = useEventTracker(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Modules` : undefined; @@ -39,6 +41,12 @@ const ProjectModulesPage: NextPageWithLayout = observer(() => { if (!value) newValues = []; else newValues = newValues.filter((val) => val !== value); + captureEvent(MODULES_FILTER_REMOVED, { + filter_type: key, + filter_property: value, + current_filters: currentProjectFilters, + }); + updateFilters(projectId.toString(), { [key]: newValues }); }, [currentProjectFilters, projectId, updateFilters]