mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: inbox events
This commit is contained in:
parent
a28cca29d5
commit
aab34ff36d
@ -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>
|
||||
|
@ -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`);
|
||||
});
|
||||
|
@ -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: {
|
||||
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<Props> = 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",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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 (
|
||||
|
@ -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" />}
|
||||
|
@ -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,
|
||||
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<TInboxIssueCreateRoot> = 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,
|
||||
|
@ -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<InboxIssueListItemProps> = 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<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(
|
||||
|
@ -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>
|
||||
|
@ -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.",
|
||||
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user