diff --git a/web/components/labels/create-label-modal.tsx b/web/components/labels/create-label-modal.tsx index e53e91147..d9c6162fb 100644 --- a/web/components/labels/create-label-modal.tsx +++ b/web/components/labels/create-label-modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { use, useEffect } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; @@ -6,7 +6,7 @@ import { TwitterPicker } from "react-color"; import { Dialog, Popover, Transition } from "@headlessui/react"; import { ChevronDown } from "lucide-react"; // hooks -import { useLabel } from "hooks/store"; +import { useEventTracker, useLabel } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { Button, Input } from "@plane/ui"; @@ -14,6 +14,7 @@ import { Button, Input } from "@plane/ui"; import type { IIssueLabel, IState } from "@plane/types"; // constants import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "constants/label"; +import { LABEL_CREATED } from "constants/event-tracker"; // types type Props = { @@ -35,6 +36,7 @@ export const CreateLabelModal: React.FC = observer((props) => { const { workspaceSlug } = router.query; // store hooks const { createLabel } = useLabel(); + const { captureEvent } = useEventTracker(); // form info const { formState: { errors, isSubmitting }, @@ -71,10 +73,20 @@ export const CreateLabelModal: React.FC = observer((props) => { await createLabel(workspaceSlug.toString(), projectId.toString(), formData) .then((res) => { + captureEvent(LABEL_CREATED, { + label_id: res.id, + color: res.color, + parent: res.parent, + element: "Project settings labels page", + state: "SUCCESS", + }); onClose(); if (onSuccess) onSuccess(res); }) .catch((error) => { + captureEvent(LABEL_CREATED, { + state: "FAILED", + }); setToastAlert({ title: "Oops!", type: "error", diff --git a/web/components/labels/create-update-label-inline.tsx b/web/components/labels/create-update-label-inline.tsx index 2d2be046d..a3a367e53 100644 --- a/web/components/labels/create-update-label-inline.tsx +++ b/web/components/labels/create-update-label-inline.tsx @@ -5,7 +5,7 @@ import { TwitterPicker } from "react-color"; import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { Popover, Transition } from "@headlessui/react"; // hooks -import { useLabel } from "hooks/store"; +import { useEventTracker, useLabel } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { Button, Input } from "@plane/ui"; @@ -13,6 +13,8 @@ import { Button, Input } from "@plane/ui"; import { IIssueLabel } from "@plane/types"; // fetch-keys import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label"; +// constants +import { LABEL_CREATED, LABEL_UPDATED } from "constants/event-tracker"; type Props = { labelForm: boolean; @@ -35,6 +37,7 @@ export const CreateUpdateLabelInline = observer( const { workspaceSlug, projectId } = router.query; // store hooks const { createLabel, updateLabel } = useLabel(); + const { captureEvent } = useEventTracker(); // toast alert const { setToastAlert } = useToast(); // form info @@ -42,7 +45,7 @@ export const CreateUpdateLabelInline = observer( handleSubmit, control, reset, - formState: { errors, isSubmitting }, + formState: { errors, isSubmitting, dirtyFields }, watch, setValue, setFocus, @@ -60,7 +63,14 @@ export const CreateUpdateLabelInline = observer( if (!workspaceSlug || !projectId || isSubmitting) return; await createLabel(workspaceSlug.toString(), projectId.toString(), formData) - .then(() => { + .then((res) => { + captureEvent(LABEL_CREATED, { + label_id: res.id, + color: res.color, + parent: res.parent, + element: "Project settings labels page", + state: "SUCCESS", + }); handleClose(); reset(defaultValues); }) @@ -78,7 +88,15 @@ export const CreateUpdateLabelInline = observer( if (!workspaceSlug || !projectId || isSubmitting) return; await updateLabel(workspaceSlug.toString(), projectId.toString(), labelToUpdate?.id!, formData) - .then(() => { + .then((res) => { + captureEvent(LABEL_UPDATED, { + label_id: res.id, + color: res.color, + parent: res.parent, + change_details: Object.keys(dirtyFields), + element: "Project settings labels page", + state: "SUCCESS", + }); reset(defaultValues); handleClose(); }) diff --git a/web/components/labels/delete-label-modal.tsx b/web/components/labels/delete-label-modal.tsx index 64d15eb65..b5d722a74 100644 --- a/web/components/labels/delete-label-modal.tsx +++ b/web/components/labels/delete-label-modal.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { Dialog, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // hooks -import { useLabel } from "hooks/store"; +import { useEventTracker, useLabel } from "hooks/store"; // icons import { AlertTriangle } from "lucide-react"; // hooks @@ -12,6 +12,8 @@ import useToast from "hooks/use-toast"; import { Button } from "@plane/ui"; // types import type { IIssueLabel } from "@plane/types"; +// constants +import { LABEL_DELETED, LABEL_GROUP_DELETED } from "constants/event-tracker"; type Props = { isOpen: boolean; @@ -25,7 +27,8 @@ export const DeleteLabelModal: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; // store hooks - const { deleteLabel } = useLabel(); + const { deleteLabel, projectLabelsTree } = useLabel(); + const { captureEvent } = useEventTracker(); // states const [isDeleteLoading, setIsDeleteLoading] = useState(false); // hooks @@ -43,6 +46,21 @@ export const DeleteLabelModal: React.FC = observer((props) => { await deleteLabel(workspaceSlug.toString(), projectId.toString(), data.id) .then(() => { + const labelChildCount = projectLabelsTree?.find((label) => label.id === data.id)?.children?.length || 0; + if (labelChildCount > 0) { + captureEvent(LABEL_GROUP_DELETED, { + group_id: data.id, + children_count: labelChildCount, + element: "Project settings labels page", + state: "SUCCESS", + }); + } else { + captureEvent(LABEL_DELETED, { + label_id: data.id, + element: "Project settings labels page", + state: "SUCCESS", + }); + } handleClose(); }) .catch((err) => { diff --git a/web/components/labels/project-setting-label-list.tsx b/web/components/labels/project-setting-label-list.tsx index fcd84d70a..e77ebd535 100644 --- a/web/components/labels/project-setting-label-list.tsx +++ b/web/components/labels/project-setting-label-list.tsx @@ -11,7 +11,7 @@ import { } from "@hello-pangea/dnd"; import { useTheme } from "next-themes"; // hooks -import { useLabel, useUser } from "hooks/store"; +import { useEventTracker, useLabel, useUser } from "hooks/store"; import useDraggableInPortal from "hooks/use-draggable-portal"; // components import { @@ -27,6 +27,7 @@ import { Button, Loader } from "@plane/ui"; import { IIssueLabel } from "@plane/types"; // constants import { PROJECT_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; +import { LABEL_ADDED_G, LABEL_REMOVED_G } from "constants/event-tracker"; const LABELS_ROOT = "labels.root"; @@ -45,7 +46,8 @@ export const ProjectSettingsLabelList: React.FC = observer(() => { const { resolvedTheme } = useTheme(); // store hooks const { currentUser } = useUser(); - const { projectLabels, updateLabelPosition, projectLabelsTree } = useLabel(); + const { projectLabels, updateLabelPosition, projectLabelsTree, getLabelById } = useLabel(); + const { captureEvent } = useEventTracker(); // portal const renderDraggable = useDraggableInPortal(); @@ -76,6 +78,30 @@ export const ProjectSettingsLabelList: React.FC = observer(() => { if (destination?.droppableId === LABELS_ROOT) parentLabel = null; if (result.reason == "DROP" && childLabel != parentLabel) { + const childLabelData = getLabelById(childLabel); + if (childLabelData?.parent != parentLabel) { + if (childLabelData?.parent) { + captureEvent(LABEL_REMOVED_G, { + group_id: childLabelData?.parent, + child_id: childLabel, + child_count: + (projectLabelsTree?.find((label) => label.id === childLabelData?.parent)?.children?.length ?? 0) - 1, + }); + parentLabel && + captureEvent(LABEL_ADDED_G, { + group_id: parentLabel, + child_id: childLabel, + child_count: (projectLabelsTree?.find((label) => label.id === parentLabel)?.children?.length ?? 0) + 1, + }); + } else { + captureEvent(LABEL_ADDED_G, { + group_id: parentLabel, + child_id: childLabel, + child_count: (projectLabelsTree?.find((label) => label.id === parentLabel)?.children?.length ?? 0) + 1, + }); + } + } + updateLabelPosition( workspaceSlug?.toString()!, projectId?.toString()!, diff --git a/web/constants/event-tracker.ts b/web/constants/event-tracker.ts index 8f86d8d7a..a123f19a5 100644 --- a/web/constants/event-tracker.ts +++ b/web/constants/event-tracker.ts @@ -178,7 +178,7 @@ export const elementFromPath = (path?: string) => { }; }; -// Workspace crud Events +// Workspace CRUD Events export const WORKSPACE_CREATED = "Workspace created"; export const WORKSPACE_UPDATED = "Workspace updated"; export const WORKSPACE_DELETED = "Workspace deleted"; @@ -213,7 +213,6 @@ export const ISSUE_UPDATED = "Issue updated"; export const ISSUE_DELETED = "Issue deleted"; export const ISSUE_ARCHIVED = "Issue archived"; export const ISSUE_RESTORED = "Issue restored"; - // Issue Checkout Events export const ISSUES_LIST_OPENED = "Issues list opened"; export const ISSUE_OPENED = "Issue opened"; @@ -231,6 +230,13 @@ export const LAYOUT_CHANGED = "Layout changed"; export const STATE_CREATED = "State created"; export const STATE_UPDATED = "State updated"; export const STATE_DELETED = "State deleted"; +// Label Events +export const LABEL_CREATED = "Label created"; +export const LABEL_UPDATED = "Label updated"; +export const LABEL_DELETED = "Label deleted"; +export const LABEL_GROUP_DELETED = "Label group deleted"; +export const LABEL_ADDED_G = "Label added to group"; +export const LABEL_REMOVED_G = "Label removed from group"; // Project Page Events export const PAGE_CREATED = "Page created"; export const PAGE_UPDATED = "Page updated";