added events for project-views

This commit is contained in:
LAKHAN BAHETI 2024-05-29 11:57:38 +05:30
parent c87749cbda
commit 02163f1e95
8 changed files with 122 additions and 30 deletions

View File

@ -9,7 +9,8 @@ import { ProjectLogo } from "@/components/project";
import { ViewListHeader } from "@/components/views"; import { ViewListHeader } from "@/components/views";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// constants // constants
import { useCommandPalette, useProject, useUser } from "@/hooks/store"; import { E_VIEWS } from "@/constants/event-tracker";
import { useCommandPalette, useProject, useUser, useEventTracker } from "@/hooks/store";
export const ProjectViewsHeader: React.FC = observer(() => { export const ProjectViewsHeader: React.FC = observer(() => {
// router // router
@ -21,6 +22,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { setTrackElement } = useEventTracker();
const canUserCreateIssue = const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
@ -60,7 +62,14 @@ export const ProjectViewsHeader: React.FC = observer(() => {
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<ViewListHeader /> <ViewListHeader />
<div> <div>
<Button variant="primary" size="sm" onClick={() => toggleCreateViewModal(true)}> <Button
variant="primary"
size="sm"
onClick={() => {
setTrackElement(E_VIEWS);
toggleCreateViewModal(true);
}}
>
Add View Add View
</Button> </Button>
</div> </div>

View File

@ -3,14 +3,14 @@ import isEqual from "lodash/isEqual";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { IIssueFilterOptions } from "@plane/types"; import { IIssueFilterOptions } from "@plane/types";
// hooks
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
import { AppliedFiltersList } from "@/components/issues";
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
import { useIssues, useLabel, useProjectState, useProjectView } from "@/hooks/store";
// components // components
// ui import { AppliedFiltersList } from "@/components/issues";
// types // constants
import { VIEW_UPDATED } from "constants/event-tracker";
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
// hooks
import { useIssues, useLabel, useProjectState, useProjectView, useEventTracker } from "@/hooks/store";
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
// router // router
@ -23,6 +23,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
const { projectLabels } = useLabel(); const { projectLabels } = useLabel();
const { projectStates } = useProjectState(); const { projectStates } = useProjectState();
const { viewMap, updateView } = useProjectView(); const { viewMap, updateView } = useProjectView();
const { captureEvent } = useEventTracker();
// derived values // derived values
const viewDetails = viewId ? viewMap[viewId.toString()] : null; const viewDetails = viewId ? viewMap[viewId.toString()] : null;
const userFilters = issueFilters?.filters; const userFilters = issueFilters?.filters;

View File

@ -7,8 +7,10 @@ import { IProjectView } from "@plane/types";
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { AlertModalCore } from "@/components/core"; import { AlertModalCore } from "@/components/core";
// constants
import { E_VIEWS, VIEW_DELETED } from "constants/event-tracker";
// hooks // hooks
import { useProjectView } from "@/hooks/store"; import { useProjectView, useEventTracker } from "@/hooks/store";
type Props = { type Props = {
data: IProjectView; data: IProjectView;
@ -25,6 +27,7 @@ export const DeleteProjectViewModal: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks // store hooks
const { deleteView } = useProjectView(); const { deleteView } = useProjectView();
const { captureEvent } = useEventTracker();
const handleClose = () => { const handleClose = () => {
onClose(); onClose();
@ -39,20 +42,29 @@ export const DeleteProjectViewModal: React.FC<Props> = observer((props) => {
await deleteView(workspaceSlug.toString(), projectId.toString(), data.id) await deleteView(workspaceSlug.toString(), projectId.toString(), data.id)
.then(() => { .then(() => {
handleClose(); handleClose();
captureEvent(VIEW_DELETED, {
view_id: data.id,
element: E_VIEWS,
state: "SUCCESS",
});
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Success!", title: "Success!",
message: "View deleted successfully.", message: "View deleted successfully.",
}); });
}) })
.catch(() => .catch(() => {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: "View could not be deleted. Please try again.", message: "View could not be deleted. Please try again.",
}) });
) captureEvent(VIEW_DELETED, {
view_id: data.id,
element: E_VIEWS,
state: "FAILED",
});
})
.finally(() => { .finally(() => {
setIsDeleteLoading(false); setIsDeleteLoading(false);
}); });

View File

@ -1,5 +1,6 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types // types
import { IProjectView } from "@plane/types"; import { IProjectView } from "@plane/types";
// ui // ui
@ -7,8 +8,10 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { ProjectViewForm } from "@/components/views"; import { ProjectViewForm } from "@/components/views";
// constants
import { VIEW_CREATED, VIEW_UPDATED } from "constants/event-tracker";
// hooks // hooks
import { useProjectView } from "@/hooks/store"; import { useProjectView, useEventTracker } from "@/hooks/store";
type Props = { type Props = {
data?: IProjectView | null; data?: IProjectView | null;
@ -21,8 +24,11 @@ type Props = {
export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => { export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => {
const { data, isOpen, onClose, preLoadedData, workspaceSlug, projectId } = props; const { data, isOpen, onClose, preLoadedData, workspaceSlug, projectId } = props;
// router
const router = useRouter();
// store hooks // store hooks
const { createView, updateView } = useProjectView(); const { createView, updateView } = useProjectView();
const { captureEvent, trackElement } = useEventTracker();
const handleClose = () => { const handleClose = () => {
onClose(); onClose();
@ -30,33 +36,59 @@ export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => {
const handleCreateView = async (payload: IProjectView) => { const handleCreateView = async (payload: IProjectView) => {
await createView(workspaceSlug, projectId, payload) await createView(workspaceSlug, projectId, payload)
.then(() => { .then((res) => {
handleClose(); handleClose();
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Success!", title: "Success!",
message: "View created successfully.", message: "View created successfully.",
}); });
captureEvent(VIEW_CREATED, {
view_id: res.id,
filters: res.filters,
// element_id: getElementFromPath(router.asPath),
// element: trackElement ? trackElement : getElementIdFromPath(router.asPath),
state: "SUCCESS",
});
}) })
.catch(() => .catch(() => {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: "Something went wrong. Please try again.", message: "Something went wrong. Please try again.",
}) });
); captureEvent(VIEW_CREATED, {
// element_id: getElementFromPath(router.asPath),
// element: trackElement ? trackElement : getElementIdFromPath(router.asPath),
state: "FAILED",
});
});
}; };
const handleUpdateView = async (payload: IProjectView) => { const handleUpdateView = async (payload: IProjectView) => {
await updateView(workspaceSlug, projectId, data?.id as string, payload) await updateView(workspaceSlug, projectId, data?.id as string, payload)
.then(() => handleClose()) .then((res) => {
.catch((err) => handleClose();
captureEvent(VIEW_UPDATED, {
view_id: res.id,
filters: res.filters,
// element: trackElement ? trackElement : getElementIdFromPath(router.asPath),
state: "SUCCESS",
});
})
.catch((err) => {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: err?.detail ?? "Something went wrong. Please try again.", message: err?.detail ?? "Something went wrong. Please try again.",
}) });
); captureEvent(VIEW_UPDATED, {
view_id: payload.id,
filters: payload.filters,
// element: trackElement ? trackElement : getElementIdFromPath(router.asPath),
state: "FAILED",
});
});
}; };
const handleFormSubmit = async (formData: IProjectView) => { const handleFormSubmit = async (formData: IProjectView) => {

View File

@ -8,12 +8,13 @@ import { ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from
// components // components
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "@/components/views"; import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "@/components/views";
// constants // constants
import { E_VIEWS } from "@/constants/event-tracker";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser, useEventTracker } from "@/hooks/store";
type Props = { type Props = {
parentRef: React.RefObject<HTMLElement>; parentRef: React.RefObject<HTMLElement>;
@ -31,6 +32,7 @@ export const ViewQuickActions: React.FC<Props> = observer((props) => {
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { setTrackElement } = useEventTracker();
// auth // auth
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -48,7 +50,10 @@ export const ViewQuickActions: React.FC<Props> = observer((props) => {
const MENU_ITEMS: TContextMenuItem[] = [ const MENU_ITEMS: TContextMenuItem[] = [
{ {
key: "edit", key: "edit",
action: () => setCreateUpdateViewModal(true), action: () => {
setTrackElement(E_VIEWS);
setCreateUpdateViewModal(true);
},
title: "Edit", title: "Edit",
icon: Pencil, icon: Pencil,
shouldRender: isEditingAllowed, shouldRender: isEditingAllowed,
@ -67,7 +72,10 @@ export const ViewQuickActions: React.FC<Props> = observer((props) => {
}, },
{ {
key: "delete", key: "delete",
action: () => setDeleteViewModal(true), action: () => {
setTrackElement(E_VIEWS);
setDeleteViewModal(true);
},
title: "Delete", title: "Delete",
icon: Trash2, icon: Trash2,
shouldRender: isEditingAllowed, shouldRender: isEditingAllowed,

View File

@ -7,11 +7,12 @@ import { IProjectView } from "@plane/types";
import { FavoriteStar } from "@/components/core"; import { FavoriteStar } from "@/components/core";
import { DeleteProjectViewModal, CreateUpdateProjectViewModal, ViewQuickActions } from "@/components/views"; import { DeleteProjectViewModal, CreateUpdateProjectViewModal, ViewQuickActions } from "@/components/views";
// constants // constants
import { E_VIEWS, VIEW_FAVORITED, VIEW_UNFAVORITED } from "constants/event-tracker";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers // helpers
import { calculateTotalFilters } from "@/helpers/filter.helper"; import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useMember, useProjectView, useUser } from "@/hooks/store"; import { useMember, useProjectView, useUser, useEventTracker } from "@/hooks/store";
import { ButtonAvatars } from "../dropdowns/member/avatar"; import { ButtonAvatars } from "../dropdowns/member/avatar";
type Props = { type Props = {
@ -33,6 +34,7 @@ export const ViewListItemAction: FC<Props> = observer((props) => {
} = useUser(); } = useUser();
const { addViewToFavorites, removeViewFromFavorites } = useProjectView(); const { addViewToFavorites, removeViewFromFavorites } = useProjectView();
const { getUserDetails } = useMember(); const { getUserDetails } = useMember();
const { captureEvent } = useEventTracker();
// derived values // derived values
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -43,13 +45,23 @@ export const ViewListItemAction: FC<Props> = observer((props) => {
const handleAddToFavorites = () => { const handleAddToFavorites = () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
addViewToFavorites(workspaceSlug.toString(), projectId.toString(), view.id); addViewToFavorites(workspaceSlug.toString(), projectId.toString(), view.id).then(() => {
captureEvent(VIEW_FAVORITED, {
view_id: view.id,
element: E_VIEWS,
});
});
}; };
const handleRemoveFromFavorites = () => { const handleRemoveFromFavorites = () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id); removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id).then(() => {
captureEvent(VIEW_UNFAVORITED, {
view_id: view.id,
element: E_VIEWS,
});
});
}; };
const createdByDetails = view.created_by ? getUserDetails(view.created_by) : undefined; const createdByDetails = view.created_by ? getUserDetails(view.created_by) : undefined;

View File

@ -6,13 +6,15 @@ import { ViewListLoader } from "@/components/ui";
import { ProjectViewListItem } from "@/components/views"; import { ProjectViewListItem } from "@/components/views";
// constants // constants
import { EmptyStateType } from "@/constants/empty-state"; import { EmptyStateType } from "@/constants/empty-state";
import { E_VIEWS_EMPTY_STATE } from "@/constants/event-tracker";
// hooks // hooks
import { useCommandPalette, useProjectView } from "@/hooks/store"; import { useCommandPalette, useProjectView, useEventTracker } from "@/hooks/store";
export const ProjectViewsList = observer(() => { export const ProjectViewsList = observer(() => {
// store hooks // store hooks
const { toggleCreateViewModal } = useCommandPalette(); const { toggleCreateViewModal } = useCommandPalette();
const { projectViewIds, getViewById, loader, searchQuery } = useProjectView(); const { projectViewIds, getViewById, loader, searchQuery } = useProjectView();
const { setTrackElement } = useEventTracker();
if (loader || !projectViewIds) return <ViewListLoader />; if (loader || !projectViewIds) return <ViewListLoader />;
@ -34,7 +36,13 @@ export const ProjectViewsList = observer(() => {
</ListLayout> </ListLayout>
</div> </div>
) : ( ) : (
<EmptyState type={EmptyStateType.PROJECT_VIEW} primaryButtonOnClick={() => toggleCreateViewModal(true)} /> <EmptyState
type={EmptyStateType.PROJECT_VIEW}
primaryButtonOnClick={() => {
setTrackElement(E_VIEWS_EMPTY_STATE);
toggleCreateViewModal(true);
}}
/>
)} )}
</> </>
); );

View File

@ -170,6 +170,12 @@ 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";
export const ISSUE_OPENED = "Issue opened"; export const ISSUE_OPENED = "Issue opened";
// Project View Events
export const VIEW_CREATED = "View created";
export const VIEW_UPDATED = "View updated";
export const VIEW_DELETED = "View deleted";
export const VIEW_FAVORITED = "View favorited";
export const VIEW_UNFAVORITED = "View unfavorited";
// Project State Events // Project State Events
export const STATE_CREATED = "State created"; export const STATE_CREATED = "State created";
export const STATE_UPDATED = "State updated"; export const STATE_UPDATED = "State updated";
@ -222,3 +228,7 @@ export const SNOOZED_NOTIFICATIONS = "Snoozed notifications viewed";
export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed"; export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed";
// Groups // Groups
export const GROUP_WORKSPACE = "Workspace_metrics"; export const GROUP_WORKSPACE = "Workspace_metrics";
// Elements
export const E_VIEWS = "Views page";
export const E_VIEWS_EMPTY_STATE = "Views empty state";