mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
7 Commits
refactor/c
...
chore/proj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
352010d4dc | ||
|
|
fff9ba7c59 | ||
|
|
b854e79c51 | ||
|
|
be60b25dad | ||
|
|
f7a4198865 | ||
|
|
3f88694366 | ||
|
|
ffc987bcc8 |
@@ -8,8 +8,10 @@ import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { SelectMonthModal } from "@/components/automation";
|
||||
// icon
|
||||
// constants
|
||||
import { AUTO_ARCHIVE_TOGGLED, AUTO_ARCHIVE_UPDATED } from "@/constants/event-tracker";
|
||||
import { EUserProjectRoles, PROJECT_AUTOMATION_MONTHS } from "@/constants/project";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useProject, useUser, useEventTracker } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
@@ -27,6 +29,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
|
||||
|
||||
@@ -54,11 +57,15 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={currentProjectDetails?.archive_in !== 0}
|
||||
onChange={() =>
|
||||
onChange={() => {
|
||||
currentProjectDetails?.archive_in === 0
|
||||
? handleChange({ archive_in: 1 })
|
||||
: handleChange({ archive_in: 0 })
|
||||
}
|
||||
: handleChange({ archive_in: 0 });
|
||||
captureEvent(AUTO_ARCHIVE_TOGGLED, {
|
||||
toggle: currentProjectDetails?.archive_in === 0 ? "true" : "false",
|
||||
range: `${currentProjectDetails?.archive_in == 0 ? 1 : 0} month`,
|
||||
});
|
||||
}}
|
||||
size="sm"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
@@ -77,6 +84,9 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
captureEvent(AUTO_ARCHIVE_UPDATED, {
|
||||
range: val === 1 ? "1 month" : `${val} months`,
|
||||
});
|
||||
}}
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
|
||||
@@ -5,8 +5,11 @@ import { ArchiveX } from "lucide-react";
|
||||
import { IProject } from "@plane/types";
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
|
||||
import { SelectMonthModal } from "@/components/automation";
|
||||
// constants
|
||||
import { AUTO_CLOSE_Toggled, AUTO_CLOSE_UPDATED } from "@/constants/event-tracker";
|
||||
import { EUserProjectRoles, PROJECT_AUTOMATION_MONTHS } from "@/constants/project";
|
||||
import { useProject, useProjectState, useUser } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useProject, useProjectState, useUser, useEventTracker } from "@/hooks/store";
|
||||
// component
|
||||
// icons
|
||||
// types
|
||||
@@ -26,6 +29,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
// const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
|
||||
|
||||
@@ -80,11 +84,16 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={currentProjectDetails?.close_in !== 0}
|
||||
onChange={() =>
|
||||
onChange={() => {
|
||||
currentProjectDetails?.close_in === 0
|
||||
? handleChange({ close_in: 1, default_state: defaultState })
|
||||
: handleChange({ close_in: 0, default_state: null })
|
||||
}
|
||||
: handleChange({ close_in: 0, default_state: null });
|
||||
captureEvent(AUTO_CLOSE_Toggled, {
|
||||
toggle: currentProjectDetails?.close_in === 0 ? "true" : "false",
|
||||
range: `${currentProjectDetails?.close_in == 0 ? 1 : 0} month`,
|
||||
state: currentProjectDetails?.close_in === 0 ? undefined : defaultState,
|
||||
});
|
||||
}}
|
||||
size="sm"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
@@ -104,6 +113,10 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ close_in: val });
|
||||
captureEvent(AUTO_CLOSE_UPDATED, {
|
||||
range: val === 1 ? "1 month" : `${val} months`,
|
||||
state: currentProjectDetails?.default_state,
|
||||
});
|
||||
}}
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
|
||||
@@ -8,10 +8,12 @@ import { IEstimate, IEstimateFormData } from "@plane/types";
|
||||
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
|
||||
// constants
|
||||
import { ESTIMATE_CREATED, ESTIMATE_UPDATED } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { checkDuplicates } from "@/helpers/array.helper";
|
||||
// hooks
|
||||
import { useEstimate } from "@/hooks/store";
|
||||
import { useEstimate, useEventTracker } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -39,6 +41,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { createEstimate, updateEstimate } = useEstimate();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
@@ -58,8 +61,16 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await createEstimate(workspaceSlug.toString(), projectId.toString(), payload)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
onClose();
|
||||
captureEvent(ESTIMATE_CREATED, {
|
||||
estimate_id: res.id,
|
||||
estimate_points: res.points.map((point) => ({
|
||||
id: point.id,
|
||||
value: point.value,
|
||||
key: point.key,
|
||||
})),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const error = err?.error;
|
||||
@@ -82,6 +93,10 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
|
||||
await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload)
|
||||
.then(() => {
|
||||
onClose();
|
||||
captureEvent(ESTIMATE_UPDATED, {
|
||||
estimate_id: data.id,
|
||||
estimate_points: payload.estimate_points
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const error = err?.error;
|
||||
|
||||
@@ -7,8 +7,10 @@ import { IEstimate } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { AlertModalCore } from "@/components/core";
|
||||
// constants
|
||||
import { ESTIMATE_DELETED } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEstimate } from "@/hooks/store";
|
||||
import { useEstimate, useEventTracker } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -25,6 +27,7 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { deleteEstimate } = useEstimate();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const handleEstimateDelete = async () => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
@@ -37,6 +40,9 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
|
||||
await deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
captureEvent(ESTIMATE_DELETED, {
|
||||
estimate_id: estimateId,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const error = err?.error;
|
||||
|
||||
@@ -5,8 +5,12 @@ import { useRouter } from "next/router";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { IEstimate } from "@plane/types";
|
||||
import { Button, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { ESTIMATE_DISABLED } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { orderArrayBy } from "@/helpers/array.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useProject, useEventTracker } from "@/hooks/store";
|
||||
// ui
|
||||
//icons
|
||||
// helpers
|
||||
@@ -25,22 +29,29 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { currentProjectDetails, updateProject } = useProject();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const handleUseEstimate = async () => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await updateProject(workspaceSlug.toString(), projectId.toString(), {
|
||||
estimate: estimate.id,
|
||||
}).catch((err) => {
|
||||
const error = err?.error;
|
||||
const errorString = Array.isArray(error) ? error[0] : error;
|
||||
})
|
||||
.then(() =>
|
||||
captureEvent(ESTIMATE_DISABLED, {
|
||||
current_estimate_id: currentProjectDetails?.estimate,
|
||||
})
|
||||
)
|
||||
.catch((err) => {
|
||||
const error = err?.error;
|
||||
const errorString = Array.isArray(error) ? error[0] : error;
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: errorString ?? "Estimate points could not be used. Please try again.",
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: errorString ?? "Estimate points could not be used. Please try again.",
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,13 +6,13 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { Dialog, Popover, Transition } from "@headlessui/react";
|
||||
import type { IIssueLabel, IState } from "@plane/types";
|
||||
// hooks
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "@/constants/label";
|
||||
import { useLabel } from "@/hooks/store";
|
||||
// ui
|
||||
// types
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { E_STATES, LABEL_CREATED } from "@/constants/event-tracker";
|
||||
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "@/constants/label";
|
||||
// hooks
|
||||
import { useLabel, useEventTracker } from "@/hooks/store";
|
||||
|
||||
// types
|
||||
type Props = {
|
||||
@@ -34,6 +34,7 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store hooks
|
||||
const { createLabel } = useLabel();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
@@ -70,6 +71,13 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
|
||||
.then((res) => {
|
||||
onClose();
|
||||
if (onSuccess) onSuccess(res);
|
||||
captureEvent(LABEL_CREATED, {
|
||||
label_id: res.id,
|
||||
color: res.color,
|
||||
parent: res.parent,
|
||||
element: E_STATES,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
@@ -78,6 +86,9 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
|
||||
message: error?.detail ?? "Something went wrong. Please try again later.",
|
||||
});
|
||||
reset(formData);
|
||||
captureEvent(LABEL_CREATED, {
|
||||
state: "FAILED",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ import { IIssueLabel } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { E_Labels, LABEL_CREATED, LABEL_UPDATED } from "@/constants/event-tracker";
|
||||
import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "@/constants/label";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { useLabel, useEventTracker } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
@@ -34,12 +35,13 @@ export const CreateUpdateLabelInline = observer(
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { createLabel, updateLabel } = useLabel();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
watch,
|
||||
setValue,
|
||||
setFocus,
|
||||
@@ -57,7 +59,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: E_Labels,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
handleClose();
|
||||
reset(defaultValues);
|
||||
})
|
||||
@@ -76,9 +85,17 @@ export const CreateUpdateLabelInline = observer(
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
await updateLabel(workspaceSlug.toString(), projectId.toString(), labelToUpdate?.id!, formData)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
reset(defaultValues);
|
||||
handleClose();
|
||||
captureEvent(LABEL_UPDATED, {
|
||||
label_id: res.id,
|
||||
color: res.color,
|
||||
parent: res.parent,
|
||||
change_details: Object.keys(dirtyFields),
|
||||
element: E_Labels,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
|
||||
@@ -7,8 +7,10 @@ import type { IIssueLabel } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { AlertModalCore } from "@/components/core";
|
||||
// constants
|
||||
import { E_Labels, LABEL_DELETED, LABEL_GROUP_DELETED } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { useLabel, useEventTracker } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -22,7 +24,8 @@ export const DeleteLabelModal: React.FC<Props> = 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);
|
||||
|
||||
@@ -39,6 +42,21 @@ export const DeleteLabelModal: React.FC<Props> = observer((props) => {
|
||||
await deleteLabel(workspaceSlug.toString(), projectId.toString(), data.id)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
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: E_Labels,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
} else {
|
||||
captureEvent(LABEL_DELETED, {
|
||||
label_id: data.id,
|
||||
element: E_Labels,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setIsDeleteLoading(false);
|
||||
|
||||
@@ -10,6 +10,10 @@ import { createRoot } from "react-dom/client";
|
||||
import { IIssueLabel, InstructionType } from "@plane/types";
|
||||
// ui
|
||||
import { DropIndicator } from "@plane/ui";
|
||||
// constants
|
||||
import { LABEL_ADDED_G, LABEL_REMOVED_G } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
// components
|
||||
import { LabelName } from "./label-block/label-name";
|
||||
import { TargetData, getCanDrop, getInstructionFromPayload } from "./label-utils";
|
||||
@@ -52,10 +56,27 @@ export const LabelDndHOC = observer((props: Props) => {
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// refs
|
||||
const labelRef = useRef<HTMLDivElement | null>(null);
|
||||
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const captureLabelDropEvent = (source: TargetData, destination: TargetData) => {
|
||||
if (source?.parentId != destination.id) {
|
||||
source?.parentId &&
|
||||
captureEvent(LABEL_REMOVED_G, {
|
||||
group_id: source?.parentId,
|
||||
child_id: source?.id,
|
||||
});
|
||||
|
||||
captureEvent(LABEL_ADDED_G, {
|
||||
group_id: destination.id,
|
||||
child_id: source?.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const element = labelRef.current;
|
||||
const dragHandleElement = dragHandleRef.current;
|
||||
@@ -144,6 +165,8 @@ export const LabelDndHOC = observer((props: Props) => {
|
||||
|
||||
const sourceData = source.data as TargetData;
|
||||
if (sourceData.id) onDrop(sourceData.id as string, parentId, droppedLabelId, dropAtEndOfList);
|
||||
|
||||
captureLabelDropEvent(sourceData, dropTargetData);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -3,8 +3,10 @@ import { useRouter } from "next/router";
|
||||
import { X, Pencil } from "lucide-react";
|
||||
// types
|
||||
import { IIssueLabel } from "@plane/types";
|
||||
// constants
|
||||
import { E_Labels, LABEL_REMOVED_G } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { useLabel, useEventTracker } from "@/hooks/store";
|
||||
// components
|
||||
import { CreateUpdateLabelInline } from "./create-update-label-inline";
|
||||
import { ICustomMenuItem, LabelItemBlock } from "./label-block/label-item-block";
|
||||
@@ -34,6 +36,7 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { updateLabel } = useLabel();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const removeFromGroup = (label: IIssueLabel) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
@@ -41,6 +44,12 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
|
||||
updateLabel(workspaceSlug.toString(), projectId.toString(), label.id, {
|
||||
parent: null,
|
||||
});
|
||||
|
||||
captureEvent(LABEL_REMOVED_G, {
|
||||
group_id: label.id,
|
||||
child_id: label.id,
|
||||
element: E_Labels,
|
||||
});
|
||||
};
|
||||
|
||||
const customMenuItems: ICustomMenuItem[] = [
|
||||
|
||||
@@ -9,9 +9,11 @@ import { CustomSelect, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ConfirmProjectMemberRemove } from "@/components/project";
|
||||
// constants
|
||||
import { PROJECT_MEMBER_LEAVE } from "@/constants/event-tracker";
|
||||
import { E_MEMBERS, PM_ROLE_CHANGED, PROJECT_MEMBER_LEAVE, PROJECT_MEMBER_REMOVED } from "@/constants/event-tracker";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { getUserRole } from "@/helpers/user.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useProject, useUser } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
@@ -50,7 +52,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
.then(async () => {
|
||||
captureEvent(PROJECT_MEMBER_LEAVE, {
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
element: E_MEMBERS,
|
||||
});
|
||||
await fetchProjects(workspaceSlug.toString());
|
||||
router.push(`/${workspaceSlug}/projects`);
|
||||
@@ -63,14 +65,23 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
})
|
||||
);
|
||||
} else
|
||||
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id).catch(
|
||||
(err) =>
|
||||
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id)
|
||||
.then(() => {
|
||||
captureEvent(PROJECT_MEMBER_REMOVED, {
|
||||
member_id: userDetails.member.id,
|
||||
role: getUserRole(userDetails.role as number),
|
||||
removed_by_role: getUserRole(currentProjectRole as number),
|
||||
state: "SUCCESS",
|
||||
element: E_MEMBERS,
|
||||
});
|
||||
})
|
||||
.catch((err) =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.error || "Something went wrong. Please try again.",
|
||||
})
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
if (!userDetails) return null;
|
||||
@@ -145,16 +156,25 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
|
||||
updateMember(workspaceSlug.toString(), projectId.toString(), userDetails.member?.id, {
|
||||
role: value,
|
||||
}).catch((err) => {
|
||||
const error = err.error;
|
||||
const errorString = Array.isArray(error) ? error[0] : error;
|
||||
})
|
||||
.then(() =>
|
||||
captureEvent(PM_ROLE_CHANGED, {
|
||||
member_id: userDetails.member.id,
|
||||
changed_role: getUserRole(value as number),
|
||||
state: "SUCCESS",
|
||||
element: E_MEMBERS,
|
||||
})
|
||||
)
|
||||
.catch((err) => {
|
||||
const error = err.error;
|
||||
const errorString = Array.isArray(error) ? error[0] : error;
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: errorString ?? "An error occurred while updating member role. Please try again.",
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: errorString ?? "An error occurred while updating member role. Please try again.",
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
disabled={
|
||||
userDetails.member?.id === currentUser?.id || !currentProjectRole || currentProjectRole < userDetails.role
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// helpers
|
||||
import { PROJECT_MEMBER_ADDED } from "@/constants/event-tracker";
|
||||
import { E_MEMBERS, PROJECT_MEMBER_ADDED } from "@/constants/event-tracker";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
import { useEventTracker, useMember, useUser } from "@/hooks/store";
|
||||
@@ -86,21 +86,17 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||
message: "Members added successfully.",
|
||||
});
|
||||
captureEvent(PROJECT_MEMBER_ADDED, {
|
||||
members: [
|
||||
...payload.members.map((member) => ({
|
||||
member_id: member.member_id,
|
||||
role: ROLE[member.role],
|
||||
})),
|
||||
],
|
||||
member_id: payload.members?.[0]?.member_id,
|
||||
role: ROLE[payload.members?.[0]?.role],
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
element: E_MEMBERS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
captureEvent(PROJECT_MEMBER_ADDED, {
|
||||
state: "FAILED",
|
||||
element: "Project settings members page",
|
||||
element: E_MEMBERS,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { IState } from "@plane/types";
|
||||
// ui
|
||||
import { Button, CustomSelect, Input, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { STATE_CREATED, STATE_UPDATED } from "@/constants/event-tracker";
|
||||
import { E_STATES, STATE_CREATED, STATE_UPDATED } from "@/constants/event-tracker";
|
||||
import { GROUP_CHOICES } from "@/constants/project";
|
||||
// hooks
|
||||
import { useEventTracker, useProjectState } from "@/hooks/store";
|
||||
@@ -43,7 +43,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
watch,
|
||||
reset,
|
||||
control,
|
||||
@@ -91,7 +91,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
payload: {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
element: E_STATES,
|
||||
},
|
||||
});
|
||||
})
|
||||
@@ -114,7 +114,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
element: E_STATES,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -130,8 +130,9 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
eventName: STATE_UPDATED,
|
||||
payload: {
|
||||
...res,
|
||||
change_details: Object.keys(dirtyFields),
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
element: E_STATES,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
@@ -158,7 +159,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
element: E_STATES,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -293,9 +294,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
variant="primary"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}}
|
||||
onClick={() => setTrackElement(E_STATES)}
|
||||
size="sm"
|
||||
>
|
||||
{data ? (isSubmitting ? "Updating" : "Update") : isSubmitting ? "Creating" : "Create"}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Pencil, X, ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { IState } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { E_STATES } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker, useProjectState } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
@@ -103,7 +105,7 @@ export const StatesListItem: React.FC<Props> = observer((props) => {
|
||||
state.default || groupLength === 1 ? "cursor-not-allowed" : ""
|
||||
} grid place-items-center`}
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
setTrackElement(E_STATES);
|
||||
handleDeleteState();
|
||||
}}
|
||||
disabled={state.default || groupLength === 1}
|
||||
|
||||
@@ -138,6 +138,7 @@ export const getProjectStateEventPayload = (payload: any) => ({
|
||||
default: payload.default,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
change_details: payload.change_details,
|
||||
});
|
||||
|
||||
// Workspace crud Events
|
||||
@@ -174,16 +175,36 @@ export const ISSUE_OPENED = "Issue opened";
|
||||
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";
|
||||
export const PAGE_DELETED = "Page deleted";
|
||||
// Project Automation events
|
||||
export const AUTO_ARCHIVE_TOGGLED = "Auto archive toggled";
|
||||
export const AUTO_ARCHIVE_UPDATED = "Auto archive updated";
|
||||
export const AUTO_CLOSE_Toggled = "Auto close toggled";
|
||||
export const AUTO_CLOSE_UPDATED = "Auto close updated";
|
||||
// Member Events
|
||||
export const MEMBER_INVITED = "Member invited";
|
||||
export const MEMBER_ACCEPTED = "Member accepted";
|
||||
export const PROJECT_MEMBER_ADDED = "Project member added";
|
||||
export const PROJECT_MEMBER_LEAVE = "Project member leave";
|
||||
export const WORKSPACE_MEMBER_lEAVE = "Workspace member leave";
|
||||
export const PROJECT_MEMBER_REMOVED = "Project member removed";
|
||||
export const PM_ROLE_CHANGED = "Project member role changed";
|
||||
// Estimate Events
|
||||
export const ESTIMATE_CREATED = "Estimate created";
|
||||
export const ESTIMATE_UPDATED = "Estimate updated";
|
||||
export const ESTIMATE_DELETED = "Estimate deleted";
|
||||
export const ESTIMATE_USED = "Estimate used";
|
||||
export const ESTIMATE_DISABLED = "Estimate disabled";
|
||||
// Sign-in & Sign-up Events
|
||||
export const NAVIGATE_TO_SIGNUP = "Navigate to sign-up page";
|
||||
export const NAVIGATE_TO_SIGNIN = "Navigate to sign-in page";
|
||||
@@ -222,3 +243,9 @@ export const SNOOZED_NOTIFICATIONS = "Snoozed notifications viewed";
|
||||
export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed";
|
||||
// Groups
|
||||
export const GROUP_WORKSPACE = "Workspace_metrics";
|
||||
|
||||
// Elements
|
||||
export const E_STATES = "Project states page";
|
||||
export const E_Labels = "Project labels page";
|
||||
export const E_AUTOMATION = "Project automation page";
|
||||
export const E_MEMBERS = "Project settings members page";
|
||||
Reference in New Issue
Block a user