[WEB-4405] chore: project settings events (#7362)

* chore: workspace events

* fix: refactor

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Akshita Goyal 2025-07-10 17:05:30 +05:30 committed by GitHub
parent eb4239417a
commit 2e75ff7f1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 172 additions and 20 deletions

View file

@ -1,6 +1,7 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Pen, Trash } from "lucide-react"; import { Pen, Trash } from "lucide-react";
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
// components // components
import { ProIcon } from "@/components/common"; import { ProIcon } from "@/components/common";
@ -29,13 +30,17 @@ export const EstimateListItemButtons: FC<TEstimateListItem> = observer((props) =
} }
position="top" position="top"
> >
<button className="relative flex-shrink-0 w-6 h-6 flex justify-center items-center rounded cursor-pointer transition-colors overflow-hidden hover:bg-custom-background-80"> <button
className="relative flex-shrink-0 w-6 h-6 flex justify-center items-center rounded cursor-pointer transition-colors overflow-hidden hover:bg-custom-background-80"
data-ph-element={PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_LIST_ITEM}
>
<Pen size={12} /> <Pen size={12} />
</button> </button>
</Tooltip> </Tooltip>
<button <button
className="relative flex-shrink-0 w-6 h-6 flex justify-center items-center rounded cursor-pointer transition-colors overflow-hidden hover:bg-custom-background-80" className="relative flex-shrink-0 w-6 h-6 flex justify-center items-center rounded cursor-pointer transition-colors overflow-hidden hover:bg-custom-background-80"
onClick={() => onDeleteClick && onDeleteClick(estimateId)} onClick={() => onDeleteClick && onDeleteClick(estimateId)}
data-ph-element={PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_LIST_ITEM}
> >
<Trash size={12} /> <Trash size={12} />
</button> </button>

View file

@ -5,7 +5,13 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { ArchiveRestore } from "lucide-react"; import { ArchiveRestore } from "lucide-react";
// types // types
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import {
PROJECT_AUTOMATION_MONTHS,
EUserPermissions,
EUserPermissionsLevel,
PROJECT_SETTINGS_TRACKER_ELEMENTS,
PROJECT_SETTINGS_TRACKER_EVENTS,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { IProject } from "@plane/types"; import { IProject } from "@plane/types";
// ui // ui
@ -14,6 +20,7 @@ import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
import { SelectMonthModal } from "@/components/automation"; import { SelectMonthModal } from "@/components/automation";
// constants // constants
// hooks // hooks
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
import { useProject, useUserPermissions } from "@/hooks/store"; import { useProject, useUserPermissions } from "@/hooks/store";
type Props = { type Props = {
@ -65,11 +72,22 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
</div> </div>
<ToggleSwitch <ToggleSwitch
value={currentProjectDetails?.archive_in !== 0} value={currentProjectDetails?.archive_in !== 0}
onChange={() => onChange={async () => {
currentProjectDetails?.archive_in === 0 if (currentProjectDetails?.archive_in === 0) {
? handleChange({ archive_in: 1 }) await handleChange({ archive_in: 1 });
: handleChange({ archive_in: 0 }) } else {
} await handleChange({ archive_in: 0 });
}
captureElementAndEvent({
element: {
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.AUTOMATIONS_ARCHIVE_TOGGLE_BUTTON,
},
event: {
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.auto_archive_workitems,
state: "SUCCESS",
},
});
}}
size="sm" size="sm"
disabled={!isAdmin} disabled={!isAdmin}
/> />

View file

@ -6,7 +6,14 @@ import { useParams } from "next/navigation";
// icons // icons
import { ArchiveX } from "lucide-react"; import { ArchiveX } from "lucide-react";
// types // types
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel, EIconSize } from "@plane/constants"; import {
PROJECT_AUTOMATION_MONTHS,
EUserPermissions,
EUserPermissionsLevel,
EIconSize,
PROJECT_SETTINGS_TRACKER_ELEMENTS,
PROJECT_SETTINGS_TRACKER_EVENTS,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { IProject } from "@plane/types"; import { IProject } from "@plane/types";
// ui // ui
@ -15,6 +22,7 @@ import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleC
import { SelectMonthModal } from "@/components/automation"; import { SelectMonthModal } from "@/components/automation";
// constants // constants
// hooks // hooks
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
import { useProject, useProjectState, useUserPermissions } from "@/hooks/store"; import { useProject, useProjectState, useUserPermissions } from "@/hooks/store";
type Props = { type Props = {
@ -91,11 +99,22 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
</div> </div>
<ToggleSwitch <ToggleSwitch
value={currentProjectDetails?.close_in !== 0} value={currentProjectDetails?.close_in !== 0}
onChange={() => onChange={async () => {
currentProjectDetails?.close_in === 0 if (currentProjectDetails?.close_in === 0) {
? handleChange({ close_in: 1, default_state: defaultState }) await handleChange({ close_in: 1, default_state: defaultState });
: handleChange({ close_in: 0, default_state: null }) } else {
} await handleChange({ close_in: 0, default_state: null });
}
captureElementAndEvent({
element: {
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.AUTOMATIONS_CLOSE_TOGGLE_BUTTON,
},
event: {
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.auto_close_workitems,
state: "SUCCESS",
},
});
}}
size="sm" size="sm"
disabled={!isAdmin} disabled={!isAdmin}
/> />

View file

@ -3,8 +3,10 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// ui // ui
import { PROJECT_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
import { Button, EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// hooks // hooks
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { useEstimate, useProject, useProjectEstimates } from "@/hooks/store"; import { useEstimate, useProject, useProjectEstimates } from "@/hooks/store";
type TDeleteEstimateModal = { type TDeleteEstimateModal = {
@ -35,6 +37,12 @@ export const DeleteEstimateModal: FC<TDeleteEstimateModal> = observer((props) =>
await updateProject(workspaceSlug, projectId, { estimate: null }); await updateProject(workspaceSlug, projectId, { estimate: null });
} }
setButtonLoader(false); setButtonLoader(false);
captureSuccess({
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.estimate_deleted,
payload: {
id: estimateId,
},
});
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Estimate deleted", title: "Estimate deleted",
@ -43,6 +51,12 @@ export const DeleteEstimateModal: FC<TDeleteEstimateModal> = observer((props) =>
handleClose(); handleClose();
} catch (error) { } catch (error) {
setButtonLoader(false); setButtonLoader(false);
captureError({
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.estimate_deleted,
payload: {
id: estimateId,
},
});
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Estimate creation failed", title: "Estimate creation failed",

View file

@ -2,8 +2,10 @@
import { FC } from "react"; import { FC } from "react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { PROJECT_SETTINGS_TRACKER_ELEMENTS, PROJECT_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// public images // public images
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
import { DetailedEmptyState } from "../empty-state"; import { DetailedEmptyState } from "../empty-state";
type TEstimateEmptyScreen = { type TEstimateEmptyScreen = {
@ -26,7 +28,18 @@ export const EstimateEmptyScreen: FC<TEstimateEmptyScreen> = (props) => {
className="w-full !px-0 !py-0" className="w-full !px-0 !py-0"
primaryButton={{ primaryButton={{
text: t("project_settings.empty_state.estimates.primary_button"), text: t("project_settings.empty_state.estimates.primary_button"),
onClick: onButtonClick, onClick: () => {
onButtonClick();
captureElementAndEvent({
element: {
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_EMPTY_STATE_CREATE_BUTTON,
},
event: {
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.estimate_created,
state: "SUCCESS",
},
});
},
}} }}
/> />
); );

View file

@ -2,9 +2,11 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { PROJECT_SETTINGS_TRACKER_ELEMENTS, PROJECT_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui"; import { TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
// hooks // hooks
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
import { useProject, useProjectEstimates } from "@/hooks/store"; import { useProject, useProjectEstimates } from "@/hooks/store";
// i18n // i18n
type TEstimateDisableSwitch = { type TEstimateDisableSwitch = {
@ -30,6 +32,15 @@ export const EstimateDisableSwitch: FC<TEstimateDisableSwitch> = observer((props
await updateProject(workspaceSlug, projectId, { await updateProject(workspaceSlug, projectId, {
estimate: currentProjectActiveEstimate ? null : currentActiveEstimateId, estimate: currentProjectActiveEstimate ? null : currentActiveEstimateId,
}); });
captureElementAndEvent({
element: {
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_TOGGLE_BUTTON,
},
event: {
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.estimates_toggle,
state: "SUCCESS",
},
});
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: currentProjectActiveEstimate title: currentProjectActiveEstimate
@ -40,6 +51,15 @@ export const EstimateDisableSwitch: FC<TEstimateDisableSwitch> = observer((props
: t("project_settings.estimates.toasts.enabled.success.message"), : t("project_settings.estimates.toasts.enabled.success.message"),
}); });
} catch (err) { } catch (err) {
captureElementAndEvent({
element: {
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_TOGGLE_BUTTON,
},
event: {
eventName: PROJECT_SETTINGS_TRACKER_EVENTS.estimates_toggle,
state: "ERROR",
},
});
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: t("project_settings.estimates.toasts.disabled.error.title"), title: t("project_settings.estimates.toasts.disabled.error.title"),

View file

@ -3,6 +3,7 @@
import { MutableRefObject, useRef, useState } from "react"; import { MutableRefObject, useRef, useState } from "react";
import { LucideIcon, X } from "lucide-react"; import { LucideIcon, X } from "lucide-react";
// plane helpers // plane helpers
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { useOutsideClickDetector } from "@plane/hooks"; import { useOutsideClickDetector } from "@plane/hooks";
// types // types
import { IIssueLabel } from "@plane/types"; import { IIssueLabel } from "@plane/types";
@ -90,7 +91,10 @@ export const LabelItemBlock = (props: ILabelItemBlock) => {
<div className="py-0.5"> <div className="py-0.5">
<button <button
className="flex size-5 items-center justify-center rounded hover:bg-custom-background-80" className="flex size-5 items-center justify-center rounded hover:bg-custom-background-80"
onClick={() => handleLabelDelete(label)} onClick={() => {
handleLabelDelete(label);
}}
data-ph-element={PROJECT_SETTINGS_TRACKER_ELEMENTS.LABELS_DELETE_BUTTON}
> >
<X className="size-3.5 flex-shrink-0 text-custom-sidebar-text-300" /> <X className="size-3.5 flex-shrink-0 text-custom-sidebar-text-300" />
</button> </button>

View file

@ -2,8 +2,10 @@ import React, { Dispatch, SetStateAction, useState } from "react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { X, Pencil } from "lucide-react"; import { X, Pencil } from "lucide-react";
// types // types
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { IIssueLabel } from "@plane/types"; import { IIssueLabel } from "@plane/types";
// hooks // hooks
import { captureClick } from "@/helpers/event-tracker.helper";
import { useLabel } from "@/hooks/store"; import { useLabel } from "@/hooks/store";
// components // components
import { CreateUpdateLabelInline, TLabelOperationsCallbacks } from "./create-update-label-inline"; import { CreateUpdateLabelInline, TLabelOperationsCallbacks } from "./create-update-label-inline";
@ -67,6 +69,9 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
onClick: () => { onClick: () => {
setEditLabelForm(true); setEditLabelForm(true);
setIsUpdating(true); setIsUpdating(true);
captureClick({
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.LABELS_CONTEXT_MENU,
});
}, },
isVisible: true, isVisible: true,
text: "Edit label", text: "Edit label",

View file

@ -4,10 +4,10 @@ import React, { useState, useRef } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
// plane imports // plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel, PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { IIssueLabel } from "@plane/types"; import { IIssueLabel } from "@plane/types";
import { Button, Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
import { DetailedEmptyState } from "@/components/empty-state"; import { DetailedEmptyState } from "@/components/empty-state";
import { import {
CreateUpdateLabelInline, CreateUpdateLabelInline,
@ -17,6 +17,7 @@ import {
TLabelOperationsCallbacks, TLabelOperationsCallbacks,
} from "@/components/labels"; } from "@/components/labels";
// hooks // hooks
import { captureClick } from "@/helpers/event-tracker.helper";
import { useLabel, useUserPermissions } from "@/hooks/store"; import { useLabel, useUserPermissions } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { SettingsHeading } from "../settings"; import { SettingsHeading } from "../settings";
@ -81,7 +82,12 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
description={t("project_settings.labels.description")} description={t("project_settings.labels.description")}
button={{ button={{
label: t("common.add_label"), label: t("common.add_label"),
onClick: newLabel, onClick: () => {
newLabel();
captureClick({
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.LABELS_HEADER_CREATE_BUTTON,
});
},
}} }}
showButton={isEditable} showButton={isEditable}
/> />
@ -110,7 +116,12 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
description={""} description={""}
primaryButton={{ primaryButton={{
text: "Create your first label", text: "Create your first label",
onClick: newLabel, onClick: () => {
newLabel();
captureClick({
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.LABELS_EMPTY_STATE_CREATE_BUTTON,
});
},
}} }}
assetPath={resolvedPath} assetPath={resolvedPath}
className="w-full !px-0 !py-0" className="w-full !px-0 !py-0"

View file

@ -2,12 +2,13 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { PROJECT_TRACKER_ELEMENTS, PROJECT_TRACKER_EVENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { IProject } from "@plane/types"; import { IProject } from "@plane/types";
import { ToggleSwitch, Tooltip, setPromiseToast } from "@plane/ui"; import { ToggleSwitch, Tooltip, setPromiseToast } from "@plane/ui";
// hooks // hooks
import { SettingsHeading } from "@/components/settings"; import { SettingsHeading } from "@/components/settings";
import { captureSuccess } from "@/helpers/event-tracker.helper";
import { useProject, useUser } from "@/hooks/store"; import { useProject, useUser } from "@/hooks/store";
// plane web components // plane web components
import { UpgradeBadge } from "@/plane-web/components/workspace"; import { UpgradeBadge } from "@/plane-web/components/workspace";
@ -37,6 +38,7 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
[featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject], [featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject],
}; };
const updateProjectPromise = updateProject(workspaceSlug, projectId, settingsPayload); const updateProjectPromise = updateProject(workspaceSlug, projectId, settingsPayload);
setPromiseToast(updateProjectPromise, { setPromiseToast(updateProjectPromise, {
loading: "Updating project feature...", loading: "Updating project feature...",
success: { success: {
@ -48,6 +50,14 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
message: () => "Something went wrong while updating project feature. Please try again.", message: () => "Something went wrong while updating project feature. Please try again.",
}, },
}); });
updateProjectPromise.then(() => {
captureSuccess({
eventName: PROJECT_TRACKER_EVENTS.feature_toggled,
payload: {
feature_key: featureKey,
},
});
});
}; };
if (!currentUser) return <></>; if (!currentUser) return <></>;

View file

@ -45,6 +45,7 @@ export const PROJECT_TRACKER_EVENTS = {
create: "project_created", create: "project_created",
update: "project_updated", update: "project_updated",
delete: "project_deleted", delete: "project_deleted",
feature_toggled: "feature_toggled",
}; };
export const PROJECT_TRACKER_ELEMENTS = { export const PROJECT_TRACKER_ELEMENTS = {
@ -361,6 +362,38 @@ export const SIDEBAR_TRACKER_ELEMENTS = {
CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button", CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button",
}; };
/**
* ===========================================================================
* Project Settings Events and Elements
* ===========================================================================
*/
export const PROJECT_SETTINGS_TRACKER_ELEMENTS = {
LABELS_EMPTY_STATE_CREATE_BUTTON: "labels_empty_state_create_button",
LABELS_HEADER_CREATE_BUTTON: "labels_header_create_button",
LABELS_CONTEXT_MENU: "labels_context_menu",
LABELS_DELETE_BUTTON: "labels_delete_button",
ESTIMATES_TOGGLE_BUTTON: "estimates_toggle_button",
ESTIMATES_EMPTY_STATE_CREATE_BUTTON: "estimates_empty_state_create_button",
ESTIMATES_LIST_ITEM: "estimates_list_item",
AUTOMATIONS_ARCHIVE_TOGGLE_BUTTON: "automations_archive_toggle_button",
AUTOMATIONS_CLOSE_TOGGLE_BUTTON: "automations_close_toggle_button",
};
export const PROJECT_SETTINGS_TRACKER_EVENTS = {
// labels
label_created: "label_created",
label_updated: "label_updated",
label_deleted: "label_deleted",
// estimates
estimate_created: "estimate_created",
estimate_updated: "estimate_updated",
estimate_deleted: "estimate_deleted",
estimates_toggle: "estimates_toggled",
// automations
auto_close_workitems: "auto_close_workitems",
auto_archive_workitems: "auto_archive_workitems",
};
/** /**
* =========================================================================== * ===========================================================================
* Profile Settings Events and Elements * Profile Settings Events and Elements