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 { BreadcrumbLink } from "@/components/common";
|
||||||
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
||||||
import { ProjectLogo } from "@/components/project";
|
import { ProjectLogo } from "@/components/project";
|
||||||
|
// constants
|
||||||
|
import { E_INBOX } from "@/constants/event-tracker";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useProjectInbox } from "@/hooks/store";
|
import { useEventTracker, useProject, useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
export const ProjectInboxHeader: FC = observer(() => {
|
export const ProjectInboxHeader: FC = observer(() => {
|
||||||
// states
|
// states
|
||||||
@ -20,6 +22,7 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { loader } = useProjectInbox();
|
const { loader } = useProjectInbox();
|
||||||
|
const { setTrackElement } = useEventTracker();
|
||||||
|
|
||||||
return (
|
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">
|
<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}
|
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
|
Add Issue
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,12 +25,13 @@ import {
|
|||||||
} from "@/components/inbox";
|
} from "@/components/inbox";
|
||||||
import { IssueUpdateStatus } from "@/components/issues";
|
import { IssueUpdateStatus } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
|
import { INBOX_ISSUE_DELETED, INBOX_ISSUE_UPDATED } from "@/constants/event-tracker";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
// helpers
|
// helpers
|
||||||
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
||||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser, useProjectInbox, useProject } from "@/hooks/store";
|
import { useUser, useProjectInbox, useProject, useEventTracker } from "@/hooks/store";
|
||||||
// store types
|
// store types
|
||||||
import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
const issue = inboxIssue?.issue;
|
const issue = inboxIssue?.issue;
|
||||||
// derived values
|
// derived values
|
||||||
@ -98,6 +100,10 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED);
|
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED);
|
||||||
setAcceptIssueModal(false);
|
setAcceptIssueModal(false);
|
||||||
handleRedirection(nextOrPreviousIssueId);
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
|
issue_status: "accepted",
|
||||||
|
issue_id: currentInboxIssueId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxIssueDecline = async () => {
|
const handleInboxIssueDecline = async () => {
|
||||||
@ -105,6 +111,10 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED);
|
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED);
|
||||||
setDeclineIssueModal(false);
|
setDeclineIssueModal(false);
|
||||||
handleRedirection(nextOrPreviousIssueId);
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
|
issue_status: "declined",
|
||||||
|
issue_id: currentInboxIssueId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxSIssueSnooze = async (date: Date) => {
|
const handleInboxSIssueSnooze = async (date: Date) => {
|
||||||
@ -112,14 +122,25 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
await inboxIssue?.updateInboxIssueSnoozeTill(date);
|
await inboxIssue?.updateInboxIssueSnoozeTill(date);
|
||||||
setIsSnoozeDateModalOpen(false);
|
setIsSnoozeDateModalOpen(false);
|
||||||
handleRedirection(nextOrPreviousIssueId);
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
|
issue_status: "snoozed",
|
||||||
|
issue_id: currentInboxIssueId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxIssueDuplicate = async (issueId: string) => {
|
const handleInboxIssueDuplicate = async (issueId: string) => {
|
||||||
await inboxIssue?.updateInboxIssueDuplicateTo(issueId);
|
await inboxIssue?.updateInboxIssueDuplicateTo(issueId);
|
||||||
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
|
issue_status: "mark as duplicate",
|
||||||
|
issue_id: currentInboxIssueId,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxIssueDelete = async () => {
|
const handleInboxIssueDelete = async () => {
|
||||||
if (!inboxIssue || !currentInboxIssueId) return;
|
if (!inboxIssue || !currentInboxIssueId) return;
|
||||||
|
captureEvent(INBOX_ISSUE_DELETED, {
|
||||||
|
issue_id: currentInboxIssueId,
|
||||||
|
});
|
||||||
await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => {
|
await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => {
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/inbox`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/inbox`);
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,8 @@ import {
|
|||||||
TIssueOperations,
|
TIssueOperations,
|
||||||
IssueAttachmentRoot,
|
IssueAttachmentRoot,
|
||||||
} from "@/components/issues";
|
} from "@/components/issues";
|
||||||
|
// constants
|
||||||
|
import { E_INBOX, INBOX_ISSUE_UPDATED } from "@/constants/event-tracker";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProjectInbox, useUser } from "@/hooks/store";
|
import { useEventTracker, useProjectInbox, useUser } from "@/hooks/store";
|
||||||
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
||||||
@ -36,7 +38,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
// hooks
|
// hooks
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
const { loader } = useProjectInbox();
|
const { loader } = useProjectInbox();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -82,14 +84,12 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||||
try {
|
try {
|
||||||
await inboxIssue.updateIssue(data);
|
await inboxIssue.updateIssue(data);
|
||||||
captureIssueEvent({
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
eventName: "Inbox issue updated",
|
...data,
|
||||||
payload: { ...data, state: "SUCCESS", element: "Inbox" },
|
changed_property: Object.keys(data).join(","),
|
||||||
updates: {
|
change_details: Object.values(data).join(","),
|
||||||
changed_property: Object.keys(data).join(","),
|
element: E_INBOX,
|
||||||
change_details: Object.values(data).join(","),
|
state: "SUCCESS",
|
||||||
},
|
|
||||||
routePath: router.asPath,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
@ -97,14 +97,12 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: "Issue update failed",
|
message: "Issue update failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureEvent(INBOX_ISSUE_UPDATED, {
|
||||||
eventName: "Inbox issue updated",
|
...data,
|
||||||
payload: { state: "SUCCESS", element: "Inbox" },
|
changed_property: Object.keys(data).join(","),
|
||||||
updates: {
|
change_details: Object.values(data).join(","),
|
||||||
changed_property: Object.keys(data).join(","),
|
element: E_INBOX,
|
||||||
change_details: Object.values(data).join(","),
|
state: "FAILED",
|
||||||
},
|
|
||||||
routePath: router.asPath,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,19 +2,26 @@ import { FC, useState } from "react";
|
|||||||
import concat from "lodash/concat";
|
import concat from "lodash/concat";
|
||||||
import uniq from "lodash/uniq";
|
import uniq from "lodash/uniq";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { TInboxIssueFilterDateKeys } from "@plane/types";
|
import { TInboxIssueFilter, TInboxIssueFilterDateKeys } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { DateFilterModal } from "@/components/core";
|
import { DateFilterModal } from "@/components/core";
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper";
|
import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
import { useEventTracker } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filterKey: TInboxIssueFilterDateKeys;
|
filterKey: TInboxIssueFilterDateKeys;
|
||||||
label?: string;
|
label?: string;
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
|
inboxFilters: Partial<TInboxIssueFilter>;
|
||||||
|
handleFilterUpdate: (
|
||||||
|
filterKey: keyof TInboxIssueFilter,
|
||||||
|
filterValue: TInboxIssueFilter[keyof TInboxIssueFilter],
|
||||||
|
isSelected: boolean,
|
||||||
|
interactedValue: string
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDate = (date: string) => {
|
const isDate = (date: string) => {
|
||||||
@ -23,9 +30,7 @@ const isDate = (date: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FilterDate: FC<Props> = observer((props) => {
|
export const FilterDate: FC<Props> = observer((props) => {
|
||||||
const { filterKey, label, searchQuery } = props;
|
const { filterKey, label, searchQuery, inboxFilters, handleFilterUpdate } = props;
|
||||||
// hooks
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
|
||||||
// state
|
// state
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
||||||
@ -46,7 +51,7 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
const handleCustomDate = () => {
|
const handleCustomDate = () => {
|
||||||
if (isCustomDateSelected()) {
|
if (isCustomDateSelected()) {
|
||||||
const updateAppliedFilters = filterValue?.filter((f) => !isDate(f.split(";")[0])) || [];
|
const updateAppliedFilters = filterValue?.filter((f) => !isDate(f.split(";")[0])) || [];
|
||||||
handleInboxIssueFilters(filterKey, updateAppliedFilters);
|
handleFilterUpdate(filterKey, updateAppliedFilters, true, "Custom");
|
||||||
} else {
|
} else {
|
||||||
setIsDateFilterModalOpen(true);
|
setIsDateFilterModalOpen(true);
|
||||||
}
|
}
|
||||||
@ -58,7 +63,7 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
<DateFilterModal
|
<DateFilterModal
|
||||||
handleClose={() => setIsDateFilterModalOpen(false)}
|
handleClose={() => setIsDateFilterModalOpen(false)}
|
||||||
isOpen={isDateFilterModalOpen}
|
isOpen={isDateFilterModalOpen}
|
||||||
onSelect={(val) => handleInboxIssueFilters(filterKey, val)}
|
onSelect={(val) => handleFilterUpdate(filterKey, val, false, "Custom")}
|
||||||
title="Created date"
|
title="Created date"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -75,7 +80,14 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={option.value}
|
key={option.value}
|
||||||
isChecked={filterValue?.includes(option.value) ? true : false}
|
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}
|
title={option.name}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Search, X } from "lucide-react";
|
import { Search, X } from "lucide-react";
|
||||||
|
import { TInboxIssueFilter } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
FilterStatus,
|
FilterStatus,
|
||||||
@ -10,8 +11,11 @@ import {
|
|||||||
FilterLabels,
|
FilterLabels,
|
||||||
FilterState,
|
FilterState,
|
||||||
} from "@/components/inbox/inbox-filter/filters";
|
} 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
|
// hooks
|
||||||
import { useMember, useLabel, useProjectState } from "@/hooks/store";
|
import { useMember, useLabel, useProjectState, useProjectInbox, useEventTracker } from "@/hooks/store";
|
||||||
|
|
||||||
export const InboxIssueFilterSelection: FC = observer(() => {
|
export const InboxIssueFilterSelection: FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
@ -20,9 +24,31 @@ export const InboxIssueFilterSelection: FC = observer(() => {
|
|||||||
} = useMember();
|
} = useMember();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
const { projectStates } = useProjectState();
|
const { projectStates } = useProjectState();
|
||||||
|
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// states
|
// states
|
||||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
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 (
|
return (
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||||
<div className="bg-custom-background-100 p-2.5 pb-0">
|
<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">
|
<div className="h-full w-full divide-y divide-custom-border-200 overflow-y-auto px-2.5 vertical-scrollbar scrollbar-sm">
|
||||||
{/* status */}
|
{/* status */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterStatus searchQuery={filtersSearchQuery} />
|
<FilterStatus
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
|
searchQuery={filtersSearchQuery}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* state */}
|
{/* state */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterState states={projectStates} searchQuery={filtersSearchQuery} />
|
<FilterState
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
|
states={projectStates}
|
||||||
|
searchQuery={filtersSearchQuery}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Priority */}
|
{/* Priority */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterPriority searchQuery={filtersSearchQuery} />
|
<FilterPriority
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
|
searchQuery={filtersSearchQuery}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* assignees */}
|
{/* assignees */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterMember
|
<FilterMember
|
||||||
filterKey="assignee"
|
filterKey="assignee"
|
||||||
label="Assignee"
|
label="Assignee"
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
searchQuery={filtersSearchQuery}
|
searchQuery={filtersSearchQuery}
|
||||||
memberIds={projectMemberIds ?? []}
|
memberIds={projectMemberIds ?? []}
|
||||||
/>
|
/>
|
||||||
@ -71,21 +112,40 @@ export const InboxIssueFilterSelection: FC = observer(() => {
|
|||||||
<FilterMember
|
<FilterMember
|
||||||
filterKey="created_by"
|
filterKey="created_by"
|
||||||
label="Created By"
|
label="Created By"
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
searchQuery={filtersSearchQuery}
|
searchQuery={filtersSearchQuery}
|
||||||
memberIds={projectMemberIds ?? []}
|
memberIds={projectMemberIds ?? []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Labels */}
|
{/* Labels */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterLabels searchQuery={filtersSearchQuery} labels={projectLabels ?? []} />
|
<FilterLabels
|
||||||
|
inboxFilters={inboxFilters}
|
||||||
|
handleFilterUpdate={handleFilterUpdate}
|
||||||
|
searchQuery={filtersSearchQuery}
|
||||||
|
labels={projectLabels ?? []}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Created at */}
|
{/* Created at */}
|
||||||
<div className="py-2">
|
<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>
|
</div>
|
||||||
{/* Updated at */}
|
{/* Updated at */}
|
||||||
<div className="py-2">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { IIssueLabel } from "@plane/types";
|
import { IIssueLabel, TInboxIssueFilter } from "@plane/types";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// hooks
|
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
|
||||||
|
|
||||||
const LabelIcons = ({ color }: { color: string }) => (
|
const LabelIcons = ({ color }: { color: string }) => (
|
||||||
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
|
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
|
||||||
@ -14,16 +12,21 @@ const LabelIcons = ({ color }: { color: string }) => (
|
|||||||
type Props = {
|
type Props = {
|
||||||
labels: IIssueLabel[] | undefined;
|
labels: IIssueLabel[] | undefined;
|
||||||
searchQuery: string;
|
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) => {
|
export const FilterLabels: FC<Props> = observer((props) => {
|
||||||
const { labels, searchQuery } = props;
|
const { labels, searchQuery, inboxFilters, handleFilterUpdate } = props;
|
||||||
|
|
||||||
const [itemsToRender, setItemsToRender] = useState(5);
|
const [itemsToRender, setItemsToRender] = useState(5);
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
|
||||||
|
|
||||||
const filterValue = inboxFilters?.labels || [];
|
const filterValue = inboxFilters?.labels || [];
|
||||||
|
|
||||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||||
@ -56,7 +59,14 @@ export const FilterLabels: FC<Props> = observer((props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={label?.id}
|
key={label?.id}
|
||||||
isChecked={filterValue?.includes(label?.id) ? true : false}
|
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} />}
|
icon={<LabelIcons color={label.color} />}
|
||||||
title={label.name}
|
title={label.name}
|
||||||
/>
|
/>
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
import { FC, useMemo, useState } from "react";
|
import { FC, useMemo, useState } from "react";
|
||||||
import sortBy from "lodash/sortBy";
|
import sortBy from "lodash/sortBy";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { TInboxIssueFilterMemberKeys } from "@plane/types";
|
import { TInboxIssueFilter, TInboxIssueFilterMemberKeys } from "@plane/types";
|
||||||
import { Avatar, Loader } from "@plane/ui";
|
import { Avatar, Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember, useProjectInbox, useUser } from "@/hooks/store";
|
import { useEventTracker, useMember, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filterKey: TInboxIssueFilterMemberKeys;
|
filterKey: TInboxIssueFilterMemberKeys;
|
||||||
label?: string;
|
label?: string;
|
||||||
memberIds: string[] | undefined;
|
memberIds: string[] | undefined;
|
||||||
searchQuery: string;
|
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) => {
|
export const FilterMember: FC<Props> = observer((props: Props) => {
|
||||||
const { filterKey, label = "Members", memberIds, searchQuery } = props;
|
const { filterKey, label = "Members", memberIds, searchQuery, inboxFilters, handleFilterUpdate } = props;
|
||||||
// hooks
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
// states
|
// states
|
||||||
@ -71,7 +76,14 @@ export const FilterMember: FC<Props> = observer((props: Props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={`members-${member.id}`}
|
key={`members-${member.id}`}
|
||||||
isChecked={filterValue?.includes(member.id) ? true : false}
|
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" />}
|
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||||
/>
|
/>
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { TIssuePriorities } from "@plane/types";
|
import { TInboxIssueFilter, TIssuePriorities } from "@plane/types";
|
||||||
import { PriorityIcon } from "@plane/ui";
|
import { PriorityIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_PRIORITIES } from "@/constants/issue";
|
import { ISSUE_PRIORITIES } from "@/constants/issue";
|
||||||
// hooks
|
|
||||||
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
searchQuery: string;
|
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) => {
|
export const FilterPriority: FC<Props> = observer((props) => {
|
||||||
const { searchQuery } = props;
|
const { searchQuery, inboxFilters, handleFilterUpdate } = props;
|
||||||
// hooks
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
|
||||||
// states
|
// states
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
// derived values
|
// derived values
|
||||||
@ -41,7 +44,14 @@ export const FilterPriority: FC<Props> = observer((props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={priority.key}
|
key={priority.key}
|
||||||
isChecked={filterValue?.includes(priority.key) ? true : false}
|
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" />}
|
icon={<PriorityIcon priority={priority.key} className="h-3.5 w-3.5" />}
|
||||||
title={priority.title}
|
title={priority.title}
|
||||||
/>
|
/>
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { IState } from "@plane/types";
|
import { IState, TInboxIssueFilter } from "@plane/types";
|
||||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// hooks
|
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
states: IState[] | undefined;
|
states: IState[] | undefined;
|
||||||
searchQuery: string;
|
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) => {
|
export const FilterState: FC<Props> = observer((props) => {
|
||||||
const { states, searchQuery } = props;
|
const { states, inboxFilters, searchQuery, handleFilterUpdate } = props;
|
||||||
|
|
||||||
const [itemsToRender, setItemsToRender] = useState(5);
|
const [itemsToRender, setItemsToRender] = useState(5);
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
|
||||||
|
|
||||||
const filterValue = inboxFilters?.state || [];
|
const filterValue = inboxFilters?.state || [];
|
||||||
|
|
||||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||||
@ -52,7 +55,14 @@ export const FilterState: FC<Props> = observer((props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={state?.id}
|
key={state?.id}
|
||||||
isChecked={filterValue?.includes(state?.id) ? true : false}
|
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" />}
|
icon={<StateGroupIcon color={state.color} stateGroup={state.group} height="12px" width="12px" />}
|
||||||
title={state.name}
|
title={state.name}
|
||||||
/>
|
/>
|
||||||
|
@ -1,22 +1,29 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// types
|
// types
|
||||||
import { TInboxIssueStatus } from "@plane/types";
|
import { TInboxIssueFilter, TInboxIssueFilterMemberKeys, TInboxIssueStatus } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
import { INBOX_STATUS } from "@/constants/inbox";
|
import { INBOX_STATUS } from "@/constants/inbox";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
|
import { useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
searchQuery: string;
|
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) => {
|
export const FilterStatus: FC<Props> = observer((props) => {
|
||||||
const { searchQuery } = props;
|
const { searchQuery, inboxFilters, handleFilterUpdate } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { currentTab, inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
const { currentTab } = useProjectInbox();
|
||||||
// states
|
// states
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
// derived values
|
// derived values
|
||||||
@ -34,7 +41,13 @@ export const FilterStatus: FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const handleStatusFilterSelect = (status: TInboxIssueStatus) => {
|
const handleStatusFilterSelect = (status: TInboxIssueStatus) => {
|
||||||
const selectedStatus = handleFilterValue(status);
|
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 (
|
return (
|
||||||
|
@ -1,20 +1,35 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ArrowDownWideNarrow, ArrowUpWideNarrow, Check, ChevronDown } from "lucide-react";
|
import { ArrowDownWideNarrow, ArrowUpWideNarrow, Check, ChevronDown } from "lucide-react";
|
||||||
|
import { TInboxIssueSorting } from "@plane/types";
|
||||||
import { CustomMenu, getButtonStyling } from "@plane/ui";
|
import { CustomMenu, getButtonStyling } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
|
import { INBOX_SORT_UPDATED } from "@/constants/event-tracker";
|
||||||
import { INBOX_ISSUE_ORDER_BY_OPTIONS, INBOX_ISSUE_SORT_BY_OPTIONS } from "@/constants/inbox";
|
import { INBOX_ISSUE_ORDER_BY_OPTIONS, INBOX_ISSUE_SORT_BY_OPTIONS } from "@/constants/inbox";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
import { useProjectInbox, useEventTracker } from "@/hooks/store";
|
||||||
|
|
||||||
export const InboxIssueOrderByDropdown: FC = observer(() => {
|
export const InboxIssueOrderByDropdown: FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { inboxSorting, handleInboxIssueSorting } = useProjectInbox();
|
const { inboxSorting, handleInboxIssueSorting } = useProjectInbox();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
const orderByDetails =
|
const orderByDetails =
|
||||||
INBOX_ISSUE_ORDER_BY_OPTIONS.find((option) => inboxSorting?.order_by?.includes(option.key)) || undefined;
|
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 (
|
return (
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
customButton={
|
customButton={
|
||||||
@ -36,7 +51,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
|
|||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
key={option.key}
|
key={option.key}
|
||||||
className="flex items-center justify-between gap-2"
|
className="flex items-center justify-between gap-2"
|
||||||
onClick={() => handleInboxIssueSorting("order_by", option.key)}
|
onClick={() => handleIssueSorting("order_by", option.key)}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
{inboxSorting?.order_by?.includes(option.key) && <Check className="h-3 w-3" />}
|
{inboxSorting?.order_by?.includes(option.key) && <Check className="h-3 w-3" />}
|
||||||
@ -47,7 +62,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
|
|||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
key={option.key}
|
key={option.key}
|
||||||
className="flex items-center justify-between gap-2"
|
className="flex items-center justify-between gap-2"
|
||||||
onClick={() => handleInboxIssueSorting("sort_by", option.key)}
|
onClick={() => handleIssueSorting("sort_by", option.key)}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
{inboxSorting?.sort_by?.includes(option.key) && <Check className="h-3 w-3" />}
|
{inboxSorting?.sort_by?.includes(option.key) && <Check className="h-3 w-3" />}
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
InboxIssueProperties,
|
InboxIssueProperties,
|
||||||
} from "@/components/inbox/modals/create-edit-modal";
|
} from "@/components/inbox/modals/create-edit-modal";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_CREATED } from "@/constants/event-tracker";
|
import { INBOX_ISSUE_CREATED, getIssueEventPayload } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
@ -41,7 +41,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||||||
// refs
|
// refs
|
||||||
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
const { createInboxIssue } = useProjectInbox();
|
const { createInboxIssue } = useProjectInbox();
|
||||||
const { getWorkspaceBySlug } = useWorkspace();
|
const { getWorkspaceBySlug } = useWorkspace();
|
||||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||||
@ -81,14 +81,13 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||||||
descriptionEditorRef?.current?.clearEditor();
|
descriptionEditorRef?.current?.clearEditor();
|
||||||
setFormData(defaultIssueData);
|
setFormData(defaultIssueData);
|
||||||
}
|
}
|
||||||
captureIssueEvent({
|
captureEvent(INBOX_ISSUE_CREATED, {
|
||||||
eventName: ISSUE_CREATED,
|
issue_id: res?.issue?.id,
|
||||||
payload: {
|
properties: getIssueEventPayload({
|
||||||
...formData,
|
eventName: INBOX_ISSUE_CREATED,
|
||||||
state: "SUCCESS",
|
payload: res?.issue,
|
||||||
element: "Inbox page",
|
}),
|
||||||
},
|
state: "SUCCESS",
|
||||||
routePath: router.pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
@ -98,14 +97,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureIssueEvent({
|
captureEvent(INBOX_ISSUE_CREATED, {
|
||||||
eventName: ISSUE_CREATED,
|
properties: getIssueEventPayload({
|
||||||
payload: {
|
eventName: INBOX_ISSUE_CREATED,
|
||||||
...formData,
|
payload: formData,
|
||||||
state: "FAILED",
|
}),
|
||||||
element: "Inbox page",
|
state: "FAILED",
|
||||||
},
|
|
||||||
routePath: router.pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
@ -5,11 +5,13 @@ import { useRouter } from "next/router";
|
|||||||
import { Tooltip, PriorityIcon } from "@plane/ui";
|
import { Tooltip, PriorityIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { InboxIssueStatus } from "@/components/inbox";
|
import { InboxIssueStatus } from "@/components/inbox";
|
||||||
|
// constants
|
||||||
|
import { INBOX_ISSUE_OPENED } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useLabel, useProjectInbox } from "@/hooks/store";
|
import { useEventTracker, useLabel, useProjectInbox } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// store
|
// store
|
||||||
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
||||||
@ -23,7 +25,7 @@ type InboxIssueListItemProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
|
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, inboxIssue, projectIdentifier,setToggleMobileSidebar } = props;
|
const { workspaceSlug, projectId, inboxIssue, projectIdentifier, setToggleMobileSidebar } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { inboxIssueId } = router.query;
|
const { inboxIssueId } = router.query;
|
||||||
@ -31,6 +33,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
|
|||||||
const { currentTab } = useProjectInbox();
|
const { currentTab } = useProjectInbox();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
const issue = inboxIssue.issue;
|
const issue = inboxIssue.issue;
|
||||||
|
|
||||||
const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => {
|
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}`}
|
id={`inbox-issue-list-item-${issue.id}`}
|
||||||
key={`${projectId}_${issue.id}`}
|
key={`${projectId}_${issue.id}`}
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}&inboxIssueId=${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
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
@ -9,11 +9,12 @@ import { FiltersRoot, InboxIssueAppliedFilters, InboxIssueList } from "@/compone
|
|||||||
import { InboxSidebarLoader } from "@/components/ui";
|
import { InboxSidebarLoader } from "@/components/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
import { EmptyStateType } from "@/constants/empty-state";
|
||||||
|
import { INBOX_TAB_CHANGED } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useProjectInbox } from "@/hooks/store";
|
import { useEventTracker, useProject, useProjectInbox } from "@/hooks/store";
|
||||||
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
||||||
|
|
||||||
type IInboxSidebarProps = {
|
type IInboxSidebarProps = {
|
||||||
@ -49,6 +50,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
fetchInboxPaginationIssues,
|
fetchInboxPaginationIssues,
|
||||||
getAppliedFiltersCount,
|
getAppliedFiltersCount,
|
||||||
} = useProjectInbox();
|
} = useProjectInbox();
|
||||||
|
const {captureEvent} = useEventTracker();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -78,6 +80,9 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentTab != option?.key) handleCurrentTab(option?.key);
|
if (currentTab != option?.key) handleCurrentTab(option?.key);
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${option?.key}`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${option?.key}`);
|
||||||
|
captureEvent(INBOX_TAB_CHANGED, {
|
||||||
|
tab: option?.key,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{option?.label}</div>
|
<div>{option?.label}</div>
|
||||||
|
@ -9,7 +9,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { IssueActivityCommentRoot, IssueCommentRoot, IssueCommentCreate } from "@/components/issues";
|
import { IssueActivityCommentRoot, IssueCommentRoot, IssueCommentCreate } from "@/components/issues";
|
||||||
// constants
|
// 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
|
// hooks
|
||||||
import { useIssueDetail, useProject, useEventTracker } from "@/hooks/store";
|
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;
|
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { inboxId } = router.query;
|
const { inboxIssueId } = router.query;
|
||||||
// hooks
|
// hooks
|
||||||
const { createComment, updateComment, removeComment } = useIssueDetail();
|
const { createComment, updateComment, removeComment } = useIssueDetail();
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
@ -59,11 +59,12 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
|||||||
createComment: async (data: Partial<TIssueComment>) => {
|
createComment: async (data: Partial<TIssueComment>) => {
|
||||||
try {
|
try {
|
||||||
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
|
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, {
|
captureEvent(COMMENT_CREATED, {
|
||||||
issue_id: issueId,
|
issue_id: issueId,
|
||||||
|
comment_id: res?.id,
|
||||||
is_public: data.access === "EXTERNAL",
|
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({
|
setToast({
|
||||||
title: "Comment created successfully.",
|
title: "Comment created successfully.",
|
||||||
@ -84,8 +85,9 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
|||||||
await updateComment(workspaceSlug, projectId, issueId, commentId, data);
|
await updateComment(workspaceSlug, projectId, issueId, commentId, data);
|
||||||
captureEvent(COMMENT_UPDATED, {
|
captureEvent(COMMENT_UPDATED, {
|
||||||
issue_id: issueId,
|
issue_id: issueId,
|
||||||
|
comment_id: commentId,
|
||||||
is_public: data.access === "EXTERNAL",
|
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({
|
setToast({
|
||||||
title: "Comment updated successfully.",
|
title: "Comment updated successfully.",
|
||||||
@ -106,7 +108,8 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
|||||||
await removeComment(workspaceSlug, projectId, issueId, commentId);
|
await removeComment(workspaceSlug, projectId, issueId, commentId);
|
||||||
captureEvent(COMMENT_DELETED, {
|
captureEvent(COMMENT_DELETED, {
|
||||||
issue_id: issueId,
|
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({
|
setToast({
|
||||||
title: "Comment removed successfully.",
|
title: "Comment removed successfully.",
|
||||||
|
@ -98,7 +98,8 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
|||||||
module_id: payload.module_id,
|
module_id: payload.module_id,
|
||||||
archived_at: payload.archived_at,
|
archived_at: payload.archived_at,
|
||||||
state: payload.state,
|
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) {
|
if (eventName === ISSUE_UPDATED) {
|
||||||
@ -214,6 +215,15 @@ export const ISSUE_UPDATED = "Issue updated";
|
|||||||
export const ISSUE_DELETED = "Issue deleted";
|
export const ISSUE_DELETED = "Issue deleted";
|
||||||
export const ISSUE_ARCHIVED = "Issue archived";
|
export const ISSUE_ARCHIVED = "Issue archived";
|
||||||
export const ISSUE_RESTORED = "Issue restored";
|
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
|
// Comment Events
|
||||||
export const COMMENT_CREATED = "Comment created";
|
export const COMMENT_CREATED = "Comment created";
|
||||||
export const COMMENT_UPDATED = "Comment updated";
|
export const COMMENT_UPDATED = "Comment updated";
|
||||||
|
Loading…
Reference in New Issue
Block a user