[WEB-4423] refactor: event trackers (#7289)
* feat: event tracker helper * feat: track click events for `data-ph-element` * fix: handled click events * fix: handled name * chore: tracker element updates * chore: remove export * chore: tracker element type * chore: track element and event helper. * chore: minor improvements * chore: minor refactors * fix: workspace events * fix: added slug * fix: changes nomenclature * fix: nomenclature * chore: update event tracker helper types * fix: data id * refactor: cycle events (#7290) * chore: update event tracker helper types * refactor: cycle events * refactor: cycle events * refactor: cycle event tracker * chore: update tracker elements * chore: check for closest element with data-ph-element attribute --------- Co-authored-by: Prateek Shourya <prateekshourya@Prateeks-MacBook-Pro.local> * Refactor module events (#7291) * chore: update event tracker helper types * refactor: cycle events * refactor: cycle events * refactor: cycle event tracker * refactor: module tracker event and element * chore: update tracker element * chore: revert unnecessary changes --------- Co-authored-by: Prateek Shourya <prateekshourya@Prateeks-MacBook-Pro.local> * refactor: global views, product tour, notifications, onboarding, users and sidebar related events * chore: member tracker events (#7302) * chore: member-tracker-events * fix: constants * refactor: update event tracker constants * refactor: auth related event trackers (#7306) * Chore: state events (#7307) * chore: state events * fix: refactor * chore: project events (#7305) * chore: project-events * fix: refactor * fix: removed hardcoded values * fix: github redirection event * chore: project page tracker events (#7304) * added events for most page events * refactor: simplify lock button event handling in PageLockControl --------- Co-authored-by: Palanikannan M <akashmalinimurugu@gmail.com> Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> * chore: minor cleanup and import fixes * refactor: added tracker elements for buttons (#7308) Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * fix: event type * refactor: posthog group event * chore: removed instances of event tracker (#7309) * refactor: remove event tracker stores and hooks * refactor: remove event tracker store * fix: build errors * clean up event tracker payloads * fix: coderabbit suggestions --------- Co-authored-by: Prateek Shourya <prateekshourya@Prateeks-MacBook-Pro.local> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: Palanikannan M <akashmalinimurugu@gmail.com> Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: Vamsi Krishna <46787868+vamsikrishnamathala@users.noreply.github.com>
This commit is contained in:
parent
fa9c63716c
commit
cfe169c6d7
139 changed files with 2095 additions and 1888 deletions
|
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
|||
// icons
|
||||
import { Eye, EyeOff, Info, X, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS, AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
import { getPasswordStrength } from "@plane/utils";
|
||||
|
|
@ -16,7 +16,7 @@ import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/accou
|
|||
// helpers
|
||||
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
|
||||
|
|
@ -46,8 +46,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||
const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props;
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// ref
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
// states
|
||||
|
|
@ -77,7 +75,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||
|
||||
const redirectToUniqueCodeSignIn = async () => {
|
||||
handleAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||
captureEvent(AUTH_TRACKER_EVENTS.sign_in_with_code);
|
||||
};
|
||||
|
||||
const passwordSupport =
|
||||
|
|
@ -85,7 +82,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||
<div className="w-full">
|
||||
{isSMTPConfigured ? (
|
||||
<Link
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.forgot_password)}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.FORGOT_PASSWORD_FROM_SIGNIN}
|
||||
href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`}
|
||||
className="text-xs font-medium text-custom-primary-100"
|
||||
>
|
||||
|
|
@ -154,17 +151,32 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||
: true;
|
||||
if (isPasswordValid) {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password
|
||||
);
|
||||
captureSuccess({
|
||||
eventName:
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password,
|
||||
payload: {
|
||||
email: passwordFormData.email,
|
||||
},
|
||||
});
|
||||
if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met
|
||||
} else {
|
||||
setBannerMessage(true);
|
||||
}
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
onError={() => {
|
||||
setIsSubmitting(false);
|
||||
captureError({
|
||||
eventName:
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password,
|
||||
payload: {
|
||||
email: passwordFormData.email,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" />
|
||||
<input type="hidden" value={passwordFormData.email} name="email" />
|
||||
|
|
@ -292,6 +304,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||
{isSMTPConfigured && (
|
||||
<Button
|
||||
type="button"
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_WITH_UNIQUE_CODE}
|
||||
onClick={redirectToUniqueCodeSignIn}
|
||||
variant="outline-primary"
|
||||
className="w-full"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CircleCheck, XCircle } from "lucide-react";
|
||||
import { API_BASE_URL, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { API_BASE_URL, AUTH_TRACKER_ELEMENTS, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { EAuthModes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
|
|
@ -38,8 +38,6 @@ const defaultValues: TUniqueCodeFormValues = {
|
|||
|
||||
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// derived values
|
||||
const defaultResetTimerValue = 5;
|
||||
// states
|
||||
|
|
@ -62,10 +60,22 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
|||
setResendCodeTimer(defaultResetTimerValue);
|
||||
handleFormChange("code", uniqueCode?.code || "");
|
||||
setIsRequestingNewCode(false);
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.new_code_requested,
|
||||
payload: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setResendCodeTimer(0);
|
||||
console.error("Error while requesting new code");
|
||||
setIsRequestingNewCode(false);
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.new_code_requested,
|
||||
payload: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -84,12 +94,23 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
|||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
||||
onSubmit={() => {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(AUTH_TRACKER_EVENTS.code_verify, {
|
||||
state: "SUCCESS",
|
||||
first_time: !isExistingEmail,
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.code_verify,
|
||||
payload: {
|
||||
state: "SUCCESS",
|
||||
first_time: !isExistingEmail,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={() => {
|
||||
setIsSubmitting(false);
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.code_verify,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
|
||||
|
|
@ -145,6 +166,7 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
|||
</p>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.REQUEST_NEW_CODE}
|
||||
onClick={() => generateNewCode(uniqueCodeFormData.email)}
|
||||
className={
|
||||
isRequestNewCodeDisabled
|
||||
|
|
@ -163,7 +185,14 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
|||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
size="lg"
|
||||
disabled={isButtonDisabled}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.VERIFY_CODE}
|
||||
>
|
||||
{isRequestingNewCode ? (
|
||||
t("auth.common.unique_code.sending_code")
|
||||
) : isSubmitting ? (
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
import { Command } from "cmdk";
|
||||
import { ContrastIcon, FileText, Layers } from "lucide-react";
|
||||
// hooks
|
||||
import { CYCLE_TRACKER_ELEMENTS, MODULE_TRACKER_ELEMENTS, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { DiceIcon } from "@plane/ui";
|
||||
import { useCommandPalette, useEventTracker } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store";
|
||||
// ui
|
||||
|
||||
type Props = {
|
||||
|
|
@ -16,15 +18,14 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
|||
// store hooks
|
||||
const { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal } =
|
||||
useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Cycle">
|
||||
<Command.Item
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
|
@ -38,9 +39,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
|||
</Command.Group>
|
||||
<Command.Group heading="Module">
|
||||
<Command.Item
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
|
@ -56,7 +57,6 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
|||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateViewModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
|
@ -70,9 +70,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
|||
</Command.Group>
|
||||
<Command.Group heading="Page">
|
||||
<Command.Item
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@ import useSWR from "swr";
|
|||
import { CommandIcon, FolderPlus, Search, Settings, X } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_DEFAULT_SEARCH_RESULT } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
PROJECT_TRACKER_ELEMENTS,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
WORKSPACE_DEFAULT_SEARCH_RESULT,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceSearchResults } from "@plane/types";
|
||||
import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui";
|
||||
|
|
@ -28,14 +34,8 @@ import {
|
|||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// helpers
|
||||
// hooks
|
||||
import {
|
||||
useCommandPalette,
|
||||
useEventTracker,
|
||||
useIssueDetail,
|
||||
useProject,
|
||||
useUser,
|
||||
useUserPermissions,
|
||||
} from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useIssueDetail, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
@ -74,7 +74,6 @@ export const CommandModal: React.FC = observer(() => {
|
|||
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
||||
useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const projectIdentifier = workItem?.toString().split("-")[0];
|
||||
const sequence_id = workItem?.toString().split("-")[1];
|
||||
// fetch work item details using identifier
|
||||
|
|
@ -346,7 +345,9 @@ export const CommandModal: React.FC = observer(() => {
|
|||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command Palette");
|
||||
captureClick({
|
||||
elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON,
|
||||
});
|
||||
toggleCreateIssueModal(true);
|
||||
}}
|
||||
className="focus:bg-custom-background-80"
|
||||
|
|
@ -364,7 +365,7 @@ export const CommandModal: React.FC = observer(() => {
|
|||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON });
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
|
|
|||
|
|
@ -5,21 +5,15 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { COMMAND_PALETTE_TRACKER_ELEMENTS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { copyTextToClipboard } from "@plane/utils";
|
||||
import { CommandModal, ShortcutsModal } from "@/components/command-palette";
|
||||
// helpers
|
||||
// hooks
|
||||
import {
|
||||
useEventTracker,
|
||||
useUser,
|
||||
useAppTheme,
|
||||
useCommandPalette,
|
||||
useUserPermissions,
|
||||
useIssueDetail,
|
||||
} from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useAppTheme, useCommandPalette, useUserPermissions, useIssueDetail } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import {
|
||||
|
|
@ -42,7 +36,6 @@ export const CommandPalette: FC = observer(() => {
|
|||
// store hooks
|
||||
const { fetchIssueWithIdentifier } = useIssueDetail();
|
||||
const { toggleSidebar } = useAppTheme();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { platform } = usePlatformOS();
|
||||
const { data: currentUser, canPerformAnyCreateAction } = useUser();
|
||||
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette();
|
||||
|
|
@ -203,7 +196,7 @@ export const CommandPalette: FC = observer(() => {
|
|||
toggleSidebar();
|
||||
}
|
||||
} else if (!isAnyModalOpen) {
|
||||
setTrackElement("Shortcut key");
|
||||
captureClick({ elementName: COMMAND_PALETTE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_KEY });
|
||||
if (
|
||||
Object.keys(shortcutsList.global).includes(keyPressed) &&
|
||||
((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions())
|
||||
|
|
@ -242,7 +235,6 @@ export const CommandPalette: FC = observer(() => {
|
|||
performProjectCreateActions,
|
||||
performWorkspaceCreateActions,
|
||||
projectId,
|
||||
setTrackElement,
|
||||
shortcutsList,
|
||||
toggleCommandPaletteModal,
|
||||
toggleShortcutModal,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ArrowRight, ChevronRight } from "lucide-react";
|
||||
// Plane Imports
|
||||
import { CYCLE_TRACKER_EVENTS, CYCLE_STATUS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
CYCLE_STATUS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle } from "@plane/types";
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
|
|
@ -13,13 +19,11 @@ import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
|||
// components
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useUserPermissions } from "@/hooks/store";
|
||||
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||
// services
|
||||
import { CycleService } from "@/services/cycle.service";
|
||||
// local imports
|
||||
import { ArchiveCycleModal } from "../archived-cycles";
|
||||
import { CycleDeleteModal } from "../delete-modal";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -38,13 +42,9 @@ const cycleService = new CycleService();
|
|||
|
||||
export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycleDetails, handleClose, isArchived = false } = props;
|
||||
// states
|
||||
const [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
||||
// hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { updateCycleDetails } = useCycle();
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { t } = useTranslation();
|
||||
const { renderFormattedDateInUserTimezone, getProjectUTCOffset } = useTimeZoneConverter(projectId);
|
||||
|
||||
|
|
@ -61,29 +61,36 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
|||
|
||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||
|
||||
const submitChanges = async (data: Partial<ICycle>, changedProperty: string) => {
|
||||
const submitChanges = async (data: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId || !cycleDetails.id) return;
|
||||
|
||||
await updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
||||
.then((res) => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
changed_properties: [changedProperty],
|
||||
element: "Right side-peek",
|
||||
.then(() => {
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
state: "SUCCESS",
|
||||
payload: {
|
||||
id: cycleDetails.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
.catch(() => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...data,
|
||||
element: "Right side-peek",
|
||||
state: "FAILED",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
state: "ERROR",
|
||||
payload: {
|
||||
id: cycleDetails.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -122,7 +129,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
|||
isDateValid = true;
|
||||
}
|
||||
if (isDateValid) {
|
||||
submitChanges(payload, "date_range");
|
||||
submitChanges(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("project_cycles.action.update.success.title"),
|
||||
|
|
@ -145,24 +152,6 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{cycleDetails && workspaceSlug && projectId && (
|
||||
<>
|
||||
<ArchiveCycleModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
cycleId={cycleDetails.id}
|
||||
isOpen={archiveCycleModal}
|
||||
handleClose={() => setArchiveCycleModal(false)}
|
||||
/>
|
||||
<CycleDeleteModal
|
||||
cycle={cycleDetails}
|
||||
isOpen={cycleDeleteModal}
|
||||
handleClose={() => setCycleDeleteModal(false)}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="sticky z-10 top-0 pt-2 flex items-center justify-between bg-custom-sidebar-background-100">
|
||||
<div className="flex items-center justify-center size-5">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
import { useState, Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { CYCLE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
|
|
@ -42,16 +44,28 @@ export const ArchiveCycleModal: React.FC<Props> = (props) => {
|
|||
title: "Archive success",
|
||||
message: "Your archives can be found in project archives.",
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
onClose();
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Cycle could not be archived. Please try again.",
|
||||
})
|
||||
)
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.finally(() => setIsArchiving(false));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ import { useTranslation } from "@plane/i18n";
|
|||
import { ICycle } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useCycle } from "@/hooks/store";
|
||||
import { useCycle } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
interface ICycleDelete {
|
||||
|
|
@ -27,7 +28,6 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
// store hooks
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { deleteCycle } = useCycle();
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
|
|
@ -49,9 +49,11 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||
payload: { ...cycle, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: cycle.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
|
|
@ -64,13 +66,16 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||
type: TOAST_TYPE.ERROR,
|
||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||
payload: { ...cycle, state: "FAILED" },
|
||||
payload: {
|
||||
id: cycle.id,
|
||||
},
|
||||
error: errors,
|
||||
});
|
||||
})
|
||||
.finally(() => handleClose());
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Warning!",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
|||
import { useUser } from "@/hooks/store/user/user-user";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
||||
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
|
||||
handleClose: () => void;
|
||||
status: boolean;
|
||||
projectId: string;
|
||||
|
|
@ -40,7 +40,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
const { projectsWithCreatePermissions } = useUser();
|
||||
// form data
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
|
|
@ -64,7 +64,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
}, [data, reset]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
|
||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData))}>
|
||||
<div className="space-y-5 p-5">
|
||||
<div className="flex items-center gap-x-3">
|
||||
{!status && (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ import { useParams, usePathname, useSearchParams } from "next/navigation";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { Eye, Users, ArrowRight, CalendarDays } from "lucide-react";
|
||||
// types
|
||||
import { CYCLE_TRACKER_EVENTS, EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, TCycleGroups } from "@plane/types";
|
||||
|
|
@ -19,7 +25,8 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
|||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { MergedDateDisplay } from "@/components/dropdowns/merged-date";
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||
|
|
@ -57,7 +64,6 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
|||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { addCycleToFavorites, removeCycleFromFavorites } = useCycle();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// local storage
|
||||
|
|
@ -98,16 +104,25 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
|||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).then(
|
||||
() => {
|
||||
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||
.then(() => {
|
||||
if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
|
||||
captureEvent(CYCLE_TRACKER_EVENTS.favorite, {
|
||||
cycle_id: cycleId,
|
||||
element: "List layout",
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(addToFavoritePromise, {
|
||||
loading: t("project_cycles.action.favorite.loading"),
|
||||
|
|
@ -126,17 +141,24 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
|||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const removeFromFavoritePromise = removeCycleFromFavorites(
|
||||
workspaceSlug?.toString(),
|
||||
projectId.toString(),
|
||||
cycleId
|
||||
).then(() => {
|
||||
captureEvent(CYCLE_TRACKER_EVENTS.unfavorite, {
|
||||
cycle_id: cycleId,
|
||||
element: "List layout",
|
||||
state: "SUCCESS",
|
||||
const removeFromFavoritePromise = removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(removeFromFavoritePromise, {
|
||||
loading: t("project_cycles.action.unfavorite.loading"),
|
||||
|
|
@ -292,6 +314,7 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
|||
)}
|
||||
{isEditingAllowed && !cycleDetails.archived_at && (
|
||||
<FavoriteStar
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
|||
import { CycleForm } from "@/components/cycles";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useProject } from "@/hooks/store";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
@ -35,7 +36,6 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
// states
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
// store hooks
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createCycle, updateCycleDetails } = useCycle();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
|
@ -63,9 +63,11 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
title: "Success!",
|
||||
message: "Cycle created successfully.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
||||
payload: { ...res, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: res.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -74,23 +76,24 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
title: "Error!",
|
||||
message: err?.detail ?? "Error in creating cycle. Please try again.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>, dirtyFields: any) => {
|
||||
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
||||
.then((res) => {
|
||||
const changed_properties = Object.keys(dirtyFields);
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: changed_properties, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: res.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -99,15 +102,15 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.detail ?? "Error in updating cycle. Please try again.",
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -121,7 +124,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
return status;
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<ICycle>, dirtyFields: any) => {
|
||||
const handleFormSubmit = async (formData: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload: Partial<ICycle> = {
|
||||
|
|
@ -145,7 +148,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
}
|
||||
|
||||
if (isDateValid) {
|
||||
if (data) await handleUpdateCycle(data.id, payload, dirtyFields);
|
||||
if (data) await handleUpdateCycle(data.id, payload);
|
||||
else {
|
||||
await handleCreateCycle(payload).then(() => {
|
||||
setCycleTab("all");
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
|||
// icons
|
||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||
|
|
@ -14,7 +19,8 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
|||
import { ArchiveCycleModal, CycleCreateUpdateModal, CycleDeleteModal } from "@/components/cycles";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useEndCycle, EndCycleModal } from "@/plane-web/components/cycles";
|
||||
|
||||
|
|
@ -35,7 +41,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
const [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { getCycleById, restoreCycle } = useCycle();
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -69,7 +74,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
|
||||
|
||||
const handleEditCycle = () => {
|
||||
setTrackElement("Cycles page list layout");
|
||||
setUpdateModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -83,18 +87,29 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
title: t("project_cycles.action.restore.success.title"),
|
||||
message: t("project_cycles.action.restore.success.description"),
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/cycles`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("project_cycles.action.restore.failed.title"),
|
||||
message: t("project_cycles.action.restore.failed.description"),
|
||||
})
|
||||
);
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const handleDeleteCycle = () => {
|
||||
setTrackElement("Cycles page list layout");
|
||||
setDeleteModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -149,6 +164,16 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
|
||||
if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu);
|
||||
|
||||
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycleDetails && (
|
||||
|
|
@ -187,7 +212,7 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect maxHeight="lg" buttonClassName={customClassName}>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
|
|
@ -197,6 +222,9 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||
});
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { TourRoot } from "@/components/onboarding";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useUserProfile, useEventTracker, useUser } from "@/hooks/store";
|
||||
import { useUserProfile, useUser } from "@/hooks/store";
|
||||
import { useHome } from "@/hooks/store/use-home";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
// plane web components
|
||||
import { HomePeekOverviewsRoot } from "@/plane-web/components/home";
|
||||
// local imports
|
||||
import { DashboardWidgets } from "./home-dashboard-widgets";
|
||||
import { UserGreetingsView } from "./user-greetings";
|
||||
|
||||
|
|
@ -21,7 +24,6 @@ export const WorkspaceHomeView = observer(() => {
|
|||
const { workspaceSlug } = useParams();
|
||||
const { data: currentUser } = useUser();
|
||||
const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { toggleWidgetSettings, fetchWidgets } = useHome();
|
||||
const [windowWidth] = useSize();
|
||||
|
||||
|
|
@ -38,9 +40,11 @@ export const WorkspaceHomeView = observer(() => {
|
|||
const handleTourCompleted = () => {
|
||||
updateTourCompleted()
|
||||
.then(() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.complete, {
|
||||
user_id: currentUser?.id,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: PRODUCT_TOUR_TRACKER_EVENTS.complete,
|
||||
payload: {
|
||||
user_id: currentUser?.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
|||
|
|
@ -5,20 +5,14 @@ import Link from "next/link";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
|
||||
// plane ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
// helpers
|
||||
// hooks
|
||||
import {
|
||||
useCommandPalette,
|
||||
useEventTracker,
|
||||
useProject,
|
||||
useUser,
|
||||
useUserPermissions,
|
||||
useWorkspace,
|
||||
} from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useProject, useUser, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web constants
|
||||
|
||||
export const NoProjectsEmptyState = observer(() => {
|
||||
|
|
@ -27,7 +21,6 @@ export const NoProjectsEmptyState = observer(() => {
|
|||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
const { joinedProjectIds } = useProject();
|
||||
const { currentWorkspace: activeWorkspace } = useWorkspace();
|
||||
|
|
@ -59,8 +52,8 @@ export const NoProjectsEmptyState = observer(() => {
|
|||
if (!canCreateProject) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setTrackElement("Sidebar");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
},
|
||||
disabled: !canCreateProject,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
|
|
@ -22,7 +21,8 @@ import {
|
|||
} from "@/components/issues";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail, useMember, useProject, useProjectInbox, useUser } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail, useMember, useProject, useProjectInbox, useUser } from "@/hooks/store";
|
||||
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
||||
// store types
|
||||
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
|
||||
|
|
@ -45,8 +45,6 @@ type Props = {
|
|||
|
||||
export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
|
||||
// navigation
|
||||
const pathname = usePathname();
|
||||
// refs
|
||||
const editorRef = useRef<EditorRefApi>(null);
|
||||
// store hooks
|
||||
|
|
@ -57,8 +55,6 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||
const { removeIssue, archiveIssue } = useIssueDetail();
|
||||
// reload confirmation
|
||||
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
||||
// event tracker
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting === "submitted") {
|
||||
|
|
@ -104,10 +100,9 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Work item deleted successfully",
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: _issueId, state: "SUCCESS", element: "Work item detail page" },
|
||||
path: pathname,
|
||||
payload: { id: _issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in deleting work item:", error);
|
||||
|
|
@ -116,56 +111,46 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||
type: TOAST_TYPE.ERROR,
|
||||
message: "Work item delete failed",
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: _issueId, state: "FAILED", element: "Work item detail page" },
|
||||
path: pathname,
|
||||
payload: { id: _issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
update: async (_workspaceSlug: string, _projectId: string, _issueId: string, data: Partial<TIssue>) => {
|
||||
try {
|
||||
await inboxIssue.updateIssue(data);
|
||||
captureIssueEvent({
|
||||
eventName: "Inbox work item updated",
|
||||
payload: { ...data, state: "SUCCESS", element: "Inbox" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: _issueId },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
title: "Work item update failed",
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: "Work item update failed",
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: "Inbox work item updated",
|
||||
payload: { state: "SUCCESS", element: "Inbox" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: _issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
await archiveIssue(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Work item details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in archiving issue:", error);
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "FAILED", element: "Work item details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { FC, FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { ETabIndices, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
|
|
@ -14,9 +13,9 @@ import { renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
|
|||
// components
|
||||
import { InboxIssueTitle, InboxIssueDescription, InboxIssueProperties } from "@/components/inbox/modals/create-modal";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useProjectInbox, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject, useProjectInbox, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
@ -53,14 +52,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const pathname = usePathname();
|
||||
// refs
|
||||
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
||||
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const modalContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
// hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { createInboxIssue } = useProjectInbox();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||
|
|
@ -167,14 +164,11 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
descriptionEditorRef?.current?.clearEditor();
|
||||
setFormData(defaultIssueData);
|
||||
}
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "SUCCESS",
|
||||
element: "Inbox page",
|
||||
id: res?.issue?.id,
|
||||
},
|
||||
path: pathname,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -184,14 +178,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Inbox page",
|
||||
id: formData?.id,
|
||||
},
|
||||
path: pathname,
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
|
|||
|
|
@ -5,19 +5,20 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import { Plus } from "lucide-react";
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IJiraImporterForm } from "@plane/types";
|
||||
// hooks
|
||||
// components
|
||||
import { CustomSelect, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "@plane/utils";
|
||||
import { useCommandPalette, useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useProject } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, getProjectById } = useProject();
|
||||
// form info
|
||||
const {
|
||||
|
|
@ -201,8 +202,9 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
|||
<div>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Jira import detail page");
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE });
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EIssueServiceType, TIssueServiceType } from "@plane/types";
|
||||
// plane ui
|
||||
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// types
|
||||
import { TAttachmentUploadStatus } from "@/store/issue/issue-details/attachment.store";
|
||||
|
||||
|
|
@ -31,7 +33,6 @@ export const useAttachmentOperations = (
|
|||
const {
|
||||
attachment: { createAttachment, removeAttachment, getAttachmentsUploadStatusByIssueId },
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
const attachmentOperations: TAttachmentOperations = useMemo(
|
||||
() => ({
|
||||
|
|
@ -51,19 +52,16 @@ export const useAttachmentOperations = (
|
|||
},
|
||||
});
|
||||
|
||||
const res = await attachmentUploadPromise;
|
||||
captureIssueEvent({
|
||||
eventName: "Issue attachment added",
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "attachment",
|
||||
change_details: res.id,
|
||||
},
|
||||
await attachmentUploadPromise;
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.add,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
captureIssueEvent({
|
||||
eventName: "Issue attachment added",
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.add,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -77,22 +75,15 @@ export const useAttachmentOperations = (
|
|||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Attachment removed",
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: "Issue attachment deleted",
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "attachment",
|
||||
change_details: "",
|
||||
},
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.remove,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
captureIssueEvent({
|
||||
eventName: "Issue attachment deleted",
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "attachment",
|
||||
change_details: "",
|
||||
},
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.remove,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
message: "The Attachment could not be removed",
|
||||
|
|
@ -102,7 +93,7 @@ export const useAttachmentOperations = (
|
|||
}
|
||||
},
|
||||
}),
|
||||
[captureIssueEvent, workspaceSlug, projectId, issueId, createAttachment, removeAttachment]
|
||||
[workspaceSlug, projectId, issueId, createAttachment, removeAttachment]
|
||||
);
|
||||
const attachmentsUploadStatus = getAttachmentsUploadStatusByIssueId(issueId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -8,7 +7,8 @@ import { EIssueServiceType, TIssue, TIssueServiceType } from "@plane/types";
|
|||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { copyUrlToClipboard } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
||||
export type TRelationIssueOperations = {
|
||||
copyLink: (path: string) => void;
|
||||
|
|
@ -20,8 +20,6 @@ export const useRelationOperations = (
|
|||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
): TRelationIssueOperations => {
|
||||
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const pathname = usePathname();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const entityName = issueServiceType === EIssueServiceType.ISSUES ? "Work item" : "Epic";
|
||||
|
|
@ -40,29 +38,20 @@ export const useRelationOperations = (
|
|||
update: async (workspaceSlug, projectId, issueId, data) => {
|
||||
try {
|
||||
await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
setToast({
|
||||
title: t("toast.success"),
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
message: t("entity.update.success", { entity: entityName }),
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
title: t("toast.error"),
|
||||
|
|
@ -74,22 +63,21 @@ export const useRelationOperations = (
|
|||
remove: async (workspaceSlug, projectId, issueId) => {
|
||||
try {
|
||||
return removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
[captureIssueEvent, entityName, pathname, removeIssue, t, updateIssue]
|
||||
[entityName, removeIssue, t, updateIssue]
|
||||
);
|
||||
|
||||
return issueOperations;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssueServiceType, TIssueServiceType, TSubIssueOperations } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { copyUrlToClipboard } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail, useProjectState } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail, useProjectState } from "@/hooks/store";
|
||||
// plane web helpers
|
||||
import { updateEpicAnalytics } from "@/plane-web/helpers/epic-analytics";
|
||||
|
||||
export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSubIssueOperations => {
|
||||
// router
|
||||
const { epicId: epicIdParam } = useParams();
|
||||
const pathname = usePathname();
|
||||
// translation
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
|
|
@ -32,7 +33,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
const { peekIssue: epicPeekIssue } = useIssueDetail(EIssueServiceType.EPICS);
|
||||
// const { updateEpicAnalytics } = useIssueTypes();
|
||||
const { updateAnalytics } = updateEpicAnalytics();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
// derived values
|
||||
const epicId = epicIdParam || epicPeekIssue?.issueId;
|
||||
|
|
@ -128,14 +128,9 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
}
|
||||
}
|
||||
}
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue updated",
|
||||
payload: { ...oldIssue, ...issueData, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(issueData).join(","),
|
||||
change_details: Object.values(issueData).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.update,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -143,15 +138,11 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
message: t("sub_work_item.update.success"),
|
||||
});
|
||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue updated",
|
||||
payload: { ...oldIssue, ...issueData, state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(issueData).join(","),
|
||||
change_details: Object.values(issueData).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.update,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -179,25 +170,16 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
title: t("toast.success"),
|
||||
message: t("sub_work_item.remove.success"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue removed",
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "parent_id",
|
||||
change_details: parentIssueId,
|
||||
},
|
||||
path: pathname,
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.remove,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
});
|
||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue removed",
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "parent_id",
|
||||
change_details: parentIssueId,
|
||||
},
|
||||
path: pathname,
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.remove,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -210,18 +192,17 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
try {
|
||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||
return deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId).then(() => {
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue deleted",
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.delete,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
});
|
||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: "Sub-issue removed",
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.delete,
|
||||
payload: { id: issueId, parent_id: parentIssueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -232,7 +213,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
},
|
||||
}),
|
||||
[
|
||||
captureIssueEvent,
|
||||
createSubIssues,
|
||||
deleteSubIssue,
|
||||
epicId,
|
||||
|
|
@ -240,7 +220,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
|||
getIssueById,
|
||||
getStateById,
|
||||
issueServiceType,
|
||||
pathname,
|
||||
removeSubIssue,
|
||||
setSubIssueHelpers,
|
||||
t,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import React, { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { LayersIcon, Plus } from "lucide-react";
|
||||
// plane imports
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
issueId: string;
|
||||
|
|
@ -28,7 +30,6 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
|||
setIssueCrudOperationState,
|
||||
issueCrudOperationState,
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
|
|
@ -52,13 +53,13 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
|||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
setTrackElement("Issue detail nested sub-issue");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.create });
|
||||
handleIssueCrudState("create", issueId, null);
|
||||
toggleCreateIssueModal(true);
|
||||
};
|
||||
|
||||
const handleAddExisting = () => {
|
||||
setTrackElement("Issue detail nested sub-issue");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.add_existing });
|
||||
handleIssueCrudState("existing", issueId, null);
|
||||
toggleSubIssuesModal(issue.id);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import React, { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ArchiveIcon, ArchiveRestoreIcon, LinkIcon, Trash2 } from "lucide-react";
|
||||
import {
|
||||
ARCHIVABLE_STATE_GROUPS,
|
||||
|
|
@ -18,8 +17,8 @@ import { cn, generateWorkItemLink, copyTextToClipboard } from "@plane/utils";
|
|||
import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import {
|
||||
useEventTracker,
|
||||
useIssueDetail,
|
||||
useIssues,
|
||||
useProject,
|
||||
|
|
@ -64,8 +63,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
|||
const {
|
||||
issues: { removeIssue: removeArchivedIssue },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const pathname = usePathname();
|
||||
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
|
|
@ -103,22 +100,21 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
|||
|
||||
return deleteIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||
router.push(redirectionPath);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Work item detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
title: t("toast.error "),
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "FAILED", element: "Work item detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -128,16 +124,15 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
|||
await archiveIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/issues/${issue.id}`);
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -14,7 +13,8 @@ import { EmptyState } from "@/components/common";
|
|||
import { IssueDetailsSidebar, IssuePeekOverview } from "@/components/issues";
|
||||
// constants
|
||||
// hooks
|
||||
import { useAppTheme, useEventTracker, useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useAppTheme, useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// images
|
||||
import emptyIssue from "@/public/empty-state/issue.svg";
|
||||
|
|
@ -57,7 +57,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
const { workspaceSlug, projectId, issueId, is_archived = false } = props;
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const pathname = usePathname();
|
||||
// hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
|
|
@ -74,7 +73,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
const {
|
||||
issues: { removeIssue: removeArchivedIssue },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { issueDetailSidebarCollapsed } = useAppTheme();
|
||||
|
||||
|
|
@ -90,25 +88,16 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||
try {
|
||||
await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in updating issue:", error);
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
title: t("common.error.label"),
|
||||
|
|
@ -126,10 +115,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
type: TOAST_TYPE.SUCCESS,
|
||||
message: t("entity.delete.success", { entity: t("issue.label") }),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in deleting issue:", error);
|
||||
|
|
@ -138,85 +126,66 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
type: TOAST_TYPE.ERROR,
|
||||
message: t("entity.delete.failed", { entity: t("issue.label") }),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
await archiveIssue(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in archiving issue:", error);
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue details page" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
addCycleToIssue: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
|
||||
try {
|
||||
await addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("common.error.label"),
|
||||
message: t("issue.add.cycle.failed"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
||||
try {
|
||||
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issueIds, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("common.error.label"),
|
||||
message: t("issue.add.cycle.failed"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -235,24 +204,15 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
},
|
||||
});
|
||||
await removeFromCyclePromise;
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -271,24 +231,15 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
},
|
||||
});
|
||||
await removeFromModulePromise;
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -300,14 +251,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
removeModuleIds: string[]
|
||||
) => {
|
||||
const promise = await changeModulesInIssue(workspaceSlug, projectId, issueId, addModuleIds, removeModuleIds);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: { addModuleIds, removeModuleIds },
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
|
@ -324,9 +270,8 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
|||
removeIssueFromCycle,
|
||||
changeModulesInIssue,
|
||||
removeIssueFromModule,
|
||||
captureIssueEvent,
|
||||
pathname,
|
||||
t,
|
||||
issueId,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,15 @@ import size from "lodash/size";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "@/components/core";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { useCommandPalette, useCycle, useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useCycle, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const CycleEmptyState: React.FC = observer(() => {
|
||||
|
|
@ -27,7 +28,6 @@ export const CycleEmptyState: React.FC = observer(() => {
|
|||
const { getCycleById } = useCycle();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
|
|
@ -133,7 +133,7 @@ export const CycleEmptyState: React.FC = observer(() => {
|
|||
primaryButton={{
|
||||
text: t("project_cycles.empty_state.no_issues.primary_button.text"),
|
||||
onClick: () => {
|
||||
setTrackElement("Cycle issue empty state");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssuesStoreType, EUserWorkspaceRoles } from "@plane/types";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
|
|
@ -17,7 +18,6 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
// store hooks
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const hasMemberLevelPermission = allowPermissions(
|
||||
|
|
@ -46,8 +46,8 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
title={t("workspace_projects.empty_state.no_projects.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("All issues empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
|
|
@ -67,7 +67,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
? {
|
||||
text: t(`workspace_views.empty_state.${resolvedCurrentView}.primary_button.text`),
|
||||
onClick: () => {
|
||||
setTrackElement("All issues empty state");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ import size from "lodash/size";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "@/components/core";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const ModuleEmptyState: React.FC = observer(() => {
|
||||
|
|
@ -26,7 +27,6 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
|||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||
|
|
@ -119,7 +119,7 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
|||
primaryButton={{
|
||||
text: t("project_module.empty_state.no_issues.primary_button.text"),
|
||||
onClick: () => {
|
||||
setTrackElement("Module issue empty state");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.MODULE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ import size from "lodash/size";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions } from "@plane/types";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
|
|
@ -18,7 +19,6 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
|
|
@ -76,7 +76,7 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
|||
title={t("project_issues.empty_state.no_issues.primary_button.comic.title")}
|
||||
description={t("project_issues.empty_state.no_issues.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Project issue empty state");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.WORK_ITEMS });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
import { EmptyState } from "@/components/common";
|
||||
// constants
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
// assets
|
||||
import emptyIssue from "@/public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// auth
|
||||
|
|
@ -34,7 +33,7 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
|
|||
text: "New work item",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("View work item empty state");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.PROJECT_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
EIssueFilterType,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
GLOBAL_VIEW_TOUR_TRACKER_EVENTS,
|
||||
GLOBAL_VIEW_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { EIssuesStoreType, EViewAccess, IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
||||
import { Header, EHeaderVariant, Loader } from "@plane/ui";
|
||||
|
|
@ -21,7 +21,8 @@ import { AppliedFiltersList } from "@/components/issues";
|
|||
import { UpdateViewComponent } from "@/components/views/update-view-component";
|
||||
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||
// hooks
|
||||
import { useEventTracker, useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { getAreFiltersEqual } from "../../../utils";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -39,7 +40,6 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
|||
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||
const { workspaceLabels } = useLabel();
|
||||
const { globalViewMap, updateGlobalView } = useGlobalView();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { data } = useUser();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
|
|
@ -107,15 +107,25 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
|||
const handleUpdateView = () => {
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
|
||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters).then((res) => {
|
||||
if (res)
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, {
|
||||
view_id: res.id,
|
||||
applied_filters: res.filters,
|
||||
state: "SUCCESS",
|
||||
element: "Spreadsheet view",
|
||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters)
|
||||
.then((res) => {
|
||||
if (res)
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// add a placeholder object instead of appliedFilters if it is undefined
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
|
|
@ -17,7 +17,8 @@ import { EIssueServiceType, EIssuesStoreType } from "@plane/types";
|
|||
import { DeleteIssueModal } from "@/components/issues";
|
||||
//constants
|
||||
//hooks
|
||||
import { useEventTracker, useIssueDetail, useIssues, useKanbanView, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail, useIssues, useKanbanView, useUserPermissions } from "@/hooks/store";
|
||||
import { useGroupIssuesDragNDrop } from "@/hooks/use-group-dragndrop";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||
|
|
@ -62,11 +63,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
} = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const storeType = useIssueStoreType() as KanbanStoreType;
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { issueMap, issuesFilter, issues } = useIssues(storeType);
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
|
|
@ -205,15 +204,23 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
|
||||
if (!draggedIssueId || !draggedIssue) return;
|
||||
|
||||
await removeIssue(draggedIssue.project_id, draggedIssueId).finally(() => {
|
||||
setDeleteIssueModal(false);
|
||||
setDraggedIssueId(undefined);
|
||||
captureIssueEvent({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: draggedIssueId, state: "FAILED", element: "Kanban layout drag & drop" },
|
||||
path: pathname,
|
||||
await removeIssue(draggedIssue.project_id, draggedIssueId)
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: draggedIssueId },
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: draggedIssueId },
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setDeleteIssueModal(false);
|
||||
setDraggedIssueId(undefined);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleCollapsedGroups = useCallback(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
// lucide icons
|
||||
import { Minimize2, Maximize2, Circle, Plus } from "lucide-react";
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { TIssue, ISearchIssueResponse, TIssueKanbanFilters, TIssueGroupByOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -12,8 +13,7 @@ import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
|||
import { ExistingIssuesListModal } from "@/components/core";
|
||||
import { CreateUpdateIssueModal } from "@/components/issues";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal";
|
||||
// types
|
||||
|
|
@ -56,7 +56,6 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||
// hooks
|
||||
const storeType = useIssueStoreType();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
// router
|
||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||
const pathname = usePathname();
|
||||
|
|
@ -167,7 +166,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
setTrackElement("Kanban layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -175,7 +174,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
setTrackElement("Kanban layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing });
|
||||
setOpenExistingIssueListModal(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -186,7 +185,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
<div
|
||||
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
setTrackElement("Kanban layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
import { CircleDashed, Plus } from "lucide-react";
|
||||
// types
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { TIssue, ISearchIssueResponse, TIssueGroupByOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -13,9 +14,7 @@ import { cn } from "@plane/utils";
|
|||
import { ExistingIssuesListModal, MultipleSelectGroupAction } from "@/components/core";
|
||||
import { CreateUpdateIssueModal } from "@/components/issues";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
// plane-web
|
||||
|
|
@ -59,8 +58,6 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
// router
|
||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||
const pathname = usePathname();
|
||||
// hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const storeType = useIssueStoreType();
|
||||
// derived values
|
||||
const isDraftIssue = pathname.includes("draft-issue");
|
||||
|
|
@ -134,7 +131,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
setTrackElement("List layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -142,7 +139,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
setTrackElement("List layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing });
|
||||
setOpenExistingIssueListModal(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -153,7 +150,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
<div
|
||||
className="flex h-5 w-5 flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
setTrackElement("List layout");
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useCallback, useMemo, SyntheticEvent } from "react";
|
||||
import xor from "lodash/xor";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
|
||||
// types
|
||||
|
|
@ -34,7 +34,8 @@ import {
|
|||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useLabel, useIssues, useProjectState, useProject, useProjectEstimates } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useLabel, useIssues, useProjectState, useProject, useProjectEstimates } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
@ -55,13 +56,12 @@ export interface IIssueProperties {
|
|||
}
|
||||
|
||||
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className, isEpic = false } = props;
|
||||
const { issue, updateIssue, displayProperties, isReadOnly, className, isEpic = false } = props;
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { labelMap } = useLabel();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const storeType = useIssueStoreType();
|
||||
const {
|
||||
issues: { changeModulesInIssue },
|
||||
|
|
@ -77,9 +77,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const pathname = usePathname();
|
||||
|
||||
const currentLayout = `${activeLayout} layout`;
|
||||
// derived values
|
||||
const stateDetails = getStateById(issue.state_id);
|
||||
const subIssueCount = issue?.sub_issues_count ?? 0;
|
||||
|
|
@ -109,14 +107,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const handleState = (stateId: string) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "state",
|
||||
change_details: stateId,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -124,14 +117,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const handlePriority = (value: TIssuePriorities) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { priority: value }).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "priority",
|
||||
change_details: value,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -139,14 +127,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const handleLabel = (ids: string[]) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "labels",
|
||||
change_details: ids,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -154,14 +137,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const handleAssignee = (ids: string[]) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "assignees",
|
||||
change_details: ids,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -179,14 +157,12 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
if (modulesToAdd.length > 0) issueOperations.addModulesToIssue(modulesToAdd);
|
||||
if (modulesToRemove.length > 0) issueOperations.removeModulesFromIssue(modulesToRemove);
|
||||
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: { changed_property: "module_ids", change_details: { module_ids: moduleIds } },
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
},
|
||||
[issueOperations, captureIssueEvent, currentLayout, pathname, issue]
|
||||
[issueOperations, issue]
|
||||
);
|
||||
|
||||
const handleCycle = useCallback(
|
||||
|
|
@ -195,28 +171,21 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
||||
else issueOperations.removeIssueFromCycle?.();
|
||||
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: { changed_property: "cycle", change_details: { cycle_id: cycleId } },
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
},
|
||||
[issue, issueOperations, captureIssueEvent, currentLayout, pathname]
|
||||
[issue, issueOperations]
|
||||
);
|
||||
|
||||
const handleStartDate = (date: Date | null) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
||||
() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "start_date",
|
||||
change_details: date ? renderFormattedPayloadDate(date) : null,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -226,14 +195,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
||||
() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "target_date",
|
||||
change_details: date ? renderFormattedPayloadDate(date) : null,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -242,14 +206,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const handleEstimate = (value: string | undefined) => {
|
||||
if (updateIssue)
|
||||
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||
path: pathname,
|
||||
updates: {
|
||||
changed_property: "estimate_point",
|
||||
change_details: value,
|
||||
},
|
||||
payload: { id: issue.id },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@ import omit from "lodash/omit";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants";
|
||||
import { ARCHIVABLE_STATE_GROUPS, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useProjectState } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useProject, useProjectState } from "@/hooks/store";
|
||||
// plane-web components
|
||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
|
|
@ -39,8 +40,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { getStateById } = useProjectState();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
// derived values
|
||||
|
|
@ -70,7 +69,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
isArchivingAllowed,
|
||||
isDeletingAllowed: isEditingAllowed,
|
||||
isInArchivableGroup,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -84,6 +82,14 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
|
||||
const MENU_ITEMS = useAllIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.GLOBAL_VIEW });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -122,7 +128,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
/>
|
||||
)}
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
customButton={customActionButton}
|
||||
|
|
@ -171,6 +177,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.GLOBAL_VIEW });
|
||||
nestedItem.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
@ -208,6 +215,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.GLOBAL_VIEW });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { DeleteIssueModal } from "@/components/issues";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||
// types
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
// helper
|
||||
|
|
@ -36,7 +37,6 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
|||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
// derived values
|
||||
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
|
||||
|
|
@ -54,7 +54,6 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
|||
isEditingAllowed,
|
||||
isDeletingAllowed: isEditingAllowed,
|
||||
isRestoringAllowed: !!isRestoringAllowed,
|
||||
setTrackElement,
|
||||
setIssueToEdit: () => {},
|
||||
setCreateUpdateIssueModal: () => {},
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -64,6 +63,13 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
|||
|
||||
const MENU_ITEMS = useArchivedIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.ARCHIVED });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -74,7 +80,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
|||
onSubmit={handleDelete}
|
||||
/>
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
customButton={customActionButton}
|
||||
|
|
@ -94,6 +100,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
item.action();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.ARCHIVED });
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
ARCHIVABLE_STATE_GROUPS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||
// hooks
|
||||
import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
// plane-web components
|
||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
// types
|
||||
|
|
@ -41,8 +47,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug, cycleId } = useParams();
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { getStateById } = useProjectState();
|
||||
|
|
@ -78,7 +82,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
isArchivingAllowed,
|
||||
isDeletingAllowed,
|
||||
isInArchivableGroup,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -94,6 +97,14 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
|
||||
const MENU_ITEMS = useCycleIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.CYCLE });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -132,7 +143,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
/>
|
||||
)}
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
placement={placements}
|
||||
|
|
@ -181,6 +192,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.CYCLE });
|
||||
nestedItem.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
@ -218,6 +230,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.CYCLE });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@ import omit from "lodash/omit";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// local imports
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
import { useDraftIssueMenuItems, MenuItemFactoryProps } from "./helper";
|
||||
|
|
@ -37,7 +38,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
// derived values
|
||||
const activeLayout = "Draft Issues";
|
||||
// auth
|
||||
|
|
@ -70,7 +70,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
isEditingAllowed,
|
||||
isDeletingAllowed,
|
||||
isDraftIssue,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -81,6 +80,14 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
|
||||
const MENU_ITEMS = useDraftIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.DRAFT });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -104,7 +111,7 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
isDraft={isDraftIssue}
|
||||
/>
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
placement={placements}
|
||||
|
|
@ -122,6 +129,7 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.DRAFT });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export interface MenuItemFactoryProps {
|
|||
issueTypeDetail?: { is_active?: boolean };
|
||||
isDraftIssue?: boolean;
|
||||
// Action handlers
|
||||
setTrackElement: (element: string) => void;
|
||||
setIssueToEdit: (issue: TIssue | undefined) => void;
|
||||
setCreateUpdateIssueModal: (open: boolean) => void;
|
||||
setDeleteIssueModal: (open: boolean) => void;
|
||||
|
|
@ -144,7 +143,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
|||
isRestoringAllowed = false,
|
||||
isInArchivableGroup = false,
|
||||
issueTypeDetail,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -160,7 +158,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
|||
action:
|
||||
customEditAction ||
|
||||
(() => {
|
||||
setTrackElement(activeLayout);
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
}),
|
||||
|
|
@ -173,7 +170,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
|||
title: t("common.actions.make_a_copy"),
|
||||
icon: Copy,
|
||||
action: () => {
|
||||
setTrackElement(activeLayout);
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
shouldRender: isEditingAllowed && (issueTypeDetail?.is_active ?? true),
|
||||
|
|
@ -182,7 +178,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
|||
return createCopyMenuWithDuplication({
|
||||
baseItem,
|
||||
activeLayout,
|
||||
setTrackElement,
|
||||
setCreateUpdateIssueModal,
|
||||
setDuplicateWorkItemModal,
|
||||
});
|
||||
|
|
@ -243,7 +238,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
|||
title: t("common.actions.delete"),
|
||||
icon: Trash2,
|
||||
action: () => {
|
||||
setTrackElement(activeLayout);
|
||||
setDeleteIssueModal(true);
|
||||
},
|
||||
shouldRender: isDeletingAllowed,
|
||||
|
|
@ -304,7 +298,6 @@ export const useCycleIssueMenuItems = (props: MenuItemFactoryProps): TContextMen
|
|||
...props.issue,
|
||||
cycle_id: props.cycleId ?? null,
|
||||
});
|
||||
props.setTrackElement(props.activeLayout || "");
|
||||
props.setCreateUpdateIssueModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -330,7 +323,6 @@ export const useModuleIssueMenuItems = (props: MenuItemFactoryProps): TContextMe
|
|||
...props.issue,
|
||||
module_ids: props.moduleId ? [props.moduleId] : [],
|
||||
});
|
||||
props.setTrackElement(props.activeLayout || "");
|
||||
props.setCreateUpdateIssueModal(true);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
ARCHIVABLE_STATE_GROUPS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||
// hooks
|
||||
import { useIssues, useEventTracker, useProjectState, useUserPermissions, useProject } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssues, useProjectState, useUserPermissions, useProject } from "@/hooks/store";
|
||||
// plane-web components
|
||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
|
|
@ -41,7 +47,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
// router
|
||||
const { workspaceSlug, moduleId } = useParams();
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { getStateById } = useProjectState();
|
||||
|
|
@ -77,7 +82,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
isArchivingAllowed,
|
||||
isDeletingAllowed,
|
||||
isInArchivableGroup,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -93,6 +97,13 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
|
||||
const MENU_ITEMS = useModuleIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.MODULE });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -131,7 +142,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
/>
|
||||
)}
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
placement={placements}
|
||||
|
|
@ -180,6 +191,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.MODULE });
|
||||
nestedItem.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
@ -217,6 +229,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.MODULE });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
ARCHIVABLE_STATE_GROUPS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||
// hooks
|
||||
import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
// plane-web components
|
||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
|
|
@ -42,7 +48,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { getStateById } = useProjectState();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
|
|
@ -85,7 +90,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
isDeletingAllowed,
|
||||
isInArchivableGroup,
|
||||
isDraftIssue,
|
||||
setTrackElement,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -99,6 +103,14 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
|
||||
const MENU_ITEMS = useProjectIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
|
|
@ -137,7 +149,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
/>
|
||||
)}
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
placement={placements}
|
||||
|
|
@ -185,6 +197,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW });
|
||||
nestedItem.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
@ -222,6 +235,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useForm, UseFormRegister } from "react-hook-form";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// plane constants
|
||||
|
|
@ -18,7 +18,7 @@ import { CreateIssueToastActionItems } from "@/components/issues";
|
|||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// plane web components
|
||||
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues";
|
||||
|
||||
|
|
@ -69,11 +69,8 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
|||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const pathname = usePathname();
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
|
||||
// store hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
reset,
|
||||
|
|
@ -136,17 +133,16 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
|||
|
||||
await quickAddPromise
|
||||
.then((res) => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: { ...res, state: "SUCCESS", element: ` ${layout} quick add` },
|
||||
path: pathname,
|
||||
payload: { id: res?.id },
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureIssueEvent({
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: { ...payload, state: "FAILED", element: `${layout} quick ad` },
|
||||
path: pathname,
|
||||
payload: { id: payload.id },
|
||||
error: error as Error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import React, { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { TIssue } from "@plane/types";
|
||||
// components
|
||||
import { CycleDropdown } from "@/components/dropdowns";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -19,9 +20,7 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
|||
const { issue, disabled, onClose } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const {
|
||||
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||
} = useIssuesStore();
|
||||
|
|
@ -31,18 +30,14 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
|||
if (!workspaceSlug || !issue || !issue.project_id || issue.cycle_id === cycleId) return;
|
||||
if (cycleId) await addCycleToIssue(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||
else await removeCycleFromIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
captureIssueEvent({
|
||||
eventName: "Work item updated",
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...issue,
|
||||
cycle_id: cycleId,
|
||||
element: "Spreadsheet layout",
|
||||
id: issue.id,
|
||||
},
|
||||
updates: { changed_property: "cycle", change_details: { cycle_id: cycleId } },
|
||||
path: pathname,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue, captureIssueEvent, pathname]
|
||||
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import React, { useCallback } from "react";
|
||||
import xor from "lodash/xor";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { TIssue } from "@plane/types";
|
||||
// components
|
||||
import { ModuleDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -21,9 +22,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
|||
const { issue, disabled, onClose } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const {
|
||||
issues: { changeModulesInIssue },
|
||||
} = useIssuesStore();
|
||||
|
|
@ -41,18 +40,14 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
|||
}
|
||||
changeModulesInIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd, modulesToRemove);
|
||||
|
||||
captureIssueEvent({
|
||||
eventName: "Work item updated",
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...issue,
|
||||
module_ids: moduleIds,
|
||||
element: "Spreadsheet layout",
|
||||
id: issue.id,
|
||||
},
|
||||
updates: { changed_property: "module_ids", change_details: { module_ids: moduleIds } },
|
||||
path: pathname,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, issue, changeModulesInIssue, captureIssueEvent, pathname]
|
||||
[workspaceSlug, issue, changeModulesInIssue]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import { useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// types
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IIssueDisplayProperties, TIssue } from "@plane/types";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// components
|
||||
import { SPREADSHEET_COLUMNS } from "@/plane-web/components/issues/issue-layouts/utils";
|
||||
import { shouldRenderColumn } from "@/plane-web/helpers/issue-filter.helper";
|
||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||
// utils
|
||||
|
||||
type Props = {
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
|
|
@ -23,9 +22,7 @@ type Props = {
|
|||
export const IssueColumn = observer((props: Props) => {
|
||||
const { displayProperties, issueDetail, disableUserActions, property, updateIssue } = props;
|
||||
// router
|
||||
const pathname = usePathname();
|
||||
const tableCellRef = useRef<HTMLTableCellElement | null>(null);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
const shouldRenderProperty = shouldRenderColumn(property);
|
||||
|
||||
|
|
@ -46,18 +43,14 @@ export const IssueColumn = observer((props: Props) => {
|
|||
>
|
||||
<Column
|
||||
issue={issueDetail}
|
||||
onChange={(issue: TIssue, data: Partial<TIssue>, updates: any) =>
|
||||
onChange={(issue: TIssue, data: Partial<TIssue>) =>
|
||||
updateIssue &&
|
||||
updateIssue(issue.project_id, issue.id, data).then(() => {
|
||||
captureIssueEvent({
|
||||
eventName: "Issue updated",
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...issue,
|
||||
...data,
|
||||
element: "Spreadsheet layout",
|
||||
id: issue.id,
|
||||
},
|
||||
updates: updates,
|
||||
path: pathname,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// Plane imports
|
||||
import { useParams } from "next/navigation";
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
// Plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssuesStoreType, TBaseIssue, TIssue } from "@plane/types";
|
||||
import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issues";
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import { useEventTracker } from "@/hooks/store/use-event-tracker";
|
||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useModule } from "@/hooks/store/use-module";
|
||||
|
|
@ -61,7 +61,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
|
||||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId, workItem } = useParams();
|
||||
const { fetchCycleDetails } = useCycle();
|
||||
const { fetchModuleDetails } = useModule();
|
||||
|
|
@ -71,8 +70,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
const { fetchIssue } = useIssueDetail();
|
||||
const { allowedProjectIds, handleCreateUpdatePropertyValues } = useIssueModal();
|
||||
const { getProjectByIdentifier } = useProject();
|
||||
// pathname
|
||||
const pathname = usePathname();
|
||||
// current store details
|
||||
const { createIssue, updateIssue } = useIssuesActions(storeType);
|
||||
// derived values
|
||||
|
|
@ -239,10 +236,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
/>
|
||||
),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: { ...response, state: "SUCCESS" },
|
||||
path: pathname,
|
||||
payload: { id: response.id },
|
||||
});
|
||||
if (!createMore) handleClose();
|
||||
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
|
||||
|
|
@ -255,10 +251,10 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
title: t("error"),
|
||||
message: error?.error ?? t(is_draft_issue ? "draft_creation_failed" : "issue_creation_failed"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
path: pathname,
|
||||
payload: { id: payload.id },
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -301,10 +297,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
title: t("success"),
|
||||
message: t("issue_updated_successfully"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, issueId: data.id, state: "SUCCESS" },
|
||||
path: pathname,
|
||||
payload: { id: data.id },
|
||||
});
|
||||
handleClose();
|
||||
} catch (error: any) {
|
||||
|
|
@ -314,10 +309,10 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
title: t("error"),
|
||||
message: error?.error ?? t("issue_could_not_be_updated"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
path: pathname,
|
||||
payload: { id: data.id },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import React, { useState } from "react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type { TIssue } from "@plane/types";
|
||||
|
|
@ -14,8 +15,9 @@ import { isEmptyHtmlString } from "@plane/utils";
|
|||
import { ConfirmIssueDiscard } from "@/components/issues";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||
import { useEventTracker, useWorkspaceDraftIssues } from "@/hooks/store";
|
||||
import { useWorkspaceDraftIssues } from "@/hooks/store";
|
||||
// local components
|
||||
import { IssueFormRoot, type IssueFormProps } from "./form";
|
||||
|
||||
|
|
@ -30,10 +32,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
// pathname
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { handleCreateUpdatePropertyValues } = useIssueModal();
|
||||
const { createIssue } = useWorkspaceDraftIssues();
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -92,26 +91,25 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
title: `${t("success")}!`,
|
||||
message: t("workspace_draft_issues.toasts.created.success"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: "Draft work item created",
|
||||
payload: { ...res, state: "SUCCESS" },
|
||||
path: pathname,
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.draft.create,
|
||||
payload: { id: res?.id },
|
||||
});
|
||||
onChange(null);
|
||||
setIssueDiscardModal(false);
|
||||
onClose();
|
||||
return res;
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: `${t("error")}!`,
|
||||
message: t("workspace_draft_issues.toasts.created.error"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: "Draft work item created",
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
path: pathname,
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.draft.create,
|
||||
payload: { id: payload.id },
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
|||
// components
|
||||
import { IssueView, TIssueOperations } from "@/components/issues";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
|
||||
|
||||
|
|
@ -40,7 +41,6 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
const issueStoreType = useIssueStoreType();
|
||||
const storeType = issueStoreFromProps ?? issueStoreType;
|
||||
const { issues } = useIssues(storeType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
useWorkItemProperties(
|
||||
peekIssue?.projectId,
|
||||
|
|
@ -73,21 +73,16 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
.updateIssue(workspaceSlug, projectId, issueId, data)
|
||||
.then(async () => {
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: Object.keys(data).join(","),
|
||||
change_details: Object.values(data).join(","),
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureIssueEvent({
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
title: t("toast.error"),
|
||||
|
|
@ -100,23 +95,22 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
removeRoutePeekId();
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
title: t("toast.error"),
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -124,16 +118,15 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
try {
|
||||
if (!issues?.archiveIssue) return;
|
||||
await issues.archiveIssue(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -145,21 +138,20 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
title: t("issue.restore.success.title"),
|
||||
message: t("issue.restore.success.message"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("issue.restore.failed.message"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -167,58 +159,40 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
try {
|
||||
await issues.addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("issue.add.cycle.failed"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
||||
try {
|
||||
await issues.addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { ...issueIds, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueIds },
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("issue.add.cycle.failed"),
|
||||
});
|
||||
captureIssueEvent({
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: cycleId,
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueIds },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -238,24 +212,15 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
});
|
||||
await removeFromCyclePromise;
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "cycle_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -274,14 +239,9 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
removeModuleIds
|
||||
);
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: { addModuleIds, removeModuleIds },
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
|
@ -301,30 +261,21 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
});
|
||||
await removeFromModulePromise;
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
captureSuccess({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
});
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
} catch (error) {
|
||||
captureError({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
updates: {
|
||||
changed_property: "module_id",
|
||||
change_details: "",
|
||||
},
|
||||
path: pathname,
|
||||
payload: { id: issueId },
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[fetchIssue, is_draft, issues, fetchActivities, captureIssueEvent, pathname, removeRoutePeekId, restoreIssue]
|
||||
[fetchIssue, is_draft, issues, fetchActivities, pathname, removeRoutePeekId, restoreIssue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import { FC, Fragment } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EDraftIssuePaginationType } from "@plane/constants";
|
||||
import { EDraftIssuePaginationType, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EUserPermissionsLevel } from "@plane/constants/src/user";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
// components
|
||||
import { cn } from "@plane/utils";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// constants
|
||||
|
||||
// helpers
|
||||
|
|
@ -77,6 +78,7 @@ export const WorkspaceDraftIssuesRoot: FC<TWorkspaceDraftIssuesRoot> = observer(
|
|||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
EUserPermissionsLevel,
|
||||
EEstimateSystem,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
// plane types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -22,15 +23,10 @@ import { Loader, LayersIcon, CustomSelect, ModuleStatusIcon, TOAST_TYPE, setToas
|
|||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||
import {
|
||||
ArchiveModuleModal,
|
||||
DeleteModuleModal,
|
||||
CreateUpdateModuleLinkModal,
|
||||
ModuleAnalyticsProgress,
|
||||
ModuleLinksList,
|
||||
} from "@/components/modules";
|
||||
import { CreateUpdateModuleLinkModal, ModuleAnalyticsProgress, ModuleLinksList } from "@/components/modules";
|
||||
import { captureElementAndEvent, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useModule, useEventTracker, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
||||
import { useModule, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
const defaultValues: Partial<IModule> = {
|
||||
lead_id: "",
|
||||
|
|
@ -50,8 +46,6 @@ type Props = {
|
|||
export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { moduleId, handleClose, isArchived } = props;
|
||||
// states
|
||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
||||
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
||||
// router
|
||||
|
|
@ -62,7 +56,6 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
||||
const { captureModuleEvent, captureEvent } = useEventTracker();
|
||||
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();
|
||||
|
||||
// derived values
|
||||
|
|
@ -79,15 +72,22 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data)
|
||||
.then((res) => {
|
||||
captureModuleEvent({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { id: res.id },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureModuleEvent({
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -97,12 +97,20 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
|
||||
const payload = { metadata: {}, ...formData };
|
||||
|
||||
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload).then(() =>
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.create, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
})
|
||||
);
|
||||
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload)
|
||||
.then(() =>
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.create,
|
||||
payload: { id: moduleId },
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.create,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateLink = async (formData: ModuleLink, linkId: string) => {
|
||||
|
|
@ -110,13 +118,20 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
|
||||
const payload = { metadata: {}, ...formData };
|
||||
|
||||
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload).then(
|
||||
() =>
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.update, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload)
|
||||
.then(() =>
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||
payload: { id: moduleId },
|
||||
})
|
||||
);
|
||||
)
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteLink = async (linkId: string) => {
|
||||
|
|
@ -124,9 +139,9 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
|
||||
deleteModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId)
|
||||
.then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.delete, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.delete,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -140,6 +155,10 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: "Some error occurred",
|
||||
});
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.delete,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -213,16 +232,6 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
createLink={handleCreateLink}
|
||||
updateLink={handleUpdateLink}
|
||||
/>
|
||||
{workspaceSlug && projectId && (
|
||||
<ArchiveModuleModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
moduleId={moduleId}
|
||||
isOpen={archiveModuleModal}
|
||||
handleClose={() => setArchiveModuleModal(false)}
|
||||
/>
|
||||
)}
|
||||
<DeleteModuleModal isOpen={moduleDeleteModal} onClose={() => setModuleDeleteModal(false)} data={moduleDetails} />
|
||||
<>
|
||||
<div
|
||||
className={`sticky z-10 top-0 flex items-center justify-between bg-custom-sidebar-background-100 pb-5 pt-5`}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import type { IModule } from "@plane/types";
|
|||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useModule } from "@/hooks/store";
|
||||
import { useModule } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -28,7 +30,6 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
|||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, moduleId, peekModule } = useParams();
|
||||
// store hooks
|
||||
const { captureModuleEvent } = useEventTracker();
|
||||
const { deleteModule } = useModule();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -51,9 +52,9 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
|||
title: "Success!",
|
||||
message: "Module deleted successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||
payload: { ...data, state: "SUCCESS" },
|
||||
payload: { id: data.id },
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
|
|
@ -66,9 +67,10 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
|||
type: TOAST_TYPE.ERROR,
|
||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data.id },
|
||||
error: errors,
|
||||
});
|
||||
})
|
||||
.finally(() => handleClose());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { Copy, Pencil, Trash2 } from "lucide-react";
|
||||
// plane types
|
||||
import { MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { ILinkDetails } from "@plane/types";
|
||||
// plane ui
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
|
|
@ -58,6 +59,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
|||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -77,6 +79,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
|||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
|||
// components
|
||||
import { ModuleForm } from "@/components/modules";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useModule, useProject } from "@/hooks/store";
|
||||
import { useModule, useProject } from "@/hooks/store";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
|
|
@ -37,7 +39,6 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||
// states
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
// store hooks
|
||||
const { captureModuleEvent } = useEventTracker();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createModule, updateModuleDetails } = useModule();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
|
@ -63,9 +64,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||
title: "Success!",
|
||||
message: "Module created successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.create,
|
||||
payload: { ...res, state: "SUCCESS" },
|
||||
payload: { id: res.id },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -74,14 +75,15 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: err?.detail ?? err?.error ?? "Module could not be created. Please try again.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.create,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data?.id },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateModule = async (payload: Partial<IModule>, dirtyFields: any) => {
|
||||
const handleUpdateModule = async (payload: Partial<IModule>) => {
|
||||
if (!workspaceSlug || !projectId || !data) return;
|
||||
|
||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||
|
|
@ -94,9 +96,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||
title: "Success!",
|
||||
message: "Module updated successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: Object.keys(dirtyFields || {}), state: "SUCCESS" },
|
||||
payload: { id: res.id },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -105,21 +107,22 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: err?.detail ?? err?.error ?? "Module could not be updated. Please try again.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data.id },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<IModule>, dirtyFields: unknown) => {
|
||||
const handleFormSubmit = async (formData: Partial<IModule>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload: Partial<IModule> = {
|
||||
...formData,
|
||||
};
|
||||
if (!data) await handleCreateModule(payload);
|
||||
else await handleUpdateModule(payload, dirtyFields);
|
||||
else await handleUpdateModule(payload);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { IModule } from "@plane/types";
|
||||
|
|
@ -33,8 +34,9 @@ import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
|||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
// helpers
|
||||
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web constants
|
||||
|
|
@ -56,7 +58,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
// local storage
|
||||
const { setValue: toggleFavoriteMenu, storedValue } = useLocalStorage<boolean>(IS_FAVORITE_MENU_OPEN, false);
|
||||
|
|
@ -79,10 +80,15 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then(
|
||||
() => {
|
||||
if (!storedValue) toggleFavoriteMenu(true);
|
||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -110,10 +116,15 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||
projectId.toString(),
|
||||
moduleId
|
||||
).then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -24,8 +25,10 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
|||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureElementAndEvent, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { ButtonAvatars } from "../dropdowns/member/avatar";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -42,7 +45,6 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
const { addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -64,17 +66,28 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
|||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then(
|
||||
() => {
|
||||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||
.then(() => {
|
||||
// open favorites menu if closed
|
||||
if (!storedValue) toggleFavoriteMenu(true);
|
||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(addToFavoritePromise, {
|
||||
loading: "Adding module to favorites...",
|
||||
|
|
@ -98,13 +111,26 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
|||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleId
|
||||
).then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
)
|
||||
.then(() => {
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(removeFromFavoritePromise, {
|
||||
loading: "Removing module from favorites...",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { observer } from "mobx-react";
|
|||
import Image from "next/image";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { ContentWrapper, Row, ERowVariant } from "@plane/ui";
|
||||
|
|
@ -11,7 +11,7 @@ import { DetailedEmptyState, ComicBoxButton } from "@/components/empty-state";
|
|||
import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "@/components/modules";
|
||||
import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "@/components/ui";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useModule, useModuleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useModule, useModuleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import AllFiltersImage from "@/public/empty-state/module/all-filters.svg";
|
||||
import NameFilterImage from "@/public/empty-state/module/name-filter.svg";
|
||||
|
|
@ -25,7 +25,6 @@ export const ModulesListView: React.FC = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateModuleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { getProjectModuleIds, getFilteredModuleIds, loader } = useModule();
|
||||
const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
|
@ -60,8 +59,8 @@ export const ModulesListView: React.FC = observer(() => {
|
|||
label={t("project_module.empty_state.general.primary_button.text")}
|
||||
title={t("project_module.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_module.empty_state.general.primary_button.comic.description")}
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Module empty state");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
|||
// icons
|
||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -14,8 +19,9 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
|||
// components
|
||||
import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules";
|
||||
// helpers
|
||||
import { captureClick, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useModule, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -35,7 +41,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { getModuleById, restoreModule } = useModule();
|
||||
|
|
@ -67,7 +72,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank");
|
||||
|
||||
const handleEditModule = () => {
|
||||
setTrackElement("Modules page list layout");
|
||||
setEditModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -81,18 +85,26 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
title: "Restore success",
|
||||
message: "Your module can be found in project modules.",
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.restore,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/modules`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Module could not be restored. Please try again.",
|
||||
})
|
||||
);
|
||||
});
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.restore,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
const handleDeleteModule = () => {
|
||||
setTrackElement("Modules page list layout");
|
||||
setDeleteModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -145,6 +157,16 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
},
|
||||
];
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{moduleDetails && (
|
||||
|
|
@ -166,7 +188,7 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
||||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
|
|
@ -176,6 +198,9 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||
});
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import { observer } from "mobx-react";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
// constants
|
||||
import {
|
||||
ONBOARDING_TRACKER_EVENTS,
|
||||
ORGANIZATION_SIZE,
|
||||
RESTRICTED_URLS,
|
||||
WORKSPACE_TRACKER_EVENTS,
|
||||
WORKSPACE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -16,7 +16,8 @@ import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
|||
// ui
|
||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
||||
|
|
@ -41,7 +42,6 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
|||
const { updateUserProfile } = useUserProfile();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
const { createWorkspace, fetchWorkspaces } = useWorkspace();
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
|
|
@ -73,26 +73,18 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
|||
title: t("workspace_creation.toast.success.title"),
|
||||
message: t("workspace_creation.toast.success.message"),
|
||||
});
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...workspaceResponse,
|
||||
state: "SUCCESS",
|
||||
first_time: true,
|
||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
await completeStep(workspaceResponse.id);
|
||||
})
|
||||
.catch(() => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
first_time: true,
|
||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
error: new Error("Error creating workspace"),
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -290,7 +282,14 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="primary" type="submit" size="lg" className="w-full" disabled={isButtonDisabled}>
|
||||
<Button
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.ONBOARDING_CREATE_WORKSPACE_BUTTON}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("workspace_creation.button.default")}
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@
|
|||
|
||||
import React, { useState } from "react";
|
||||
// plane imports
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// types
|
||||
import { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Checkbox, Spinner } from "@plane/ui";
|
||||
import { truncateText, getUserRole } from "@plane/utils";
|
||||
import { truncateText } from "@plane/utils";
|
||||
// constants
|
||||
// helpers
|
||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||
// hooks
|
||||
import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
||||
|
|
@ -29,7 +30,6 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { fetchWorkspaces } = useWorkspace();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
|
||||
|
|
@ -50,26 +50,23 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
|
||||
try {
|
||||
await workspaceService.joinWorkspaces({ invitations: invitationsRespond });
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "SUCCESS",
|
||||
element: "Workspace invitations page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
},
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
await fetchCurrentUserSettings();
|
||||
await handleNextStep();
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "FAILED",
|
||||
element: "Workspace invitations page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
setIsJoiningWorkspaces(false);
|
||||
}
|
||||
|
|
@ -117,6 +114,7 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
className="w-full"
|
||||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || !invitationsRespond.length}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_JOIN_WORKSPACE}
|
||||
>
|
||||
{isJoiningWorkspaces ? <Spinner height="20px" width="20px" /> : "Continue to workspace"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { usePopper } from "react-popper";
|
|||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IUser, IWorkspace } from "@plane/types";
|
||||
|
|
@ -28,9 +28,8 @@ import { IUser, IWorkspace } from "@plane/types";
|
|||
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { getUserRole } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
// assets
|
||||
|
|
@ -276,8 +275,6 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
|
||||
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -311,16 +308,11 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||
})),
|
||||
})
|
||||
.then(async () => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...payload.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "SUCCESS",
|
||||
element: "Onboarding",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
workspace: workspace.slug,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -331,10 +323,12 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||
await nextStep();
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
project_id: undefined,
|
||||
state: "FAILED",
|
||||
element: "Onboarding",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
workspace: workspace.slug,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -426,6 +420,7 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isInvitationDisabled || !isValid || isSubmitting}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_INVITE_MEMBER}
|
||||
>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Image from "next/image";
|
|||
import { useTheme } from "next-themes";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_EVENTS, USER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_ELEMENTS, USER_TRACKER_EVENTS } from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUser, TUserProfile, TOnboardingSteps } from "@plane/types";
|
||||
|
|
@ -20,7 +20,8 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding
|
|||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useUser, useUserProfile } from "@/hooks/store";
|
||||
import { captureError, captureSuccess, captureView } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useUserProfile } from "@/hooks/store";
|
||||
// assets
|
||||
import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp";
|
||||
import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp";
|
||||
|
|
@ -98,7 +99,6 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
// store hooks
|
||||
const { updateCurrentUser } = useUser();
|
||||
const { updateUserProfile } = useUserProfile();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
getValues,
|
||||
|
|
@ -143,11 +143,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
updateUserProfile(profileUpdatePayload),
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -159,9 +160,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
finishOnboarding();
|
||||
}
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -183,9 +183,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
formData.password && handleSetPassword(formData.password),
|
||||
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -205,11 +204,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
updateUserProfile(profileUpdatePayload),
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -221,9 +221,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
finishOnboarding();
|
||||
}
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -235,6 +234,9 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||
|
||||
const onSubmit = async (formData: TProfileSetupFormValues) => {
|
||||
if (!user) return;
|
||||
captureView({
|
||||
elementName: ONBOARDING_TRACKER_ELEMENTS.PROFILE_SETUP_FORM,
|
||||
});
|
||||
if (profileSetupStep === EProfileSetupSteps.ALL) await handleSubmitProfileSetup(formData);
|
||||
if (profileSetupStep === EProfileSetupSteps.USER_DETAILS) await handleSubmitUserDetail(formData);
|
||||
if (profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION) await handleSubmitUserPersonalization(formData);
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ import { observer } from "mobx-react";
|
|||
import Image, { StaticImageData } from "next/image";
|
||||
import { X } from "lucide-react";
|
||||
// ui
|
||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { PRODUCT_TOUR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { TourSidebar } from "@/components/onboarding";
|
||||
// constants
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUser } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useUser } from "@/hooks/store";
|
||||
// assets
|
||||
import CyclesTour from "@/public/onboarding/cycles.webp";
|
||||
import IssuesTour from "@/public/onboarding/issues.webp";
|
||||
|
|
@ -85,7 +86,6 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement, captureEvent } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||
|
|
@ -112,7 +112,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.start);
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.START_BUTTON,
|
||||
});
|
||||
setStep("work-items");
|
||||
}}
|
||||
>
|
||||
|
|
@ -122,7 +124,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||
type="button"
|
||||
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
||||
onClick={() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.skip);
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.SKIP_BUTTON,
|
||||
});
|
||||
onComplete();
|
||||
}}
|
||||
>
|
||||
|
|
@ -171,7 +175,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setTrackElement("Product tour");
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.CREATE_PROJECT_BUTTON,
|
||||
});
|
||||
onComplete();
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
Trash2,
|
||||
} from "lucide-react";
|
||||
// constants
|
||||
import { EPageAccess } from "@plane/constants";
|
||||
import { EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// plane editor
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// plane ui
|
||||
|
|
@ -26,6 +26,7 @@ import { cn } from "@plane/utils";
|
|||
import { DeletePageModal } from "@/components/pages";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// plane web components
|
||||
import { MovePageModal } from "@/plane-web/components/pages";
|
||||
|
|
@ -92,14 +93,24 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
|||
const menuItems: (TContextMenuItem & { key: TPageActions })[] = [
|
||||
{
|
||||
key: "toggle-lock",
|
||||
action: pageOperations.toggleLock,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON,
|
||||
});
|
||||
pageOperations.toggleLock();
|
||||
},
|
||||
title: is_locked ? "Unlock" : "Lock",
|
||||
icon: is_locked ? LockKeyholeOpen : LockKeyhole,
|
||||
shouldRender: canCurrentUserLockPage,
|
||||
},
|
||||
{
|
||||
key: "toggle-access",
|
||||
action: pageOperations.toggleAccess,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.ACCESS_TOGGLE,
|
||||
});
|
||||
pageOperations.toggleAccess();
|
||||
},
|
||||
title: access === EPageAccess.PUBLIC ? "Make private" : "Make public",
|
||||
icon: access === EPageAccess.PUBLIC ? Lock : Globe2,
|
||||
shouldRender: canCurrentUserChangeAccess && !archived_at,
|
||||
|
|
@ -120,21 +131,36 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
|||
},
|
||||
{
|
||||
key: "make-a-copy",
|
||||
action: pageOperations.duplicate,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.DUPLICATE_BUTTON,
|
||||
});
|
||||
pageOperations.duplicate();
|
||||
},
|
||||
title: "Make a copy",
|
||||
icon: Copy,
|
||||
shouldRender: canCurrentUserDuplicatePage,
|
||||
},
|
||||
{
|
||||
key: "archive-restore",
|
||||
action: pageOperations.toggleArchive,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.ARCHIVE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleArchive();
|
||||
},
|
||||
title: archived_at ? "Restore" : "Archive",
|
||||
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
||||
shouldRender: canCurrentUserArchivePage,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => setDeletePageModal(true),
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
setDeletePageModal(true);
|
||||
},
|
||||
title: "Delete",
|
||||
icon: Trash2,
|
||||
shouldRender: canCurrentUserDeletePage && !!archived_at,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
// constants
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// ui
|
||||
import { FavoriteStar } from "@plane/ui";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// store
|
||||
|
|
@ -23,7 +27,12 @@ export const PageFavoriteControl = observer(({ page }: Props) => {
|
|||
return (
|
||||
<FavoriteStar
|
||||
selected={is_favorite}
|
||||
onClick={pageOperations.toggleFavorite}
|
||||
onClick={() => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleFavorite();
|
||||
}}
|
||||
buttonClassName="flex-shrink-0 size-6 group rounded hover:bg-custom-background-80 transition-colors"
|
||||
iconClassName="size-3.5 text-custom-text-200 group-hover:text-custom-text-10"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Earth, Info, Lock, Minus } from "lucide-react";
|
||||
// constants
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// ui
|
||||
import { Avatar, FavoriteStar, Tooltip } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { PageActions } from "@/components/pages";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
|
|
@ -64,6 +67,9 @@ export const BlockItemAction: FC<Props> = observer((props) => {
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleFavorite();
|
||||
}}
|
||||
selected={is_favorite}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
|||
// components
|
||||
import { PageForm } from "@/components/pages";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web hooks
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
|
|
@ -42,7 +42,6 @@ export const CreatePageModal: FC<Props> = (props) => {
|
|||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { createPage } = usePageStore(storeType);
|
||||
const { capturePageEvent } = useEventTracker();
|
||||
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
||||
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
|
|
@ -62,22 +61,19 @@ export const CreatePageModal: FC<Props> = (props) => {
|
|||
try {
|
||||
const pageData = await createPage(pageFormData);
|
||||
if (pageData) {
|
||||
capturePageEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...pageData,
|
||||
state: "SUCCESS",
|
||||
id: pageData.id,
|
||||
},
|
||||
});
|
||||
handleStateClear();
|
||||
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
||||
}
|
||||
} catch {
|
||||
capturePageEvent({
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { PROJECT_PAGE_TRACKER_EVENTS } from "@plane/constants";
|
|||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// plane web hooks
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
|
|
@ -28,7 +28,6 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
// store hooks
|
||||
const { removePage } = usePageStore(storeType);
|
||||
const { capturePageEvent } = useEventTracker();
|
||||
if (!page || !page.id) return null;
|
||||
// derived values
|
||||
const { id: pageId, name } = page;
|
||||
|
|
@ -45,11 +44,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||
setIsDeleting(true);
|
||||
await removePage(pageId)
|
||||
.then(() => {
|
||||
capturePageEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...page,
|
||||
state: "SUCCESS",
|
||||
id: pageId,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
|
|
@ -64,11 +62,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||
}
|
||||
})
|
||||
.catch(() => {
|
||||
capturePageEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...page,
|
||||
state: "FAILED",
|
||||
id: pageId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EPageAccess } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserProjectRoles, TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { PageLoader } from "@/components/pages";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// plane web hooks
|
||||
|
|
@ -63,6 +64,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
|||
text: t("project_page.empty_state.general.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
|
|
@ -79,6 +81,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
|||
text: t("project_page.empty_state.public.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
|
|
@ -94,6 +97,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
|||
text: t("project_page.empty_state.private.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EventProps, STATE_TRACKER_EVENTS, STATE_GROUPS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS, STATE_GROUPS } from "@plane/constants";
|
||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { StateForm } from "@/components/project-states";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
|
||||
type TStateCreate = {
|
||||
groupKey: TStateGroups;
|
||||
|
|
@ -19,17 +19,10 @@ type TStateCreate = {
|
|||
|
||||
export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||
const { groupKey, shouldTrackEvents, createStateCallback, handleClose } = props;
|
||||
// hooks
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setLoader(false);
|
||||
handleClose();
|
||||
|
|
@ -38,19 +31,16 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
|||
const onSubmit = async (formData: Partial<IState>) => {
|
||||
if (!groupKey) return { status: "error" };
|
||||
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
try {
|
||||
const stateResponse = await createStateCallback({ ...formData, group: groupKey });
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
const response = await createStateCallback({ ...formData, group: groupKey });
|
||||
if (shouldTrackEvents)
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state_group: groupKey,
|
||||
id: response.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
|
|
@ -60,14 +50,13 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
|||
return { status: "success" };
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents)
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state_group: groupKey,
|
||||
},
|
||||
});
|
||||
if (errorStatus?.status === 400) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EventProps, STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { StateForm } from "@/components/project-states";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
|
||||
type TStateUpdate = {
|
||||
state: IState;
|
||||
|
|
@ -19,8 +19,6 @@ type TStateUpdate = {
|
|||
|
||||
export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||
const { state, updateStateCallback, shouldTrackEvents, handleClose } = props;
|
||||
// hooks
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
|
|
@ -29,28 +27,20 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
|||
handleClose();
|
||||
};
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: Partial<IState>) => {
|
||||
if (!state.id) return { status: "error" };
|
||||
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
try {
|
||||
const stateResponse = await updateStateCallback(state.id, formData);
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
await updateStateCallback(state.id, formData);
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state_group: state.group,
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
|
|
@ -73,14 +63,15 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: "State could not be updated. Please try again.",
|
||||
});
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state_group: state.group,
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return { status: "error" };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { FC, useState, useRef } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { ChevronDown, Plus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
|
|
@ -81,6 +81,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
|
|||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_GROUP_ADD_BUTTON}
|
||||
className={cn(
|
||||
"flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-primary-100/80 hover:text-custom-primary-100",
|
||||
(!isEditable || createState) && "cursor-not-allowed text-custom-text-400 hover:text-custom-text-400"
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { FC, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { Loader, X } from "lucide-react";
|
||||
// plane imports
|
||||
import { EventProps, STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { AlertModalCore, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TStateDelete = {
|
||||
|
|
@ -23,45 +23,39 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
|||
const { totalStates, state, deleteStateCallback, shouldTrackEvents } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
// states
|
||||
const [isDeleteModal, setIsDeleteModal] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState(false);
|
||||
// derived values
|
||||
const isDeleteDisabled = state.default ? true : totalStates === 1 ? true : false;
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteState = async () => {
|
||||
if (isDeleteDisabled) return;
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
|
||||
setIsDelete(true);
|
||||
|
||||
try {
|
||||
await deleteStateCallback(state.id);
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setIsDelete(false);
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "FAILED",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (errorStatus.status === 400) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -107,6 +101,7 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
|||
)}
|
||||
disabled={isDeleteDisabled}
|
||||
onClick={() => setIsDeleteModal(true)}
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_DELETE_BUTTON}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import type { IState } from "@plane/types";
|
|||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useProjectState } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
|
||||
type TStateDeleteModal = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -24,8 +25,6 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
|||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { captureProjectStateEvent } = useEventTracker();
|
||||
const { deleteState } = useProjectState();
|
||||
|
||||
const handleClose = () => {
|
||||
|
|
@ -40,11 +39,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
|||
|
||||
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
||||
.then(() => {
|
||||
captureProjectStateEvent({
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "SUCCESS",
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
|
|
@ -63,11 +61,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
|||
title: "Error!",
|
||||
message: "State could not be deleted. Please try again.",
|
||||
});
|
||||
captureProjectStateEvent({
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "FAILED",
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { SetStateAction } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { GripVertical, Pencil } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// local imports
|
||||
|
|
@ -70,6 +70,7 @@ export const StateItemTitle = observer((props: TStateItemTitleProps) => {
|
|||
<button
|
||||
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
|
||||
onClick={() => setUpdateStateModal(true)}
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_EDIT_BUTTON}
|
||||
>
|
||||
<Pencil className="w-3 h-3" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserPermissions, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ProjectCard } from "@/components/project";
|
||||
import { ProjectsLoader } from "@/components/ui";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// assets
|
||||
import AllFiltersImage from "@/public/empty-state/project/all-filters.svg";
|
||||
|
|
@ -26,7 +27,6 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
loader,
|
||||
fetchStatus,
|
||||
|
|
@ -65,8 +65,8 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Project empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import type { IProject } from "@plane/types";
|
|||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type DeleteProjectModal = {
|
||||
|
|
@ -29,7 +30,6 @@ const defaultValues = {
|
|||
export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
||||
const { isOpen, project, onClose } = props;
|
||||
// store hooks
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { deleteProject } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
|
|
@ -62,9 +62,11 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
|||
if (projectId && projectId.toString() === project.id) router.push(`/${workspaceSlug}/projects`);
|
||||
|
||||
handleClose();
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.delete,
|
||||
payload: { ...project, state: "SUCCESS", element: "Project general settings" },
|
||||
payload: {
|
||||
id: project.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -73,9 +75,11 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
|||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureProjectEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.delete,
|
||||
payload: { ...project, state: "FAILED", element: "Project general settings" },
|
||||
payload: {
|
||||
id: project.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { FC, useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Info, Lock } from "lucide-react";
|
||||
import { NETWORK_CHOICES, PROJECT_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NETWORK_CHOICES, PROJECT_TRACKER_ELEMENTS, PROJECT_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IProject, IWorkspace } from "@plane/types";
|
||||
|
|
@ -27,7 +27,8 @@ import { TimezoneSelect } from "@/components/global";
|
|||
import { ProjectNetworkIcon } from "@/components/project";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
|
|
@ -46,7 +47,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// store hooks
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { updateProject } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
setValue,
|
||||
setError,
|
||||
reset,
|
||||
formState: { errors, dirtyFields },
|
||||
formState: { errors },
|
||||
getValues,
|
||||
} = useForm<IProject>({
|
||||
defaultValues: {
|
||||
|
|
@ -92,15 +92,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
if (!workspaceSlug || !project) return;
|
||||
return updateProject(workspaceSlug.toString(), project.id, payload)
|
||||
.then((res) => {
|
||||
const changed_properties = Object.keys(dirtyFields);
|
||||
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
changed_properties: changed_properties,
|
||||
state: "SUCCESS",
|
||||
element: "Project general settings",
|
||||
id: projectId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
|
|
@ -110,9 +105,11 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureProjectEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, state: "FAILED", element: "Project general settings" },
|
||||
payload: {
|
||||
id: projectId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -397,7 +394,13 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
</div>
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<>
|
||||
<Button variant="primary" type="submit" loading={isLoading} disabled={!isAdmin}>
|
||||
<Button
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.UPDATE_PROJECT_BUTTON}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{isLoading ? `${t("updating")}...` : t("common.update_project")}
|
||||
</Button>
|
||||
<span className="text-sm italic text-custom-sidebar-text-400">
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ import { observer } from "mobx-react";
|
|||
import { usePathname } from "next/navigation";
|
||||
import { Briefcase } from "lucide-react";
|
||||
// i18n
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
// components
|
||||
import HeaderFilters from "./filters";
|
||||
|
|
@ -22,7 +23,6 @@ export const ProjectsBaseHeader = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
|
@ -57,9 +57,9 @@ export const ProjectsBaseHeader = observer(() => {
|
|||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("Projects page");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.CREATE_HEADER_BUTTON}
|
||||
className="items-center gap-1"
|
||||
>
|
||||
<span className="hidden sm:inline-block">{t("workspace_projects.create.label")}</span>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ import { IProject } from "@plane/types";
|
|||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type FormData = {
|
||||
|
|
@ -39,7 +40,6 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
|||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { leaveProject } = useUserPermissions();
|
||||
|
||||
const {
|
||||
|
|
@ -64,21 +64,26 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
|||
return leaveProject(workspaceSlug.toString(), project.id)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: project.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: project.id,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "FAILED",
|
||||
element: "Project settings members page",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import { TOAST_TYPE, Table, setToast } from "@plane/ui";
|
|||
// components
|
||||
import { ConfirmProjectMemberRemove } from "@/components/project";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web imports
|
||||
import { useProjectColumns } from "@/plane-web/components/projects/settings/useProjectColumns";
|
||||
|
|
@ -30,7 +31,6 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||
const {
|
||||
project: { removeMemberFromProject },
|
||||
} = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// helper hooks
|
||||
const { columns, removeMemberModal, setRemoveMemberModal } = useProjectColumns({
|
||||
projectId,
|
||||
|
|
@ -44,18 +44,27 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||
await leaveProject(workspaceSlug.toString(), projectId.toString())
|
||||
.then(async () => {
|
||||
router.push(`/${workspaceSlug}/projects`);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: projectId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) =>
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: projectId,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "You can’t leave this project yet.",
|
||||
message: err?.error || "Something went wrong. Please try again.",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} else
|
||||
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), memberId).catch((err) =>
|
||||
setToast({
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { Search } from "lucide-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectMemberListItem, SendProjectInvitationModal } from "@/components/project";
|
||||
import { MembersSettingsLoader } from "@/components/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type TProjectMemberListProps = {
|
||||
projectId: string;
|
||||
|
|
@ -23,8 +23,6 @@ export const ProjectMemberList: React.FC<TProjectMemberListProps> = observer((pr
|
|||
// states
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
project: { projectMemberIds, getProjectMemberDetails },
|
||||
} = useMember();
|
||||
|
|
@ -73,9 +71,9 @@ export const ProjectMemberList: React.FC<TProjectMemberListProps> = observer((pr
|
|||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_MEMBERS_PAGE_HEADER");
|
||||
setInviteModal(true);
|
||||
}}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
|
||||
>
|
||||
{t("add_member")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast
|
|||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -45,7 +46,6 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
|
||||
const {
|
||||
project: { getProjectMemberDetails, bulkAddMembersToProject },
|
||||
|
|
@ -86,22 +86,22 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Members added successfully.",
|
||||
});
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.add, {
|
||||
members: [
|
||||
...payload.members.map((member) => ({
|
||||
member_id: member.member_id,
|
||||
role: ROLE[member.role],
|
||||
})),
|
||||
],
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.add,
|
||||
payload: {
|
||||
members: [...payload.members.map((member) => member.member_id)],
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.add, {
|
||||
state: "FAILED",
|
||||
element: "Project settings members page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.add,
|
||||
payload: {
|
||||
members: [...payload.members.map((member) => member.member_id)],
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React from "react";
|
|||
import { ChevronRight, ChevronUp } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Loader } from "@plane/ui";
|
||||
|
|
@ -43,7 +44,11 @@ export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) =>
|
|||
<div>
|
||||
{projectDetails ? (
|
||||
<div>
|
||||
<Button variant="danger" onClick={handleDelete}>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleDelete}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.DELETE_PROJECT_BUTTON}
|
||||
>
|
||||
Delete my project
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
import { ToggleSwitch, Tooltip, setPromiseToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { SettingsHeading } from "@/components/settings";
|
||||
import { useEventTracker, useProject, useUser } from "@/hooks/store";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { UpgradeBadge } from "@/plane-web/components/workspace";
|
||||
// plane web constants
|
||||
|
|
@ -23,7 +24,6 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
const { workspaceSlug, projectId, isAdmin } = props;
|
||||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
const { getProjectById, updateProject } = useProject();
|
||||
// derived values
|
||||
|
|
@ -32,12 +32,6 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
const handleSubmit = async (featureKey: string, featureProperty: string) => {
|
||||
if (!workspaceSlug || !projectId || !currentProjectDetails) return;
|
||||
|
||||
// capturing event
|
||||
captureEvent(`Toggle ${featureKey}`, {
|
||||
enabled: !currentProjectDetails?.[featureProperty as keyof IProject],
|
||||
element: "Project settings feature page",
|
||||
});
|
||||
|
||||
// making the request to update the project feature
|
||||
const settingsPayload = {
|
||||
[featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject],
|
||||
|
|
@ -92,6 +86,7 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
|
||||
disabled={!featureItem.isEnabled || !isAdmin}
|
||||
size="sm"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.TOGGLE_FEATURE}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-14">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { CircleMinus } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EUserProjectRoles, IUser, IWorkspaceMember, TProjectMembership } from "@plane/types";
|
||||
import { CustomMenu, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
|
|
@ -70,6 +70,7 @@ export const NameColumn: React.FC<NameProps> = (props) => {
|
|||
<CustomMenu.MenuItem>
|
||||
<div
|
||||
className="flex items-center gap-x-1 cursor-pointer text-red-600 font-medium"
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.PROJECT_MEMBER_TABLE_CONTEXT_MENU}
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
>
|
||||
<CircleMinus className="flex-shrink-0 size-3.5" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { SetStateAction, useEffect, useState } from "react";
|
||||
import { GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button } from "@plane/ui";
|
||||
import { LockedComponent } from "../icons/locked-component";
|
||||
|
||||
|
|
@ -58,7 +59,13 @@ export const UpdateViewComponent = (props: Props) => {
|
|||
!areFiltersEqual &&
|
||||
isAuthorizedUser && (
|
||||
<>
|
||||
<Button variant="outline-primary" size="md" className="flex-shrink-0" onClick={() => setIsModalOpen(true)}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="md"
|
||||
className="flex-shrink-0"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.HEADER_SAVE_VIEW_BUTTON}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
Save as
|
||||
</Button>
|
||||
{isOwner && <>{updateButton}</>}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@ import { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { CheckCheck, RefreshCw } from "lucide-react";
|
||||
// plane imports
|
||||
import { ENotificationLoader, ENotificationQueryParamType, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
ENotificationLoader,
|
||||
ENotificationQueryParamType,
|
||||
NOTIFICATION_TRACKER_ELEMENTS,
|
||||
NOTIFICATION_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Spinner, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationFilter, NotificationHeaderMenuOption } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TNotificationSidebarHeaderOptions = {
|
||||
|
|
@ -21,7 +27,6 @@ export const NotificationSidebarHeaderOptions: FC<TNotificationSidebarHeaderOpti
|
|||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { loader, getNotifications, markAllNotificationsAsRead } = useWorkspaceNotifications();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const refreshNotifications = async () => {
|
||||
|
|
@ -49,8 +54,11 @@ export const NotificationSidebarHeaderOptions: FC<TNotificationSidebarHeaderOpti
|
|||
<Tooltip tooltipContent={t("notification.options.mark_all_as_read")} isMobile={isMobile} position="bottom">
|
||||
<div
|
||||
className="flex-shrink-0 w-5 h-5 flex justify-center items-center overflow-hidden cursor-pointer transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.MARK_ALL_AS_READ_BUTTON}
|
||||
onClick={() => {
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read);
|
||||
captureSuccess({
|
||||
eventName: NOTIFICATION_TRACKER_EVENTS.all_marked_read,
|
||||
});
|
||||
handleMarkAllNotificationsAsRead();
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ArchiveIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationItemOptionButton } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
// store
|
||||
import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
|
|
@ -22,7 +23,6 @@ type TNotificationItemArchiveOption = {
|
|||
export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> = observer((props) => {
|
||||
const { workspaceSlug, notification } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
const { asJson: data, archiveNotification, unArchiveNotification } = notification;
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -31,10 +31,12 @@ export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> =
|
|||
try {
|
||||
const request = data.archived_at ? unArchiveNotification : archiveNotification;
|
||||
await request(workspaceSlug);
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.archive, {
|
||||
issue_id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
title: data.archived_at ? t("notification.toasts.unarchived") : t("notification.toasts.archived"),
|
||||
|
|
@ -42,11 +44,19 @@ export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> =
|
|||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
captureError({
|
||||
eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationItemOptionButton
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.ARCHIVE_UNARCHIVE_BUTTON}
|
||||
tooltipContent={
|
||||
data.archived_at ? t("notification.options.mark_unarchive") : t("notification.options.mark_archive")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MessageSquare } from "lucide-react";
|
||||
import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationItemOptionButton } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
// store
|
||||
import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
|
|
@ -22,7 +23,6 @@ type TNotificationItemReadOption = {
|
|||
export const NotificationItemReadOption: FC<TNotificationItemReadOption> = observer((props) => {
|
||||
const { workspaceSlug, notification } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
const { asJson: data, markNotificationAsRead, markNotificationAsUnRead } = notification;
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -31,10 +31,12 @@ export const NotificationItemReadOption: FC<TNotificationItemReadOption> = obser
|
|||
try {
|
||||
const request = data.read_at ? markNotificationAsUnRead : markNotificationAsRead;
|
||||
await request(workspaceSlug);
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read, {
|
||||
issue_id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
title: data.read_at ? t("notification.toasts.unread") : t("notification.toasts.read"),
|
||||
|
|
@ -42,11 +44,19 @@ export const NotificationItemReadOption: FC<TNotificationItemReadOption> = obser
|
|||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
captureError({
|
||||
eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationItemOptionButton
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.MARK_READ_UNREAD_BUTTON}
|
||||
tooltipContent={data.read_at ? t("notification.options.mark_unread") : t("notification.options.mark_read")}
|
||||
callBack={handleNotificationUpdate}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@
|
|||
import { Dispatch, SetStateAction, useEffect, useState, FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS, WORKSPACE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
ORGANIZATION_SIZE,
|
||||
RESTRICTED_URLS,
|
||||
WORKSPACE_TRACKER_ELEMENTS,
|
||||
WORKSPACE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// constants
|
||||
// types
|
||||
|
|
@ -11,7 +16,8 @@ import { IWorkspace } from "@plane/types";
|
|||
// ui
|
||||
import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
|
@ -51,7 +57,6 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||
// router
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { createWorkspace } = useWorkspace();
|
||||
// form info
|
||||
const {
|
||||
|
|
@ -71,13 +76,9 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||
|
||||
await createWorkspace(formData)
|
||||
.then(async (res) => {
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
element: "Create workspace page",
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -88,12 +89,10 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||
if (onSubmit) await onSubmit(res);
|
||||
})
|
||||
.catch(() => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
element: "Create workspace page",
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
error: new Error("Error creating workspace"),
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -248,7 +247,14 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||
|
||||
<div className="flex items-center gap-4">
|
||||
{secondaryButton}
|
||||
<Button variant="primary" type="submit" size="md" disabled={!isValid} loading={isSubmitting}>
|
||||
<Button
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.CREATE_WORKSPACE_BUTTON}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="md"
|
||||
disabled={!isValid}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? t(primaryButtonText.loading) : t(primaryButtonText.default)}
|
||||
</Button>
|
||||
{!secondaryButton && (
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
|||
// constants
|
||||
// hooks
|
||||
import { cn } from "@plane/utils";
|
||||
import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -31,7 +32,6 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
|||
// router
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { deleteWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
||||
|
|
@ -64,13 +64,9 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
|||
await fetchCurrentUserSettings();
|
||||
handleClose();
|
||||
router.push(getWorkspaceRedirectionUrl());
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: data.slug },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -84,13 +80,10 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
|||
title: t("workspace_settings.settings.general.delete_modal.error_title"),
|
||||
message: t("workspace_settings.settings.general.delete_modal.error_message"),
|
||||
});
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "FAILED",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: data.slug },
|
||||
error: new Error("Error deleting workspace"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { ChevronDown, LinkIcon, Trash2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { CustomSelect, TOAST_TYPE, setToast, TContextMenuItem, CustomMenu } from "@plane/ui";
|
||||
import { cn, copyTextToClipboard } from "@plane/utils";
|
||||
// components
|
||||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -93,7 +94,12 @@ export const WorkspaceInvitationsListItem: FC<Props> = observer((props) => {
|
|||
},
|
||||
{
|
||||
key: "remove",
|
||||
action: () => setRemoveMemberModal(true),
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: MEMBER_TRACKER_ELEMENTS.WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU,
|
||||
});
|
||||
setRemoveMemberModal(true);
|
||||
},
|
||||
title: t("common.remove"),
|
||||
icon: Trash2,
|
||||
shouldRender: isAdmin,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { Trash2 } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IUser, IWorkspaceMember } from "@plane/types";
|
||||
// plane ui
|
||||
import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -74,6 +74,7 @@ export const NameColumn: React.FC<NameProps> = (props) => {
|
|||
<div
|
||||
className="flex items-center gap-x-3 cursor-pointer"
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.WORKSPACE_MEMBER_TABLE_CONTEXT_MENU}
|
||||
>
|
||||
<Trash2 className="size-3.5 align-middle" /> {id === currentUser?.id ? "Leave " : "Remove "}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import { MembersLayoutLoader } from "@/components/ui/loader/layouts/members-layo
|
|||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useMemberColumns } from "@/plane-web/components/workspace/settings/useMemberColumns";
|
||||
|
||||
|
|
@ -32,7 +33,6 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||
workspace: { removeMemberFromWorkspace },
|
||||
} = useMember();
|
||||
const { leaveWorkspace } = useUserPermissions();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -45,18 +45,27 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||
.then(async () => {
|
||||
await fetchCurrentUserSettings();
|
||||
router.push(getWorkspaceRedirectionUrl());
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.workspace.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Workspace settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
|
||||
payload: {
|
||||
workspace: workspaceSlug,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err: any) =>
|
||||
.catch((err: any) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
|
||||
payload: {
|
||||
workspace: workspaceSlug,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.error || t("something_went_wrong_please_try_again"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (memberId: string) => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ import { observer } from "mobx-react";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Pencil } from "lucide-react";
|
||||
// constants
|
||||
import { ORGANIZATION_SIZE, EUserPermissions, EUserPermissionsLevel, WORKSPACE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
ORGANIZATION_SIZE,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
WORKSPACE_TRACKER_EVENTS,
|
||||
WORKSPACE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspace } from "@plane/types";
|
||||
import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -15,7 +21,8 @@ import { LogoSpinner } from "@/components/common";
|
|||
import { WorkspaceImageUploadModal } from "@/components/core";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { DeleteWorkspaceSection } from "@/plane-web/components/workspace";
|
||||
|
||||
|
|
@ -31,7 +38,6 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { currentWorkspace, updateWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -61,13 +67,9 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
|
||||
await updateWorkspace(currentWorkspace.slug, payload)
|
||||
.then((res) => {
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
});
|
||||
setToast({
|
||||
title: "Success!",
|
||||
|
|
@ -76,12 +78,10 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
error: err,
|
||||
});
|
||||
console.error(err);
|
||||
});
|
||||
|
|
@ -282,7 +282,12 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||
|
||||
{isAdmin && (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isLoading}>
|
||||
<Button
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.UPDATE_WORKSPACE_BUTTON}
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? t("updating") : t("workspace_settings.settings.general.update_workspace")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { createRoot } from "react-dom/client";
|
|||
import { LinkIcon, Settings, Share2, LogOut, MoreHorizontal, ChevronRight } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// plane helpers
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
|
|
@ -23,7 +23,7 @@ import { Logo } from "@/components/common/logo";
|
|||
import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane-web components
|
||||
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
|
||||
|
|
@ -59,7 +59,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
// store hooks
|
||||
const { sidebarCollapsed } = useAppTheme();
|
||||
const { t } = useTranslation();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { getPartialProjectById } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
|
@ -97,7 +96,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
);
|
||||
|
||||
const handleLeaveProject = () => {
|
||||
setTrackElement("APP_SIDEBAR_PROJECT_DROPDOWN");
|
||||
setLeaveProjectModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -376,7 +374,10 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
</CustomMenu.MenuItem>
|
||||
{/* leave project */}
|
||||
{!isAuthorized && (
|
||||
<CustomMenu.MenuItem onClick={handleLeaveProject}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={handleLeaveProject}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.SIDEBAR_PROJECT_QUICK_ACTIONS}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LogOut className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||
<span>{t("leave_project")}</span>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
import { Briefcase, ChevronRight, Plus } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Loader, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
|
|
@ -17,7 +17,7 @@ import { CreateProjectModal } from "@/components/project";
|
|||
import { SidebarProjectsListItem } from "@/components/workspace";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types";
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { sidebarCollapsed } = useAppTheme();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { loader, getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject();
|
||||
|
|
@ -193,9 +192,9 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_TOOLTIP}
|
||||
className="p-0.5 rounded hover:bg-custom-sidebar-background-80 flex-shrink-0"
|
||||
onClick={() => {
|
||||
setTrackElement(`APP_SIDEBAR_JOINED_BLOCK`);
|
||||
setIsProjectModalOpen(true);
|
||||
}}
|
||||
aria-label={t("aria_labels.projects_sidebar.create_new_project")}
|
||||
|
|
@ -277,8 +276,8 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
"p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Sidebar");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useRef, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { PenSquare } from "lucide-react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, SIDEBAR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
|
|
@ -12,7 +12,7 @@ import { CreateUpdateIssueModal } from "@/components/issues";
|
|||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
// plane web components
|
||||
import { AppSearch } from "@/plane-web/components/workspace";
|
||||
|
|
@ -31,7 +31,6 @@ export const SidebarQuickActions = observer(() => {
|
|||
// store hooks
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { joinedProjectIds } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// local storage
|
||||
|
|
@ -89,8 +88,8 @@ export const SidebarQuickActions = observer(() => {
|
|||
"px-3 border-[0.5px] border-custom-sidebar-border-300": !isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
data-ph-element={SIDEBAR_TRACKER_ELEMENTS.CREATE_WORK_ITEM_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("APP_SIDEBAR_QUICK_ACTIONS");
|
||||
toggleCreateIssueModal(true);
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, SIDEBAR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, SIDEBAR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { usePlatformOS } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
|
|
@ -12,7 +12,8 @@ import { Tooltip } from "@plane/ui";
|
|||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
import { NotificationAppSidebarOption } from "@/components/workspace-notifications";
|
||||
// hooks
|
||||
import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useAppTheme, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
export interface SidebarUserMenuItemProps {
|
||||
item: {
|
||||
|
|
@ -34,7 +35,6 @@ export const SidebarUserMenuItem: FC<SidebarUserMenuItemProps> = observer((props
|
|||
// package hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
|
@ -50,8 +50,11 @@ export const SidebarUserMenuItem: FC<SidebarUserMenuItemProps> = observer((props
|
|||
if (window.innerWidth < 768) {
|
||||
toggleSidebar();
|
||||
}
|
||||
captureEvent(SIDEBAR_TRACKER_EVENTS.click, {
|
||||
destination: itemKey,
|
||||
captureClick({
|
||||
elementName: SIDEBAR_TRACKER_ELEMENTS.USER_MENU_ITEM,
|
||||
context: {
|
||||
destination: itemKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import React, { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { GLOBAL_VIEW_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { GLOBAL_VIEW_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IWorkspaceView } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useGlobalView, useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
data: IWorkspaceView;
|
||||
|
|
@ -26,7 +27,6 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { deleteGlobalView } = useGlobalView();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const handleClose = () => onClose();
|
||||
|
||||
|
|
@ -37,15 +37,20 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
|||
|
||||
await deleteGlobalView(workspaceSlug.toString(), data.id)
|
||||
.then(() => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, {
|
||||
view_id: data.id,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
view_id: data.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error: any) => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, {
|
||||
view_id: data.id,
|
||||
state: "FAILED",
|
||||
captureError({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
view_id: data.id,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import {
|
|||
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
GLOBAL_VIEW_TOUR_TRACKER_EVENTS,
|
||||
GLOBAL_VIEW_TRACKER_ELEMENTS,
|
||||
GLOBAL_VIEW_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { TStaticViewTypes } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -20,7 +21,8 @@ import {
|
|||
} from "@/components/workspace";
|
||||
// constants
|
||||
// store hooks
|
||||
import { useEventTracker, useGlobalView, useUserPermissions } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
const ViewTab = observer((props: { viewId: string }) => {
|
||||
const { viewId } = props;
|
||||
|
|
@ -72,16 +74,17 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||
const { currentWorkspaceViews } = useGlobalView();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
// bring the active view to the centre of the header
|
||||
useEffect(() => {
|
||||
if (globalViewId && currentWorkspaceViews) {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.open, {
|
||||
view_id: globalViewId,
|
||||
view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||
? "Default"
|
||||
: "Custom",
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.open,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||
? "Default"
|
||||
: "Custom",
|
||||
},
|
||||
});
|
||||
const activeTabElement = document.querySelector(`#global-view-${globalViewId.toString()}`);
|
||||
if (activeTabElement && containerRef.current) {
|
||||
|
|
@ -91,7 +94,7 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||
activeTabElement.scrollIntoView({ behavior: "smooth", inline: diff > 500 ? "center" : "nearest" });
|
||||
}
|
||||
}
|
||||
}, [globalViewId, currentWorkspaceViews, containerRef, captureEvent]);
|
||||
}, [globalViewId, currentWorkspaceViews, containerRef]);
|
||||
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
|
|
@ -115,6 +118,7 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||
{isAuthorizedUser ? (
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
className="sticky -right-4 flex flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 py-3 hover:border-custom-border-200 hover:text-custom-text-400"
|
||||
onClick={() => setCreateViewModal(true)}
|
||||
>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue