From aab34ff36d43ae5e0a447e7d3b3b2819a574367d Mon Sep 17 00:00:00 2001 From: LAKHAN BAHETI Date: Tue, 30 Apr 2024 18:23:16 +0530 Subject: [PATCH] chore: inbox events --- web/components/headers/project-inbox.tsx | 15 +++- .../inbox/content/inbox-issue-header.tsx | 23 +++++- web/components/inbox/content/issue-root.tsx | 32 ++++---- .../inbox/inbox-filter/filters/date.tsx | 28 +++++-- .../inbox-filter/filters/filter-selection.tsx | 76 +++++++++++++++++-- .../inbox/inbox-filter/filters/labels.tsx | 24 ++++-- .../inbox/inbox-filter/filters/members.tsx | 24 ++++-- .../inbox/inbox-filter/filters/priority.tsx | 24 ++++-- .../inbox/inbox-filter/filters/state.tsx | 24 ++++-- .../inbox/inbox-filter/filters/status.tsx | 23 ++++-- .../inbox/inbox-filter/sorting/order-by.tsx | 23 +++++- .../modals/create-edit-modal/create-root.tsx | 33 ++++---- .../inbox/sidebar/inbox-list-item.tsx | 14 +++- web/components/inbox/sidebar/root.tsx | 7 +- .../issue-detail/issue-activity/root.tsx | 15 ++-- web/constants/event-tracker.ts | 12 ++- 16 files changed, 296 insertions(+), 101 deletions(-) diff --git a/web/components/headers/project-inbox.tsx b/web/components/headers/project-inbox.tsx index 80a39862b..59a18e306 100644 --- a/web/components/headers/project-inbox.tsx +++ b/web/components/headers/project-inbox.tsx @@ -8,8 +8,10 @@ import { Breadcrumbs, Button, LayersIcon } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; import { InboxIssueCreateEditModalRoot } from "@/components/inbox"; import { ProjectLogo } from "@/components/project"; +// constants +import { E_INBOX } from "@/constants/event-tracker"; // hooks -import { useProject, useProjectInbox } from "@/hooks/store"; +import { useEventTracker, useProject, useProjectInbox } from "@/hooks/store"; export const ProjectInboxHeader: FC = observer(() => { // states @@ -20,6 +22,7 @@ export const ProjectInboxHeader: FC = observer(() => { // store hooks const { currentProjectDetails } = useProject(); const { loader } = useProjectInbox(); + const { setTrackElement } = useEventTracker(); return (
@@ -70,7 +73,15 @@ export const ProjectInboxHeader: FC = observer(() => { issue={undefined} /> -
diff --git a/web/components/inbox/content/inbox-issue-header.tsx b/web/components/inbox/content/inbox-issue-header.tsx index 23ff83706..f73b44d6d 100644 --- a/web/components/inbox/content/inbox-issue-header.tsx +++ b/web/components/inbox/content/inbox-issue-header.tsx @@ -25,12 +25,13 @@ import { } from "@/components/inbox"; import { IssueUpdateStatus } from "@/components/issues"; // constants +import { INBOX_ISSUE_DELETED, INBOX_ISSUE_UPDATED } from "@/constants/event-tracker"; import { EUserProjectRoles } from "@/constants/project"; // helpers import { EInboxIssueStatus } from "@/helpers/inbox.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks -import { useUser, useProjectInbox, useProject } from "@/hooks/store"; +import { useUser, useProjectInbox, useProject, useEventTracker } from "@/hooks/store"; // store types import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store"; @@ -60,6 +61,7 @@ export const InboxIssueActionsHeader: FC = observer((p const router = useRouter(); const { getProjectById } = useProject(); + const { captureEvent } = useEventTracker(); const issue = inboxIssue?.issue; // derived values @@ -98,6 +100,10 @@ export const InboxIssueActionsHeader: FC = observer((p await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED); setAcceptIssueModal(false); handleRedirection(nextOrPreviousIssueId); + captureEvent(INBOX_ISSUE_UPDATED, { + issue_status: "accepted", + issue_id: currentInboxIssueId, + }); }; const handleInboxIssueDecline = async () => { @@ -105,6 +111,10 @@ export const InboxIssueActionsHeader: FC = observer((p await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED); setDeclineIssueModal(false); handleRedirection(nextOrPreviousIssueId); + captureEvent(INBOX_ISSUE_UPDATED, { + issue_status: "declined", + issue_id: currentInboxIssueId, + }); }; const handleInboxSIssueSnooze = async (date: Date) => { @@ -112,14 +122,25 @@ export const InboxIssueActionsHeader: FC = observer((p await inboxIssue?.updateInboxIssueSnoozeTill(date); setIsSnoozeDateModalOpen(false); handleRedirection(nextOrPreviousIssueId); + captureEvent(INBOX_ISSUE_UPDATED, { + issue_status: "snoozed", + issue_id: currentInboxIssueId, + }); }; const handleInboxIssueDuplicate = async (issueId: string) => { await inboxIssue?.updateInboxIssueDuplicateTo(issueId); + captureEvent(INBOX_ISSUE_UPDATED, { + issue_status: "mark as duplicate", + issue_id: currentInboxIssueId, + }); }; const handleInboxIssueDelete = async () => { if (!inboxIssue || !currentInboxIssueId) return; + captureEvent(INBOX_ISSUE_DELETED, { + issue_id: currentInboxIssueId, + }); await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => { router.push(`/${workspaceSlug}/projects/${projectId}/inbox`); }); diff --git a/web/components/inbox/content/issue-root.tsx b/web/components/inbox/content/issue-root.tsx index 7f8892ef5..cdfcd359a 100644 --- a/web/components/inbox/content/issue-root.tsx +++ b/web/components/inbox/content/issue-root.tsx @@ -13,6 +13,8 @@ import { TIssueOperations, IssueAttachmentRoot, } from "@/components/issues"; +// constants +import { E_INBOX, INBOX_ISSUE_UPDATED } from "@/constants/event-tracker"; // hooks import { useEventTracker, useProjectInbox, useUser } from "@/hooks/store"; import useReloadConfirmations from "@/hooks/use-reload-confirmation"; @@ -36,7 +38,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => { // hooks const { data: currentUser } = useUser(); const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting"); - const { captureIssueEvent } = useEventTracker(); + const { captureEvent } = useEventTracker(); const { loader } = useProjectInbox(); useEffect(() => { @@ -82,14 +84,12 @@ export const InboxIssueMainContent: React.FC = observer((props) => { update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { try { await inboxIssue.updateIssue(data); - captureIssueEvent({ - eventName: "Inbox issue updated", - payload: { ...data, state: "SUCCESS", element: "Inbox" }, - updates: { - changed_property: Object.keys(data).join(","), - change_details: Object.values(data).join(","), - }, - routePath: router.asPath, + captureEvent(INBOX_ISSUE_UPDATED, { + ...data, + changed_property: Object.keys(data).join(","), + change_details: Object.values(data).join(","), + element: E_INBOX, + state: "SUCCESS", }); } catch (error) { setToast({ @@ -97,14 +97,12 @@ export const InboxIssueMainContent: React.FC = observer((props) => { type: TOAST_TYPE.ERROR, message: "Issue update failed", }); - captureIssueEvent({ - eventName: "Inbox issue updated", - payload: { state: "SUCCESS", element: "Inbox" }, - updates: { - changed_property: Object.keys(data).join(","), - change_details: Object.values(data).join(","), - }, - routePath: router.asPath, + captureEvent(INBOX_ISSUE_UPDATED, { + ...data, + changed_property: Object.keys(data).join(","), + change_details: Object.values(data).join(","), + element: E_INBOX, + state: "FAILED", }); } }, diff --git a/web/components/inbox/inbox-filter/filters/date.tsx b/web/components/inbox/inbox-filter/filters/date.tsx index aee188be8..902ead05f 100644 --- a/web/components/inbox/inbox-filter/filters/date.tsx +++ b/web/components/inbox/inbox-filter/filters/date.tsx @@ -2,19 +2,26 @@ import { FC, useState } from "react"; import concat from "lodash/concat"; import uniq from "lodash/uniq"; import { observer } from "mobx-react"; -import { TInboxIssueFilterDateKeys } from "@plane/types"; +import { TInboxIssueFilter, TInboxIssueFilterDateKeys } from "@plane/types"; // components import { DateFilterModal } from "@/components/core"; import { FilterHeader, FilterOption } from "@/components/issues"; // constants import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper"; // hooks -import { useProjectInbox } from "@/hooks/store"; +import { useEventTracker } from "@/hooks/store"; type Props = { filterKey: TInboxIssueFilterDateKeys; label?: string; searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; const isDate = (date: string) => { @@ -23,9 +30,7 @@ const isDate = (date: string) => { }; export const FilterDate: FC = observer((props) => { - const { filterKey, label, searchQuery } = props; - // hooks - const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { filterKey, label, searchQuery, inboxFilters, handleFilterUpdate } = props; // state const [previewEnabled, setPreviewEnabled] = useState(true); const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); @@ -46,7 +51,7 @@ export const FilterDate: FC = observer((props) => { const handleCustomDate = () => { if (isCustomDateSelected()) { const updateAppliedFilters = filterValue?.filter((f) => !isDate(f.split(";")[0])) || []; - handleInboxIssueFilters(filterKey, updateAppliedFilters); + handleFilterUpdate(filterKey, updateAppliedFilters, true, "Custom"); } else { setIsDateFilterModalOpen(true); } @@ -58,7 +63,7 @@ export const FilterDate: FC = observer((props) => { setIsDateFilterModalOpen(false)} isOpen={isDateFilterModalOpen} - onSelect={(val) => handleInboxIssueFilters(filterKey, val)} + onSelect={(val) => handleFilterUpdate(filterKey, val, false, "Custom")} title="Created date" /> )} @@ -75,7 +80,14 @@ export const FilterDate: FC = observer((props) => { handleInboxIssueFilters(filterKey, handleFilterValue(option.value))} + onClick={() => + handleFilterUpdate( + filterKey, + handleFilterValue(option.value), + filterValue?.includes(option.value), + option.name + ) + } title={option.name} multiple={false} /> diff --git a/web/components/inbox/inbox-filter/filters/filter-selection.tsx b/web/components/inbox/inbox-filter/filters/filter-selection.tsx index fd1dc951c..8f3711c12 100644 --- a/web/components/inbox/inbox-filter/filters/filter-selection.tsx +++ b/web/components/inbox/inbox-filter/filters/filter-selection.tsx @@ -1,6 +1,7 @@ -import { FC, useState } from "react"; +import { FC, useCallback, useState } from "react"; import { observer } from "mobx-react"; import { Search, X } from "lucide-react"; +import { TInboxIssueFilter } from "@plane/types"; // components import { FilterStatus, @@ -10,8 +11,11 @@ import { FilterLabels, FilterState, } from "@/components/inbox/inbox-filter/filters"; +// constants +import { INBOX_FILTERS_APPLIED, INBOX_FILTERS_REMOVED } from "@/constants/event-tracker"; +import { INBOX_STATUS } from "@/constants/inbox"; // hooks -import { useMember, useLabel, useProjectState } from "@/hooks/store"; +import { useMember, useLabel, useProjectState, useProjectInbox, useEventTracker } from "@/hooks/store"; export const InboxIssueFilterSelection: FC = observer(() => { // hooks @@ -20,9 +24,31 @@ export const InboxIssueFilterSelection: FC = observer(() => { } = useMember(); const { projectLabels } = useLabel(); const { projectStates } = useProjectState(); + const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { captureEvent } = useEventTracker(); // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + const handleFilterUpdate = useCallback( + ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => { + captureEvent(isSelected ? INBOX_FILTERS_REMOVED : INBOX_FILTERS_APPLIED, { + filter_type: filterKey, + filter_property: interactedValue, + current_filters: { + ...inboxFilters, + status: inboxFilters?.status?.map((status) => INBOX_STATUS.find((s) => s.status === status)?.title), + }, + }); + handleInboxIssueFilters(filterKey, filterValue); + }, + [captureEvent, inboxFilters, handleInboxIssueFilters] + ); + return (
@@ -47,21 +73,36 @@ export const InboxIssueFilterSelection: FC = observer(() => {
{/* status */}
- +
{/* state */}
- +
{/* Priority */}
- +
{/* assignees */}
@@ -71,21 +112,40 @@ export const InboxIssueFilterSelection: FC = observer(() => {
{/* Labels */}
- +
{/* Created at */}
- +
{/* Updated at */}
- +
diff --git a/web/components/inbox/inbox-filter/filters/labels.tsx b/web/components/inbox/inbox-filter/filters/labels.tsx index 8f6b765b5..74f284d69 100644 --- a/web/components/inbox/inbox-filter/filters/labels.tsx +++ b/web/components/inbox/inbox-filter/filters/labels.tsx @@ -1,11 +1,9 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; -import { IIssueLabel } from "@plane/types"; +import { IIssueLabel, TInboxIssueFilter } from "@plane/types"; import { Loader } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "@/components/issues"; -// hooks -import { useProjectInbox } from "@/hooks/store"; const LabelIcons = ({ color }: { color: string }) => ( @@ -14,16 +12,21 @@ const LabelIcons = ({ color }: { color: string }) => ( type Props = { labels: IIssueLabel[] | undefined; searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; export const FilterLabels: FC = observer((props) => { - const { labels, searchQuery } = props; + const { labels, searchQuery, inboxFilters, handleFilterUpdate } = props; const [itemsToRender, setItemsToRender] = useState(5); const [previewEnabled, setPreviewEnabled] = useState(true); - const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); - const filterValue = inboxFilters?.labels || []; const appliedFiltersCount = filterValue?.length ?? 0; @@ -56,7 +59,14 @@ export const FilterLabels: FC = observer((props) => { handleInboxIssueFilters("labels", handleFilterValue(label.id))} + onClick={() => + handleFilterUpdate( + "labels", + handleFilterValue(label?.id), + filterValue?.includes(label?.id), + label?.id + ) + } icon={} title={label.name} /> diff --git a/web/components/inbox/inbox-filter/filters/members.tsx b/web/components/inbox/inbox-filter/filters/members.tsx index f86c385e7..9f20fa975 100644 --- a/web/components/inbox/inbox-filter/filters/members.tsx +++ b/web/components/inbox/inbox-filter/filters/members.tsx @@ -1,24 +1,29 @@ import { FC, useMemo, useState } from "react"; import sortBy from "lodash/sortBy"; import { observer } from "mobx-react"; -import { TInboxIssueFilterMemberKeys } from "@plane/types"; +import { TInboxIssueFilter, TInboxIssueFilterMemberKeys } from "@plane/types"; import { Avatar, Loader } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "@/components/issues"; // hooks -import { useMember, useProjectInbox, useUser } from "@/hooks/store"; +import { useEventTracker, useMember, useUser } from "@/hooks/store"; type Props = { filterKey: TInboxIssueFilterMemberKeys; label?: string; memberIds: string[] | undefined; searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; export const FilterMember: FC = observer((props: Props) => { - const { filterKey, label = "Members", memberIds, searchQuery } = props; - // hooks - const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { filterKey, label = "Members", memberIds, searchQuery, inboxFilters, handleFilterUpdate } = props; const { getUserDetails } = useMember(); const { currentUser } = useUser(); // states @@ -71,7 +76,14 @@ export const FilterMember: FC = observer((props: Props) => { handleInboxIssueFilters(filterKey, handleFilterValue(member.id))} + onClick={() => + handleFilterUpdate( + filterKey, + handleFilterValue(member.id), + filterValue?.includes(member.id), + member.id + ) + } icon={} title={currentUser?.id === member.id ? "You" : member?.display_name} /> diff --git a/web/components/inbox/inbox-filter/filters/priority.tsx b/web/components/inbox/inbox-filter/filters/priority.tsx index 0639623e4..373cf719e 100644 --- a/web/components/inbox/inbox-filter/filters/priority.tsx +++ b/web/components/inbox/inbox-filter/filters/priority.tsx @@ -1,22 +1,25 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; -import { TIssuePriorities } from "@plane/types"; +import { TInboxIssueFilter, TIssuePriorities } from "@plane/types"; import { PriorityIcon } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "@/components/issues"; // constants import { ISSUE_PRIORITIES } from "@/constants/issue"; -// hooks -import { useProjectInbox } from "@/hooks/store/use-project-inbox"; type Props = { searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; export const FilterPriority: FC = observer((props) => { - const { searchQuery } = props; - // hooks - const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { searchQuery, inboxFilters, handleFilterUpdate } = props; // states const [previewEnabled, setPreviewEnabled] = useState(true); // derived values @@ -41,7 +44,14 @@ export const FilterPriority: FC = observer((props) => { handleInboxIssueFilters("priority", handleFilterValue(priority.key))} + onClick={() => + handleFilterUpdate( + "priority", + handleFilterValue(priority.key), + filterValue?.includes(priority.key), + priority.title + ) + } icon={} title={priority.title} /> diff --git a/web/components/inbox/inbox-filter/filters/state.tsx b/web/components/inbox/inbox-filter/filters/state.tsx index b3cfad435..c2730e35d 100644 --- a/web/components/inbox/inbox-filter/filters/state.tsx +++ b/web/components/inbox/inbox-filter/filters/state.tsx @@ -1,25 +1,28 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; -import { IState } from "@plane/types"; +import { IState, TInboxIssueFilter } from "@plane/types"; import { Loader, StateGroupIcon } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "@/components/issues"; -// hooks -import { useProjectInbox } from "@/hooks/store"; type Props = { states: IState[] | undefined; searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; export const FilterState: FC = observer((props) => { - const { states, searchQuery } = props; + const { states, inboxFilters, searchQuery, handleFilterUpdate } = props; const [itemsToRender, setItemsToRender] = useState(5); const [previewEnabled, setPreviewEnabled] = useState(true); - const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); - const filterValue = inboxFilters?.state || []; const appliedFiltersCount = filterValue?.length ?? 0; @@ -52,7 +55,14 @@ export const FilterState: FC = observer((props) => { handleInboxIssueFilters("state", handleFilterValue(state.id))} + onClick={() => + handleFilterUpdate( + "state", + handleFilterValue(state.id), + filterValue?.includes(state.id), + state.id + ) + } icon={} title={state.name} /> diff --git a/web/components/inbox/inbox-filter/filters/status.tsx b/web/components/inbox/inbox-filter/filters/status.tsx index b27f360da..f90799132 100644 --- a/web/components/inbox/inbox-filter/filters/status.tsx +++ b/web/components/inbox/inbox-filter/filters/status.tsx @@ -1,22 +1,29 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; // types -import { TInboxIssueStatus } from "@plane/types"; +import { TInboxIssueFilter, TInboxIssueFilterMemberKeys, TInboxIssueStatus } from "@plane/types"; // components import { FilterHeader, FilterOption } from "@/components/issues"; // constants import { INBOX_STATUS } from "@/constants/inbox"; // hooks -import { useProjectInbox } from "@/hooks/store/use-project-inbox"; +import { useProjectInbox } from "@/hooks/store"; type Props = { searchQuery: string; + inboxFilters: Partial; + handleFilterUpdate: ( + filterKey: keyof TInboxIssueFilter, + filterValue: TInboxIssueFilter[keyof TInboxIssueFilter], + isSelected: boolean, + interactedValue: string + ) => void; }; export const FilterStatus: FC = observer((props) => { - const { searchQuery } = props; + const { searchQuery, inboxFilters, handleFilterUpdate } = props; // hooks - const { currentTab, inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { currentTab } = useProjectInbox(); // states const [previewEnabled, setPreviewEnabled] = useState(true); // derived values @@ -34,7 +41,13 @@ export const FilterStatus: FC = observer((props) => { const handleStatusFilterSelect = (status: TInboxIssueStatus) => { const selectedStatus = handleFilterValue(status); - if (selectedStatus.length >= 1) handleInboxIssueFilters("status", selectedStatus); + if (selectedStatus.length >= 1) + handleFilterUpdate( + "status", + selectedStatus, + filterValue?.includes(status), + INBOX_STATUS.find((s) => s.status === status)?.title ?? "" + ); }; return ( diff --git a/web/components/inbox/inbox-filter/sorting/order-by.tsx b/web/components/inbox/inbox-filter/sorting/order-by.tsx index 4eb9dde22..75d67a59e 100644 --- a/web/components/inbox/inbox-filter/sorting/order-by.tsx +++ b/web/components/inbox/inbox-filter/sorting/order-by.tsx @@ -1,20 +1,35 @@ -import { FC } from "react"; +import { FC, useCallback } from "react"; import { observer } from "mobx-react"; import { ArrowDownWideNarrow, ArrowUpWideNarrow, Check, ChevronDown } from "lucide-react"; +import { TInboxIssueSorting } from "@plane/types"; import { CustomMenu, getButtonStyling } from "@plane/ui"; // constants +import { INBOX_SORT_UPDATED } from "@/constants/event-tracker"; import { INBOX_ISSUE_ORDER_BY_OPTIONS, INBOX_ISSUE_SORT_BY_OPTIONS } from "@/constants/inbox"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useProjectInbox } from "@/hooks/store"; +import { useProjectInbox, useEventTracker } from "@/hooks/store"; export const InboxIssueOrderByDropdown: FC = observer(() => { // hooks const { inboxSorting, handleInboxIssueSorting } = useProjectInbox(); + const { captureEvent } = useEventTracker(); const orderByDetails = INBOX_ISSUE_ORDER_BY_OPTIONS.find((option) => inboxSorting?.order_by?.includes(option.key)) || undefined; + const handleIssueSorting = useCallback( + (filterKey: keyof TInboxIssueSorting, filterValue: TInboxIssueSorting[keyof TInboxIssueSorting]) => { + captureEvent(INBOX_SORT_UPDATED, { + changed_property: filterKey, + changed_details: filterValue, + current_sort: inboxSorting, + }); + handleInboxIssueSorting(filterKey, filterValue); + }, + [handleInboxIssueSorting, captureEvent, inboxSorting] + ); + return ( { handleInboxIssueSorting("order_by", option.key)} + onClick={() => handleIssueSorting("order_by", option.key)} > {option.label} {inboxSorting?.order_by?.includes(option.key) && } @@ -47,7 +62,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => { handleInboxIssueSorting("sort_by", option.key)} + onClick={() => handleIssueSorting("sort_by", option.key)} > {option.label} {inboxSorting?.sort_by?.includes(option.key) && } diff --git a/web/components/inbox/modals/create-edit-modal/create-root.tsx b/web/components/inbox/modals/create-edit-modal/create-root.tsx index e317e0879..62c971f31 100644 --- a/web/components/inbox/modals/create-edit-modal/create-root.tsx +++ b/web/components/inbox/modals/create-edit-modal/create-root.tsx @@ -11,7 +11,7 @@ import { InboxIssueProperties, } from "@/components/inbox/modals/create-edit-modal"; // constants -import { ISSUE_CREATED } from "@/constants/event-tracker"; +import { INBOX_ISSUE_CREATED, getIssueEventPayload } from "@/constants/event-tracker"; // helpers import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; // hooks @@ -41,7 +41,7 @@ export const InboxIssueCreateRoot: FC = observer((props) // refs const descriptionEditorRef = useRef(null); // hooks - const { captureIssueEvent } = useEventTracker(); + const { captureEvent } = useEventTracker(); const { createInboxIssue } = useProjectInbox(); const { getWorkspaceBySlug } = useWorkspace(); const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id; @@ -81,14 +81,13 @@ export const InboxIssueCreateRoot: FC = observer((props) descriptionEditorRef?.current?.clearEditor(); setFormData(defaultIssueData); } - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { - ...formData, - state: "SUCCESS", - element: "Inbox page", - }, - routePath: router.pathname, + captureEvent(INBOX_ISSUE_CREATED, { + issue_id: res?.issue?.id, + properties: getIssueEventPayload({ + eventName: INBOX_ISSUE_CREATED, + payload: res?.issue, + }), + state: "SUCCESS", }); setToast({ type: TOAST_TYPE.SUCCESS, @@ -98,14 +97,12 @@ export const InboxIssueCreateRoot: FC = observer((props) }) .catch((error) => { console.error(error); - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { - ...formData, - state: "FAILED", - element: "Inbox page", - }, - routePath: router.pathname, + captureEvent(INBOX_ISSUE_CREATED, { + properties: getIssueEventPayload({ + eventName: INBOX_ISSUE_CREATED, + payload: formData, + }), + state: "FAILED", }); setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/components/inbox/sidebar/inbox-list-item.tsx b/web/components/inbox/sidebar/inbox-list-item.tsx index 89b6a8f8a..1b08fb5ac 100644 --- a/web/components/inbox/sidebar/inbox-list-item.tsx +++ b/web/components/inbox/sidebar/inbox-list-item.tsx @@ -5,11 +5,13 @@ import { useRouter } from "next/router"; import { Tooltip, PriorityIcon } from "@plane/ui"; // components import { InboxIssueStatus } from "@/components/inbox"; +// constants +import { INBOX_ISSUE_OPENED } from "@/constants/event-tracker"; // helpers import { cn } from "@/helpers/common.helper"; import { renderFormattedDate } from "@/helpers/date-time.helper"; // hooks -import { useLabel, useProjectInbox } from "@/hooks/store"; +import { useEventTracker, useLabel, useProjectInbox } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // store import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store"; @@ -23,7 +25,7 @@ type InboxIssueListItemProps = { }; export const InboxIssueListItem: FC = observer((props) => { - const { workspaceSlug, projectId, inboxIssue, projectIdentifier,setToggleMobileSidebar } = props; + const { workspaceSlug, projectId, inboxIssue, projectIdentifier, setToggleMobileSidebar } = props; // router const router = useRouter(); const { inboxIssueId } = router.query; @@ -31,6 +33,7 @@ export const InboxIssueListItem: FC = observer((props) const { currentTab } = useProjectInbox(); const { projectLabels } = useLabel(); const { isMobile } = usePlatformOS(); + const { captureEvent } = useEventTracker(); const issue = inboxIssue.issue; const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => { @@ -45,7 +48,12 @@ export const InboxIssueListItem: FC = observer((props) id={`inbox-issue-list-item-${issue.id}`} key={`${projectId}_${issue.id}`} href={`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}&inboxIssueId=${issue.id}`} - onClick={(e) => handleIssueRedirection(e, issue.id)} + onClick={(e) => { + handleIssueRedirection(e, issue.id); + captureEvent(INBOX_ISSUE_OPENED, { + issueId: issue.id, + }); + }} >
= observer((props) => { fetchInboxPaginationIssues, getAppliedFiltersCount, } = useProjectInbox(); + const {captureEvent} = useEventTracker(); const router = useRouter(); @@ -78,6 +80,9 @@ export const InboxSidebar: FC = observer((props) => { onClick={() => { if (currentTab != option?.key) handleCurrentTab(option?.key); router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${option?.key}`); + captureEvent(INBOX_TAB_CHANGED, { + tab: option?.key, + }); }} >
{option?.label}
diff --git a/web/components/issues/issue-detail/issue-activity/root.tsx b/web/components/issues/issue-detail/issue-activity/root.tsx index cf1481b10..42453405e 100644 --- a/web/components/issues/issue-detail/issue-activity/root.tsx +++ b/web/components/issues/issue-detail/issue-activity/root.tsx @@ -9,7 +9,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { IssueActivityCommentRoot, IssueCommentRoot, IssueCommentCreate } from "@/components/issues"; // constants -import { COMMENT_CREATED, COMMENT_DELETED, COMMENT_UPDATED } from "@/constants/event-tracker"; +import { COMMENT_CREATED, COMMENT_DELETED, COMMENT_UPDATED, E_INBOX, E_ISSUE_DETAILS } from "@/constants/event-tracker"; // hooks import { useIssueDetail, useProject, useEventTracker } from "@/hooks/store"; @@ -45,7 +45,7 @@ export const IssueActivity: FC = observer((props) => { const { workspaceSlug, projectId, issueId, disabled = false } = props; // router const router = useRouter(); - const { inboxId } = router.query; + const { inboxIssueId } = router.query; // hooks const { createComment, updateComment, removeComment } = useIssueDetail(); const { captureEvent } = useEventTracker(); @@ -59,11 +59,12 @@ export const IssueActivity: FC = observer((props) => { createComment: async (data: Partial) => { try { if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields"); - await createComment(workspaceSlug, projectId, issueId, data); + const res = await createComment(workspaceSlug, projectId, issueId, data); captureEvent(COMMENT_CREATED, { issue_id: issueId, + comment_id: res?.id, is_public: data.access === "EXTERNAL", - element: peekIssue ? "Peek issue" : inboxId ? "Inbox issue" : "Issue detail", + element: peekIssue ? "Peek issue" : inboxIssueId ? E_INBOX : E_ISSUE_DETAILS, }); setToast({ title: "Comment created successfully.", @@ -84,8 +85,9 @@ export const IssueActivity: FC = observer((props) => { await updateComment(workspaceSlug, projectId, issueId, commentId, data); captureEvent(COMMENT_UPDATED, { issue_id: issueId, + comment_id: commentId, is_public: data.access === "EXTERNAL", - element: peekIssue ? "Peek issue" : inboxId ? "Inbox issue" : "Issue detail", + element: peekIssue ? "Peek issue" : inboxIssueId ? E_INBOX : E_ISSUE_DETAILS, }); setToast({ title: "Comment updated successfully.", @@ -106,7 +108,8 @@ export const IssueActivity: FC = observer((props) => { await removeComment(workspaceSlug, projectId, issueId, commentId); captureEvent(COMMENT_DELETED, { issue_id: issueId, - element: peekIssue ? "Peek issue" : inboxId ? "Inbox issue" : "Issue detail", + comment_id: commentId, + element: peekIssue ? "Peek issue" : inboxIssueId ? E_INBOX : E_ISSUE_DETAILS, }); setToast({ title: "Comment removed successfully.", diff --git a/web/constants/event-tracker.ts b/web/constants/event-tracker.ts index 22fed506d..0c7a6ad33 100644 --- a/web/constants/event-tracker.ts +++ b/web/constants/event-tracker.ts @@ -98,7 +98,8 @@ export const getIssueEventPayload = (props: IssueEventProps) => { module_id: payload.module_id, archived_at: payload.archived_at, state: payload.state, - view_id: routePath?.includes("workspace-views") || routePath?.includes("views") ? routePath.split("/").pop() : "", + view_id: + routePath?.includes("workspace-views") || routePath?.includes("views") ? routePath.split("/").pop() : undefined, }; if (eventName === ISSUE_UPDATED) { @@ -214,6 +215,15 @@ export const ISSUE_UPDATED = "Issue updated"; export const ISSUE_DELETED = "Issue deleted"; export const ISSUE_ARCHIVED = "Issue archived"; export const ISSUE_RESTORED = "Issue restored"; +// Inbox Events +export const INBOX_ISSUE_CREATED = "Inbox issue created"; +export const INBOX_ISSUE_UPDATED = "Inbox issue updated"; +export const INBOX_ISSUE_DELETED = "Inbox issue deleted"; +export const INBOX_FILTERS_APPLIED = "Inbox filters applied"; +export const INBOX_FILTERS_REMOVED = "Inbox filters removed"; +export const INBOX_SORT_UPDATED= "Inbox sort updated"; +export const INBOX_ISSUE_OPENED = "Inbox issue opened"; +export const INBOX_TAB_CHANGED = "Inbox tab changed"; // Comment Events export const COMMENT_CREATED = "Comment created"; export const COMMENT_UPDATED = "Comment updated";