chore: inbox events

This commit is contained in:
LAKHAN BAHETI 2024-04-30 18:23:16 +05:30
parent a28cca29d5
commit aab34ff36d
16 changed files with 296 additions and 101 deletions

View File

@ -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 (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
@ -70,7 +73,15 @@ export const ProjectInboxHeader: FC = observer(() => {
issue={undefined}
/>
<Button variant="primary" prependIcon={<Plus />} size="sm" onClick={() => setCreateIssueModal(true)}>
<Button
variant="primary"
prependIcon={<Plus />}
size="sm"
onClick={() => {
setTrackElement(E_INBOX);
setCreateIssueModal(true);
}}
>
Add Issue
</Button>
</div>

View File

@ -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<TInboxIssueActionsHeader> = 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<TInboxIssueActionsHeader> = 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<TInboxIssueActionsHeader> = 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<TInboxIssueActionsHeader> = 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`);
});

View File

@ -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<Props> = 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<Props> = observer((props) => {
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
try {
await inboxIssue.updateIssue(data);
captureIssueEvent({
eventName: "Inbox issue updated",
payload: { ...data, state: "SUCCESS", element: "Inbox" },
updates: {
captureEvent(INBOX_ISSUE_UPDATED, {
...data,
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
routePath: router.asPath,
element: E_INBOX,
state: "SUCCESS",
});
} catch (error) {
setToast({
@ -97,14 +97,12 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
});
captureIssueEvent({
eventName: "Inbox issue updated",
payload: { state: "SUCCESS", element: "Inbox" },
updates: {
captureEvent(INBOX_ISSUE_UPDATED, {
...data,
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
routePath: router.asPath,
element: E_INBOX,
state: "FAILED",
});
}
},

View File

@ -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<TInboxIssueFilter>;
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<Props> = 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<Props> = 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<Props> = observer((props) => {
<DateFilterModal
handleClose={() => 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<Props> = observer((props) => {
<FilterOption
key={option.value}
isChecked={filterValue?.includes(option.value) ? true : false}
onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(option.value))}
onClick={() =>
handleFilterUpdate(
filterKey,
handleFilterValue(option.value),
filterValue?.includes(option.value),
option.name
)
}
title={option.name}
multiple={false}
/>

View File

@ -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 (
<div className="flex h-full w-full flex-col overflow-hidden">
<div className="bg-custom-background-100 p-2.5 pb-0">
@ -47,21 +73,36 @@ export const InboxIssueFilterSelection: FC = observer(() => {
<div className="h-full w-full divide-y divide-custom-border-200 overflow-y-auto px-2.5 vertical-scrollbar scrollbar-sm">
{/* status */}
<div className="py-2">
<FilterStatus searchQuery={filtersSearchQuery} />
<FilterStatus
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
searchQuery={filtersSearchQuery}
/>
</div>
{/* state */}
<div className="py-2">
<FilterState states={projectStates} searchQuery={filtersSearchQuery} />
<FilterState
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
states={projectStates}
searchQuery={filtersSearchQuery}
/>
</div>
{/* Priority */}
<div className="py-2">
<FilterPriority searchQuery={filtersSearchQuery} />
<FilterPriority
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
searchQuery={filtersSearchQuery}
/>
</div>
{/* assignees */}
<div className="py-2">
<FilterMember
filterKey="assignee"
label="Assignee"
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
searchQuery={filtersSearchQuery}
memberIds={projectMemberIds ?? []}
/>
@ -71,21 +112,40 @@ export const InboxIssueFilterSelection: FC = observer(() => {
<FilterMember
filterKey="created_by"
label="Created By"
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
searchQuery={filtersSearchQuery}
memberIds={projectMemberIds ?? []}
/>
</div>
{/* Labels */}
<div className="py-2">
<FilterLabels searchQuery={filtersSearchQuery} labels={projectLabels ?? []} />
<FilterLabels
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
searchQuery={filtersSearchQuery}
labels={projectLabels ?? []}
/>
</div>
{/* Created at */}
<div className="py-2">
<FilterDate filterKey="created_at" label="Created date" searchQuery={filtersSearchQuery} />
<FilterDate
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
filterKey="created_at"
label="Created date"
searchQuery={filtersSearchQuery}
/>
</div>
{/* Updated at */}
<div className="py-2">
<FilterDate filterKey="updated_at" label="Last updated date" searchQuery={filtersSearchQuery} />
<FilterDate
inboxFilters={inboxFilters}
handleFilterUpdate={handleFilterUpdate}
filterKey="updated_at"
label="Last updated date"
searchQuery={filtersSearchQuery}
/>
</div>
</div>
</div>

View File

@ -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 }) => (
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
@ -14,16 +12,21 @@ const LabelIcons = ({ color }: { color: string }) => (
type Props = {
labels: IIssueLabel[] | undefined;
searchQuery: string;
inboxFilters: Partial<TInboxIssueFilter>;
handleFilterUpdate: (
filterKey: keyof TInboxIssueFilter,
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
isSelected: boolean,
interactedValue: string
) => void;
};
export const FilterLabels: FC<Props> = 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<Props> = observer((props) => {
<FilterOption
key={label?.id}
isChecked={filterValue?.includes(label?.id) ? true : false}
onClick={() => handleInboxIssueFilters("labels", handleFilterValue(label.id))}
onClick={() =>
handleFilterUpdate(
"labels",
handleFilterValue(label?.id),
filterValue?.includes(label?.id),
label?.id
)
}
icon={<LabelIcons color={label.color} />}
title={label.name}
/>

View File

@ -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<TInboxIssueFilter>;
handleFilterUpdate: (
filterKey: keyof TInboxIssueFilter,
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
isSelected: boolean,
interactedValue: string
) => void;
};
export const FilterMember: FC<Props> = 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<Props> = observer((props: Props) => {
<FilterOption
key={`members-${member.id}`}
isChecked={filterValue?.includes(member.id) ? true : false}
onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(member.id))}
onClick={() =>
handleFilterUpdate(
filterKey,
handleFilterValue(member.id),
filterValue?.includes(member.id),
member.id
)
}
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
title={currentUser?.id === member.id ? "You" : member?.display_name}
/>

View File

@ -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<TInboxIssueFilter>;
handleFilterUpdate: (
filterKey: keyof TInboxIssueFilter,
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
isSelected: boolean,
interactedValue: string
) => void;
};
export const FilterPriority: FC<Props> = 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<Props> = observer((props) => {
<FilterOption
key={priority.key}
isChecked={filterValue?.includes(priority.key) ? true : false}
onClick={() => handleInboxIssueFilters("priority", handleFilterValue(priority.key))}
onClick={() =>
handleFilterUpdate(
"priority",
handleFilterValue(priority.key),
filterValue?.includes(priority.key),
priority.title
)
}
icon={<PriorityIcon priority={priority.key} className="h-3.5 w-3.5" />}
title={priority.title}
/>

View File

@ -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<TInboxIssueFilter>;
handleFilterUpdate: (
filterKey: keyof TInboxIssueFilter,
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
isSelected: boolean,
interactedValue: string
) => void;
};
export const FilterState: FC<Props> = 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<Props> = observer((props) => {
<FilterOption
key={state?.id}
isChecked={filterValue?.includes(state?.id) ? true : false}
onClick={() => handleInboxIssueFilters("state", handleFilterValue(state.id))}
onClick={() =>
handleFilterUpdate(
"state",
handleFilterValue(state.id),
filterValue?.includes(state.id),
state.id
)
}
icon={<StateGroupIcon color={state.color} stateGroup={state.group} height="12px" width="12px" />}
title={state.name}
/>

View File

@ -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<TInboxIssueFilter>;
handleFilterUpdate: (
filterKey: keyof TInboxIssueFilter,
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
isSelected: boolean,
interactedValue: string
) => void;
};
export const FilterStatus: FC<Props> = 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<Props> = 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 (

View File

@ -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 (
<CustomMenu
customButton={
@ -36,7 +51,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
<CustomMenu.MenuItem
key={option.key}
className="flex items-center justify-between gap-2"
onClick={() => handleInboxIssueSorting("order_by", option.key)}
onClick={() => handleIssueSorting("order_by", option.key)}
>
{option.label}
{inboxSorting?.order_by?.includes(option.key) && <Check className="h-3 w-3" />}
@ -47,7 +62,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
<CustomMenu.MenuItem
key={option.key}
className="flex items-center justify-between gap-2"
onClick={() => handleInboxIssueSorting("sort_by", option.key)}
onClick={() => handleIssueSorting("sort_by", option.key)}
>
{option.label}
{inboxSorting?.sort_by?.includes(option.key) && <Check className="h-3 w-3" />}

View File

@ -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<TInboxIssueCreateRoot> = observer((props)
// refs
const descriptionEditorRef = useRef<EditorRefApi>(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<TInboxIssueCreateRoot> = observer((props)
descriptionEditorRef?.current?.clearEditor();
setFormData(defaultIssueData);
}
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: {
...formData,
captureEvent(INBOX_ISSUE_CREATED, {
issue_id: res?.issue?.id,
properties: getIssueEventPayload({
eventName: INBOX_ISSUE_CREATED,
payload: res?.issue,
}),
state: "SUCCESS",
element: "Inbox page",
},
routePath: router.pathname,
});
setToast({
type: TOAST_TYPE.SUCCESS,
@ -98,14 +97,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
})
.catch((error) => {
console.error(error);
captureIssueEvent({
eventName: ISSUE_CREATED,
payload: {
...formData,
captureEvent(INBOX_ISSUE_CREATED, {
properties: getIssueEventPayload({
eventName: INBOX_ISSUE_CREATED,
payload: formData,
}),
state: "FAILED",
element: "Inbox page",
},
routePath: router.pathname,
});
setToast({
type: TOAST_TYPE.ERROR,

View File

@ -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";
@ -31,6 +33,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = 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<InboxIssueListItemProps> = 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,
});
}}
>
<div
className={cn(

View File

@ -9,11 +9,12 @@ import { FiltersRoot, InboxIssueAppliedFilters, InboxIssueList } from "@/compone
import { InboxSidebarLoader } from "@/components/ui";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { INBOX_TAB_CHANGED } from "@/constants/event-tracker";
// helpers
import { cn } from "@/helpers/common.helper";
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
// hooks
import { useProject, useProjectInbox } from "@/hooks/store";
import { useEventTracker, useProject, useProjectInbox } from "@/hooks/store";
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
type IInboxSidebarProps = {
@ -49,6 +50,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
fetchInboxPaginationIssues,
getAppliedFiltersCount,
} = useProjectInbox();
const {captureEvent} = useEventTracker();
const router = useRouter();
@ -78,6 +80,9 @@ export const InboxSidebar: FC<IInboxSidebarProps> = 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,
});
}}
>
<div>{option?.label}</div>

View File

@ -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<TIssueActivity> = 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<TIssueActivity> = observer((props) => {
createComment: async (data: Partial<TIssueComment>) => {
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<TIssueActivity> = 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<TIssueActivity> = 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.",

View File

@ -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";