[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
|
|
@ -1,163 +1,46 @@
|
|||
export type IssueEventProps = {
|
||||
eventName: string;
|
||||
payload: any;
|
||||
updates?: any;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type EventProps = {
|
||||
eventName: string;
|
||||
payload: any;
|
||||
updates?: any;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export const getWorkspaceEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
organization_size: payload.organization_size,
|
||||
first_time: payload.first_time,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getProjectEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.id,
|
||||
identifier: payload.identifier,
|
||||
project_visibility: payload.network == 2 ? "Public" : "Private",
|
||||
changed_properties: payload.changed_properties,
|
||||
lead_id: payload.project_lead,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getCycleEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.project,
|
||||
cycle_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
start_date: payload.start_date,
|
||||
target_date: payload.target_date,
|
||||
cycle_status: payload.status,
|
||||
changed_properties: payload.changed_properties,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getModuleEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.project,
|
||||
module_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
start_date: payload.start_date,
|
||||
target_date: payload.target_date,
|
||||
module_status: payload.status,
|
||||
lead_id: payload.lead,
|
||||
changed_properties: payload.changed_properties,
|
||||
member_ids: payload.members,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getPageEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.project,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
access: payload.access === 0 ? "Public" : "Private",
|
||||
is_locked: payload.is_locked,
|
||||
archived_at: payload.archived_at,
|
||||
created_by: payload.created_by,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getIssueEventPayload = (props: IssueEventProps) => {
|
||||
const { eventName, payload, updates, path } = props;
|
||||
let eventPayload: any = {
|
||||
issue_id: payload.id,
|
||||
estimate_point: payload.estimate_point,
|
||||
link_count: payload.link_count,
|
||||
target_date: payload.target_date,
|
||||
is_draft: payload.is_draft,
|
||||
label_ids: payload.label_ids,
|
||||
assignee_ids: payload.assignee_ids,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
sequence_id: payload.sequence_id,
|
||||
module_ids: payload.module_ids,
|
||||
sub_issues_count: payload.sub_issues_count,
|
||||
parent_id: payload.parent_id,
|
||||
project_id: payload.project_id,
|
||||
workspace_id: payload.workspace_id,
|
||||
priority: payload.priority,
|
||||
state_id: payload.state_id,
|
||||
start_date: payload.start_date,
|
||||
attachment_count: payload.attachment_count,
|
||||
cycle_id: payload.cycle_id,
|
||||
module_id: payload.module_id,
|
||||
archived_at: payload.archived_at,
|
||||
state: payload.state,
|
||||
view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "",
|
||||
};
|
||||
|
||||
if (eventName === WORK_ITEM_TRACKER_EVENTS.update) {
|
||||
eventPayload = {
|
||||
...eventPayload,
|
||||
...updates,
|
||||
updated_from: props.path?.includes("workspace-views")
|
||||
? "All views"
|
||||
: props.path?.includes("cycles")
|
||||
? "Cycle"
|
||||
: props.path?.includes("modules")
|
||||
? "Module"
|
||||
: props.path?.includes("views")
|
||||
? "Project view"
|
||||
: props.path?.includes("inbox")
|
||||
? "Inbox"
|
||||
: props.path?.includes("draft")
|
||||
? "Draft"
|
||||
: "Project",
|
||||
};
|
||||
}
|
||||
return eventPayload;
|
||||
};
|
||||
|
||||
export const getProjectStateEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.id,
|
||||
state_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
group: payload.group,
|
||||
color: payload.color,
|
||||
default: payload.default,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
// Dashboard Events
|
||||
export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected";
|
||||
export const HEADER_GITHUB_ICON = "header_github_icon";
|
||||
|
||||
// Groups
|
||||
export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics";
|
||||
|
||||
// Command palette tracker
|
||||
export const COMMAND_PALETTE_TRACKER_ELEMENTS = {
|
||||
COMMAND_PALETTE_SHORTCUT_KEY: "command_palette_shortcut_key",
|
||||
};
|
||||
|
||||
export const WORKSPACE_TRACKER_EVENTS = {
|
||||
create: "workspace_created",
|
||||
update: "workspace_updated",
|
||||
delete: "workspace_deleted",
|
||||
};
|
||||
export const WORKSPACE_TRACKER_ELEMENTS = {
|
||||
DELETE_WORKSPACE_BUTTON: "delete_workspace_button",
|
||||
ONBOARDING_CREATE_WORKSPACE_BUTTON: "onboarding_create_workspace_button",
|
||||
CREATE_WORKSPACE_BUTTON: "create_workspace_button",
|
||||
UPDATE_WORKSPACE_BUTTON: "update_workspace_button",
|
||||
};
|
||||
|
||||
export const PROJECT_TRACKER_EVENTS = {
|
||||
create: "project_created",
|
||||
update: "project_updated",
|
||||
delete: "project_deleted",
|
||||
};
|
||||
export const PROJECT_TRACKER_ELEMENTS = {
|
||||
EXTENDED_SIDEBAR_ADD_BUTTON: "extended_sidebar_add_project_button",
|
||||
SIDEBAR_CREATE_PROJECT_BUTTON: "sidebar_create_project_button",
|
||||
SIDEBAR_CREATE_PROJECT_TOOLTIP: "sidebar_create_project_tooltip",
|
||||
COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_project_button",
|
||||
COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_project_button",
|
||||
EMPTY_STATE_CREATE_PROJECT_BUTTON: "empty_state_create_project_button",
|
||||
CREATE_HEADER_BUTTON: "create_project_header_button",
|
||||
CREATE_FIRST_PROJECT_BUTTON: "create_first_project_button",
|
||||
DELETE_PROJECT_BUTTON: "delete_project_button",
|
||||
UPDATE_PROJECT_BUTTON: "update_project_button",
|
||||
CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE: "create_project_jira_import_detail_page",
|
||||
TOGGLE_FEATURE: "toggle_project_feature",
|
||||
};
|
||||
|
||||
export const CYCLE_TRACKER_EVENTS = {
|
||||
create: "cycle_created",
|
||||
|
|
@ -165,7 +48,18 @@ export const CYCLE_TRACKER_EVENTS = {
|
|||
delete: "cycle_deleted",
|
||||
favorite: "cycle_favorited",
|
||||
unfavorite: "cycle_unfavorited",
|
||||
archive: "cycle_archived",
|
||||
restore: "cycle_restored",
|
||||
};
|
||||
export const CYCLE_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "right_header_add_cycle_button",
|
||||
EMPTY_STATE_ADD_BUTTON: "empty_state_add_cycle_button",
|
||||
COMMAND_PALETTE_ADD_ITEM: "command_palette_add_cycle_item",
|
||||
RIGHT_SIDEBAR: "cycle_right_sidebar",
|
||||
QUICK_ACTIONS: "cycle_quick_actions",
|
||||
CONTEXT_MENU: "cycle_context_menu",
|
||||
LIST_ITEM: "cycle_list_item",
|
||||
} as const;
|
||||
|
||||
export const MODULE_TRACKER_EVENTS = {
|
||||
create: "module_created",
|
||||
|
|
@ -173,32 +67,120 @@ export const MODULE_TRACKER_EVENTS = {
|
|||
delete: "module_deleted",
|
||||
favorite: "module_favorited",
|
||||
unfavorite: "module_unfavorited",
|
||||
archive: "module_archived",
|
||||
restore: "module_restored",
|
||||
link: {
|
||||
create: "module_link_created",
|
||||
update: "module_link_updated",
|
||||
delete: "module_link_deleted",
|
||||
},
|
||||
};
|
||||
export const MODULE_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "right_header_add_module_button",
|
||||
EMPTY_STATE_ADD_BUTTON: "empty_state_add_module_button",
|
||||
COMMAND_PALETTE_ADD_ITEM: "command_palette_add_module_item",
|
||||
RIGHT_SIDEBAR: "module_right_sidebar",
|
||||
QUICK_ACTIONS: "module_quick_actions",
|
||||
CONTEXT_MENU: "module_context_menu",
|
||||
LIST_ITEM: "module_list_item",
|
||||
CARD_ITEM: "module_card_item",
|
||||
} as const;
|
||||
|
||||
export const WORK_ITEM_TRACKER_EVENTS = {
|
||||
create: "work_item_created",
|
||||
add_existing: "work_item_add_existing",
|
||||
update: "work_item_updated",
|
||||
delete: "work_item_deleted",
|
||||
archive: "work_item_archived",
|
||||
restore: "work_item_restored",
|
||||
attachment: {
|
||||
add: "work_item_attachment_added",
|
||||
remove: "work_item_attachment_removed",
|
||||
},
|
||||
sub_issue: {
|
||||
update: "sub_issue_updated",
|
||||
remove: "sub_issue_removed",
|
||||
delete: "sub_issue_deleted",
|
||||
create: "sub_issue_created",
|
||||
add_existing: "sub_issue_add_existing",
|
||||
},
|
||||
draft: {
|
||||
create: "draft_work_item_created",
|
||||
},
|
||||
};
|
||||
export const WORK_ITEM_TRACKER_ELEMENTS = {
|
||||
HEADER_ADD_BUTTON: {
|
||||
WORK_ITEMS: "work_items_header_add_work_item_button",
|
||||
PROJECT_VIEW: "project_view_header_add_work_item_button",
|
||||
CYCLE: "cycle_header_add_work_item_button",
|
||||
MODULE: "module_header_add_work_item_button",
|
||||
},
|
||||
COMMAND_PALETTE_ADD_BUTTON: "command_palette_add_work_item_button",
|
||||
EMPTY_STATE_ADD_BUTTON: {
|
||||
WORK_ITEMS: "work_items_empty_state_add_work_item_button",
|
||||
PROJECT_VIEW: "project_view_empty_state_add_work_item_button",
|
||||
CYCLE: "cycle_empty_state_add_work_item_button",
|
||||
MODULE: "module_empty_state_add_work_item_button",
|
||||
GLOBAL_VIEW: "global_view_empty_state_add_work_item_button",
|
||||
},
|
||||
QUICK_ACTIONS: {
|
||||
WORK_ITEMS: "work_items_quick_actions",
|
||||
PROJECT_VIEW: "project_view_work_items_quick_actions",
|
||||
CYCLE: "cycle_work_items_quick_actions",
|
||||
MODULE: "module_work_items_quick_actions",
|
||||
GLOBAL_VIEW: "global_view_work_items_quick_actions",
|
||||
ARCHIVED: "archived_work_items_quick_actions",
|
||||
DRAFT: "draft_work_items_quick_actions",
|
||||
},
|
||||
CONTEXT_MENU: {
|
||||
WORK_ITEMS: "work_items_context_menu",
|
||||
PROJECT_VIEW: "project_view_context_menu",
|
||||
CYCLE: "cycle_context_menu",
|
||||
MODULE: "module_context_menu",
|
||||
GLOBAL_VIEW: "global_view_context_menu",
|
||||
ARCHIVED: "archived_context_menu",
|
||||
DRAFT: "draft_context_menu",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const STATE_TRACKER_EVENTS = {
|
||||
create: "state_created",
|
||||
update: "state_updated",
|
||||
delete: "state_deleted",
|
||||
};
|
||||
export const STATE_TRACKER_ELEMENTS = {
|
||||
STATE_GROUP_ADD_BUTTON: "state_group_add_button",
|
||||
STATE_LIST_DELETE_BUTTON: "state_list_delete_button",
|
||||
STATE_LIST_EDIT_BUTTON: "state_list_edit_button",
|
||||
};
|
||||
|
||||
export const PROJECT_PAGE_TRACKER_EVENTS = {
|
||||
create: "project_page_created",
|
||||
update: "project_page_updated",
|
||||
delete: "project_page_deleted",
|
||||
archive: "project_page_archived",
|
||||
restore: "project_page_restored",
|
||||
lock: "project_page_locked",
|
||||
unlock: "project_page_unlocked",
|
||||
access_update: "project_page_access_updated",
|
||||
duplicate: "project_page_duplicated",
|
||||
favorite: "project_page_favorited",
|
||||
unfavorite: "project_page_unfavorited",
|
||||
move: "project_page_moved",
|
||||
};
|
||||
export const PROJECT_PAGE_TRACKER_ELEMENTS = {
|
||||
COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_page_button",
|
||||
EMPTY_STATE_CREATE_BUTTON: "empty_state_create_page_button",
|
||||
COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_page_button",
|
||||
CONTEXT_MENU: "page_context_menu",
|
||||
QUICK_ACTIONS: "page_quick_actions",
|
||||
LIST_ITEM: "page_list_item",
|
||||
FAVORITE_BUTTON: "page_favorite_button",
|
||||
ARCHIVE_BUTTON: "page_archive_button",
|
||||
LOCK_BUTTON: "page_lock_button",
|
||||
ACCESS_TOGGLE: "page_access_toggle",
|
||||
DUPLICATE_BUTTON: "page_duplicate_button",
|
||||
} as const;
|
||||
|
||||
export const MEMBER_TRACKER_EVENTS = {
|
||||
invite: "member_invited",
|
||||
|
|
@ -211,48 +193,78 @@ export const MEMBER_TRACKER_EVENTS = {
|
|||
leave: "workspace_member_left",
|
||||
},
|
||||
};
|
||||
export const MEMBER_TRACKER_ELEMENTS = {
|
||||
HEADER_ADD_BUTTON: "header_add_member_button",
|
||||
ACCEPT_INVITATION_BUTTON: "accept_invitation_button",
|
||||
ONBOARDING_JOIN_WORKSPACE: "workspace_join_continue_to_workspace_button",
|
||||
ONBOARDING_INVITE_MEMBER: "invite_member_continue_button",
|
||||
SIDEBAR_PROJECT_QUICK_ACTIONS: "sidebar_project_quick_actions",
|
||||
PROJECT_MEMBER_TABLE_CONTEXT_MENU: "project_member_table_context_menu",
|
||||
WORKSPACE_MEMBER_TABLE_CONTEXT_MENU: "workspace_member_table_context_menu",
|
||||
WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU: "workspace_invitations_list_context_menu",
|
||||
} as const;
|
||||
|
||||
export const AUTH_TRACKER_EVENTS = {
|
||||
navigate: {
|
||||
sign_up: "navigate_to_sign_up_page",
|
||||
sign_in: "navigate_to_sign_in_page",
|
||||
},
|
||||
code_verify: "code_verified",
|
||||
sign_up_with_password: "sign_up_with_password",
|
||||
sign_in_with_password: "sign_in_with_password",
|
||||
sign_in_with_code: "sign_in_with_magic_link",
|
||||
forgot_password: "forgot_password_clicked",
|
||||
new_code_requested: "new_code_requested",
|
||||
};
|
||||
export const AUTH_TRACKER_ELEMENTS = {
|
||||
NAVIGATE_TO_SIGN_UP: "navigate_to_sign_up",
|
||||
FORGOT_PASSWORD_FROM_SIGNIN: "forgot_password_from_signin",
|
||||
SIGNUP_FROM_FORGOT_PASSWORD: "signup_from_forgot_password",
|
||||
SIGN_IN_FROM_SIGNUP: "sign_in_from_signup",
|
||||
SIGN_IN_WITH_UNIQUE_CODE: "sign_in_with_unique_code",
|
||||
REQUEST_NEW_CODE: "request_new_code",
|
||||
VERIFY_CODE: "verify_code",
|
||||
};
|
||||
|
||||
export const PRODUCT_TOUR_TRACKER_EVENTS = {
|
||||
start: "product_tour_started",
|
||||
complete: "product_tour_completed",
|
||||
skip: "product_tour_skipped",
|
||||
};
|
||||
|
||||
export const GLOBAL_VIEW_TOUR_TRACKER_EVENTS = {
|
||||
export const GLOBAL_VIEW_TRACKER_EVENTS = {
|
||||
create: "global_view_created",
|
||||
update: "global_view_updated",
|
||||
delete: "global_view_deleted",
|
||||
open: "global_view_opened",
|
||||
};
|
||||
export const GLOBAL_VIEW_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "global_view_right_header_add_button",
|
||||
HEADER_SAVE_VIEW_BUTTON: "global_view_header_save_view_button",
|
||||
QUICK_ACTIONS: "global_view_quick_actions",
|
||||
LIST_ITEM: "global_view_list_item",
|
||||
};
|
||||
|
||||
export const PRODUCT_TOUR_TRACKER_EVENTS = {
|
||||
complete: "product_tour_completed",
|
||||
};
|
||||
export const PRODUCT_TOUR_TRACKER_ELEMENTS = {
|
||||
START_BUTTON: "product_tour_start_button",
|
||||
SKIP_BUTTON: "product_tour_skip_button",
|
||||
CREATE_PROJECT_BUTTON: "product_tour_create_project_button",
|
||||
};
|
||||
|
||||
export const NOTIFICATION_TRACKER_EVENTS = {
|
||||
archive: "notification_archived",
|
||||
unarchive: "notification_unarchived",
|
||||
mark_read: "notification_marked_read",
|
||||
mark_unread: "notification_marked_unread",
|
||||
all_marked_read: "all_notifications_marked_read",
|
||||
};
|
||||
export const NOTIFICATION_TRACKER_ELEMENTS = {
|
||||
MARK_ALL_AS_READ_BUTTON: "mark_all_as_read_button",
|
||||
ARCHIVE_UNARCHIVE_BUTTON: "archive_unarchive_button",
|
||||
MARK_READ_UNREAD_BUTTON: "mark_read_unread_button",
|
||||
};
|
||||
|
||||
export const USER_TRACKER_EVENTS = {
|
||||
add_details: "user_details_added",
|
||||
onboarding_complete: "user_onboarding_completed",
|
||||
};
|
||||
|
||||
export const ONBOARDING_TRACKER_EVENTS = {
|
||||
root: "onboarding",
|
||||
step_1: "onboarding_step_1",
|
||||
step_2: "onboarding_step_2",
|
||||
export const ONBOARDING_TRACKER_ELEMENTS = {
|
||||
PROFILE_SETUP_FORM: "onboarding_profile_setup_form",
|
||||
};
|
||||
|
||||
export const SIDEBAR_TRACKER_EVENTS = {
|
||||
click: "sidenav_clicked",
|
||||
export const SIDEBAR_TRACKER_ELEMENTS = {
|
||||
USER_MENU_ITEM: "sidenav_user_menu_item",
|
||||
CREATE_WORK_ITEM_BUTTON: "sidebar_create_work_item_button",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ const defaultValues: TUniqueCodeFormValues = {
|
|||
|
||||
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
const { mode, email, nextPath, handleEmailClear, generateEmailUniqueCode } = props;
|
||||
// hooks
|
||||
// const { captureEvent } = useEventTracker();
|
||||
// derived values
|
||||
const defaultResetTimerValue = 5;
|
||||
// states
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useMemo } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
// plane package imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { type TabItem, Tabs } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -12,7 +12,8 @@ import AnalyticsFilterActions from "@/components/analytics/analytics-filter-acti
|
|||
import { PageHead } from "@/components/core";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
|
||||
|
||||
|
|
@ -36,7 +37,6 @@ const AnalyticsPage = observer((props: Props) => {
|
|||
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, loader } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
|
@ -101,8 +101,8 @@ const AnalyticsPage = observer((props: Props) => {
|
|||
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { Plus, Search } from "lucide-react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
import { cn, copyUrlToClipboard, orderJoinedProjects } from "@plane/utils";
|
||||
|
|
@ -122,6 +122,7 @@ export const ExtendedProjectSidebar = observer(() => {
|
|||
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EXTENDED_SIDEBAR_ADD_BUTTON}
|
||||
className="p-0.5 rounded hover:bg-custom-sidebar-background-80 flex-shrink-0"
|
||||
onClick={() => {
|
||||
setIsProjectModalOpen(true);
|
||||
|
|
|
|||
|
|
@ -7,18 +7,17 @@ import { Home } from "lucide-react";
|
|||
import githubBlackImage from "/public/logos/github-black.png";
|
||||
import githubWhiteImage from "/public/logos/github-white.png";
|
||||
// ui
|
||||
import { GITHUB_REDIRECTED_TRACKER_EVENT } from "@plane/constants";
|
||||
import { GITHUB_REDIRECTED_TRACKER_EVENT, HEADER_GITHUB_ICON } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||
|
||||
export const WorkspaceDashboardHeader = () => {
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -39,8 +38,14 @@ export const WorkspaceDashboardHeader = () => {
|
|||
<Header.RightItem>
|
||||
<a
|
||||
onClick={() =>
|
||||
captureEvent(GITHUB_REDIRECTED_TRACKER_EVENT, {
|
||||
element: "navbar",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: HEADER_GITHUB_ICON,
|
||||
},
|
||||
event: {
|
||||
eventName: GITHUB_REDIRECTED_TRACKER_EVENT,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
})
|
||||
}
|
||||
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
EUserPermissionsLevel,
|
||||
EProjectFeatureKey,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { usePlatformOS } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -34,7 +35,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||
import {
|
||||
useCommandPalette,
|
||||
useCycle,
|
||||
useEventTracker,
|
||||
useIssues,
|
||||
useLabel,
|
||||
useMember,
|
||||
|
|
@ -68,7 +68,6 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { currentProjectCycleIds, getCycleById } = useCycle();
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
const { projectLabels } = useLabel();
|
||||
|
|
@ -263,9 +262,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
<Button
|
||||
className="h-full self-start"
|
||||
onClick={() => {
|
||||
setTrackElement("Cycle work items page");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
}}
|
||||
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.CYCLE}
|
||||
size="sm"
|
||||
>
|
||||
{t("issue.add.label")}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { CyclesViewHeader } from "@/components/cycles";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
||||
|
|
@ -23,7 +23,6 @@ export const CyclesListHeader: FC = observer(() => {
|
|||
|
||||
// store hooks
|
||||
const { toggleCreateCycleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -51,8 +50,8 @@ export const CyclesListHeader: FC = observer(() => {
|
|||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Cycles page");
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserProjectRoles, TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -16,7 +16,7 @@ import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
|||
import { CycleModuleListLayout } from "@/components/ui";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ const ProjectCyclesPage = observer(() => {
|
|||
// states
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { currentProjectCycleIds, loader } = useCycle();
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// router
|
||||
|
|
@ -100,8 +99,8 @@ const ProjectCyclesPage = observer(() => {
|
|||
label={t("project_cycles.empty_state.general.primary_button.text")}
|
||||
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Cycle empty state");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
EProjectFeatureKey,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
|
|
@ -31,7 +32,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||
import { ModuleQuickActions } from "@/components/modules";
|
||||
// hooks
|
||||
import {
|
||||
useEventTracker,
|
||||
useLabel,
|
||||
useMember,
|
||||
useModule,
|
||||
|
|
@ -66,7 +66,6 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
|
||||
const { projectModuleIds, getModuleById } = useModule();
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { projectLabels } = useLabel();
|
||||
|
|
@ -259,9 +258,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
<Button
|
||||
className="hidden sm:flex"
|
||||
onClick={() => {
|
||||
setTrackElement("Module work items page");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||
}}
|
||||
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.MODULE}
|
||||
size="sm"
|
||||
>
|
||||
Add work item
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { ModuleViewHeader } from "@/components/modules";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
||||
|
|
@ -22,7 +22,6 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
const { toggleCreateModuleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { loader } = useProject();
|
||||
|
|
@ -55,8 +54,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Modules page");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
// constants
|
||||
import { EPageAccess, EProjectFeatureKey } from "@plane/constants";
|
||||
import { EPageAccess, EProjectFeatureKey, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// plane types
|
||||
import { TPage } from "@plane/types";
|
||||
// plane ui
|
||||
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { useProject } from "@/hooks/store";
|
||||
// plane web
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
||||
// plane web hooks
|
||||
|
|
@ -27,11 +27,9 @@ export const PagesListHeader = observer(() => {
|
|||
// store hooks
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
|
||||
const { setTrackElement } = useEventTracker();
|
||||
// handle page create
|
||||
const handleCreatePage = async () => {
|
||||
setIsCreatingPage(true);
|
||||
setTrackElement("Project pages page");
|
||||
|
||||
const payload: Partial<TPage> = {
|
||||
access: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
|
||||
|
|
@ -66,7 +64,13 @@ export const PagesListHeader = observer(() => {
|
|||
</Header.LeftItem>
|
||||
{canCurrentUserCreatePage ? (
|
||||
<Header.RightItem>
|
||||
<Button variant="primary" size="sm" onClick={handleCreatePage} loading={isCreatingPage}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={handleCreatePage}
|
||||
loading={isCreatingPage}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.CREATE_HEADER_BUTTON}
|
||||
>
|
||||
{isCreatingPage ? "Adding" : "Add page"}
|
||||
</Button>
|
||||
</Header.RightItem>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
EProjectFeatureKey,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
// types
|
||||
import {
|
||||
|
|
@ -30,11 +31,9 @@ import { SwitcherIcon, SwitcherLabel } from "@/components/common";
|
|||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ViewQuickActions } from "@/components/views";
|
||||
// helpers
|
||||
// hooks
|
||||
import {
|
||||
useCommandPalette,
|
||||
useEventTracker,
|
||||
useIssues,
|
||||
useLabel,
|
||||
useMember,
|
||||
|
|
@ -57,7 +56,6 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
|
|
@ -258,9 +256,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||
{canUserCreateIssue ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_VIEW_PAGE_HEADER");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||
}}
|
||||
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.PROJECT_VIEW}
|
||||
size="sm"
|
||||
>
|
||||
Add work item
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import { Layers } from "lucide-react";
|
|||
import {
|
||||
EIssueFilterType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||
EIssueLayoutTypes,
|
||||
GLOBAL_VIEW_TRACKER_ELEMENTS,
|
||||
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import {
|
||||
|
|
@ -225,7 +226,12 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
<></>
|
||||
)}
|
||||
|
||||
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => setCreateViewModal(true)}
|
||||
>
|
||||
{t("workspace_views.add_view")}
|
||||
</Button>
|
||||
<div className="hidden md:block">
|
||||
|
|
|
|||
|
|
@ -5,12 +5,17 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Search } from "lucide-react";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
MEMBER_TRACKER_ELEMENTS,
|
||||
MEMBER_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { cn, getUserRole } from "@plane/utils";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { CountChip } from "@/components/common";
|
||||
|
|
@ -19,7 +24,8 @@ import { SettingsContentWrapper } from "@/components/settings";
|
|||
import { WorkspaceMembersList } from "@/components/workspace";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { BillingActionsButton } from "@/plane-web/components/workspace/billing";
|
||||
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members";
|
||||
|
|
@ -32,7 +38,6 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const {
|
||||
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
|
||||
} = useMember();
|
||||
|
|
@ -52,16 +57,11 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||
.then(() => {
|
||||
setInviteModal(false);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...data.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace settings member page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -70,16 +70,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...data.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "FAILED",
|
||||
element: "Workspace settings member page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -129,7 +125,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
/>
|
||||
</div>
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setInviteModal(true)}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
|
||||
>
|
||||
{t("workspace_settings.settings.members.add_member")}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useCommandPalette } from "@/hooks/store";
|
||||
|
|
@ -27,7 +28,11 @@ const ProjectSettingsPage = () => {
|
|||
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("neutral-primary", "sm"))}>
|
||||
Learn more about projects
|
||||
</Link>
|
||||
<Button size="sm" onClick={() => toggleCreateProjectModal(true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => toggleCreateProjectModal(true)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||
>
|
||||
Start your first project
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,15 @@ import { Controller, useForm } from "react-hook-form";
|
|||
// icons
|
||||
import { CircleCheck } from "lucide-react";
|
||||
// plane imports
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||
import { cn, checkEmailValidity } from "@plane/utils";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
|
|
@ -45,8 +46,6 @@ const ForgotPasswordPage = observer(() => {
|
|||
const email = searchParams.get("email");
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { config } = useInstance();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
|
@ -71,8 +70,11 @@ const ForgotPasswordPage = observer(() => {
|
|||
email: formData.email,
|
||||
})
|
||||
.then(() => {
|
||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||
payload: {
|
||||
email: formData.email,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
|
|
@ -82,8 +84,11 @@ const ForgotPasswordPage = observer(() => {
|
|||
setResendCodeTimer(30);
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
||||
state: "FAILED",
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||
payload: {
|
||||
email: formData.email,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -120,7 +125,7 @@ const ForgotPasswordPage = observer(() => {
|
|||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGNUP_FROM_FORGOT_PASSWORD}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.create_account")}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,21 @@ import { useTheme } from "next-themes";
|
|||
import useSWR, { mutate } from "swr";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS, GROUP_WORKSPACE_TRACKER_EVENT } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { truncateText, getUserRole } from "@plane/utils";
|
||||
import { truncateText } from "@plane/utils";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common";
|
||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess, joinEventGroup } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// services
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
|
|
@ -43,7 +44,6 @@ const UserInvitationsPage = observer(() => {
|
|||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
const { updateUserProfile } = useUserProfile();
|
||||
|
||||
|
|
@ -85,15 +85,17 @@ const UserInvitationsPage = observer(() => {
|
|||
const firstInviteId = invitationsRespond[0];
|
||||
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
||||
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
||||
joinWorkspaceMetricGroup(redirectWorkspace?.id);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
if (redirectWorkspace?.id) {
|
||||
joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, redirectWorkspace?.id, {
|
||||
date: new Date().toDateString(),
|
||||
workspace_id: redirectWorkspace?.id,
|
||||
});
|
||||
}
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
role: getUserRole((invitation?.role as unknown as EUserPermissions)!),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "SUCCESS",
|
||||
element: "Workspace invitations page",
|
||||
},
|
||||
});
|
||||
updateUserProfile({ last_workspace_id: redirectWorkspace?.id })
|
||||
.then(() => {
|
||||
|
|
@ -111,12 +113,13 @@ const UserInvitationsPage = observer(() => {
|
|||
setIsJoiningWorkspaces(false);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "FAILED",
|
||||
element: "Workspace invitations page",
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitationsRespond?.[0],
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
@ -194,6 +197,7 @@ const UserInvitationsPage = observer(() => {
|
|||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
||||
loading={isJoiningWorkspaces}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ACCEPT_INVITATION_BUTTON}
|
||||
>
|
||||
{t("accept_and_join")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
|||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useWorkspace, useUserProfile } from "@/hooks/store";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
|
@ -35,7 +36,6 @@ const OnboardingPage = observer(() => {
|
|||
const [step, setStep] = useState<EOnboardingSteps | null>(null);
|
||||
const [totalSteps, setTotalSteps] = useState<number | null>(null);
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isLoading: userLoader, data: user, updateCurrentUser } = useUser();
|
||||
const { data: profile, updateUserProfile, finishUserOnboarding } = useUserProfile();
|
||||
const { workspaces, fetchWorkspaces } = useWorkspace();
|
||||
|
|
@ -73,10 +73,12 @@ const OnboardingPage = observer(() => {
|
|||
|
||||
await finishUserOnboarding()
|
||||
.then(() => {
|
||||
captureEvent(USER_TRACKER_EVENTS.onboarding_complete, {
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.onboarding_complete,
|
||||
payload: {
|
||||
email: user.email,
|
||||
user_id: user.id,
|
||||
status: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ import Link from "next/link";
|
|||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
// constants
|
||||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
// assets
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg";
|
||||
|
|
@ -26,8 +24,6 @@ export type AuthType = "sign-in" | "sign-up";
|
|||
const SignInPage = observer(() => {
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
|
|
@ -54,7 +50,7 @@ const SignInPage = observer(() => {
|
|||
{t("auth.common.already_have_an_account")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_in, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_FROM_SIGNUP}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.login")}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
|||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
import { PageHead } from "@/components/core";
|
||||
|
|
@ -14,7 +14,7 @@ import { PageHead } from "@/components/core";
|
|||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// layouts
|
||||
import DefaultLayout from "@/layouts/default-layout";
|
||||
// wrappers
|
||||
|
|
@ -29,8 +29,6 @@ const HomePage = observer(() => {
|
|||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// store
|
||||
const { config } = useInstance();
|
||||
// derived values
|
||||
|
|
@ -63,7 +61,7 @@ const HomePage = observer(() => {
|
|||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/sign-up"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.NAVIGATE_TO_SIGN_UP}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.create_account")}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
EUserPermissionsLevel,
|
||||
SPACE_BASE_PATH,
|
||||
SPACE_BASE_URL,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
EProjectFeatureKey,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
|
@ -21,7 +22,7 @@ import { CountChip } from "@/components/common";
|
|||
import HeaderFilters from "@/components/issues/filters";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
import { useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
@ -42,7 +43,6 @@ export const IssuesHeader = observer(() => {
|
|||
const { currentProjectDetails, loader } = useProject();
|
||||
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
|
|
@ -104,9 +104,9 @@ export const IssuesHeader = observer(() => {
|
|||
{canUserCreateIssue ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("Project work items page");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
}}
|
||||
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.WORK_ITEMS}
|
||||
size="sm"
|
||||
>
|
||||
<div className="block sm:hidden">{t("issue.label", { count: 1 })}</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export interface CopyMenuHelperProps {
|
|||
shouldRender: boolean;
|
||||
};
|
||||
activeLayout: string;
|
||||
setTrackElement: (element: string) => void;
|
||||
setCreateUpdateIssueModal: (open: boolean) => void;
|
||||
setDuplicateWorkItemModal?: (open: boolean) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import { useState, useEffect, useRef } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
|
||||
// plane imports
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// store
|
||||
|
|
@ -78,6 +81,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
|||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON}
|
||||
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
|
||||
aria-label="Lock"
|
||||
>
|
||||
|
|
@ -90,6 +94,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
|||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON}
|
||||
className="h-6 flex items-center gap-1 px-2 rounded text-custom-primary-100 bg-custom-primary-100/20 hover:bg-custom-primary-100/30 transition-colors"
|
||||
aria-label="Locked"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import ProjectCommonAttributes from "@/components/project/create/common-attribut
|
|||
import ProjectCreateHeader from "@/components/project/create/header";
|
||||
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
|
||||
// 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";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
|
|
@ -32,7 +33,6 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
|||
const { setToFavorite, workspaceSlug, data, onClose, handleNextStep, updateCoverImageStatus } = props;
|
||||
// store
|
||||
const { t } = useTranslation();
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { addProjectToFavorites, createProject } = useProject();
|
||||
// states
|
||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||
|
|
@ -70,25 +70,30 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
|||
if (coverImage) {
|
||||
await updateCoverImageStatus(res.id, coverImage);
|
||||
}
|
||||
const newPayload = {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
};
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||
payload: newPayload,
|
||||
payload: {
|
||||
identifier: formData.identifier,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("success"),
|
||||
message: t("project_created_successfully"),
|
||||
});
|
||||
|
||||
if (setToFavorite) {
|
||||
handleAddToFavorites(res.id);
|
||||
}
|
||||
handleNextStep(res.id);
|
||||
})
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
identifier: formData.identifier,
|
||||
},
|
||||
});
|
||||
if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { FC, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
// types
|
||||
import { WORKSPACE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspace } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -48,7 +49,11 @@ export const DeleteWorkspaceSection: FC<TDeleteWorkspace> = observer((props) =>
|
|||
{t("workspace_settings.settings.general.delete_workspace_description")}
|
||||
</span>
|
||||
<div>
|
||||
<Button variant="danger" onClick={() => setDeleteWorkspaceModal(true)}>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => setDeleteWorkspaceModal(true)}
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.DELETE_WORKSPACE_BUTTON}
|
||||
>
|
||||
{t("workspace_settings.settings.general.delete_btn")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// types
|
||||
import {
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
PROJECT_PAGE_TRACKER_ELEMENTS,
|
||||
PROJECT_TRACKER_ELEMENTS,
|
||||
WORK_ITEM_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types";
|
||||
// store
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { store } from "@/lib/store-context";
|
||||
|
||||
export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
||||
|
|
@ -10,7 +18,10 @@ export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
|||
c: {
|
||||
title: "Create a new work item",
|
||||
description: "Create a new work item in the current project",
|
||||
action: () => toggleCreateIssueModal(true),
|
||||
action: () => {
|
||||
toggleCreateIssueModal(true);
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON });
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -22,7 +33,10 @@ export const getWorkspaceShortcutsList: () => TCommandPaletteActionList = () =>
|
|||
p: {
|
||||
title: "Create a new project",
|
||||
description: "Create a new project in the current workspace",
|
||||
action: () => toggleCreateProjectModal(true),
|
||||
action: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON });
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -40,17 +54,26 @@ export const getProjectShortcutsList: () => TCommandPaletteActionList = () => {
|
|||
d: {
|
||||
title: "Create a new page",
|
||||
description: "Create a new page in the current project",
|
||||
action: () => toggleCreatePageModal({ isOpen: true }),
|
||||
action: () => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON });
|
||||
},
|
||||
},
|
||||
m: {
|
||||
title: "Create a new module",
|
||||
description: "Create a new module in the current project",
|
||||
action: () => toggleCreateModuleModal(true),
|
||||
action: () => {
|
||||
toggleCreateModuleModal(true);
|
||||
captureClick({ elementName: MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM });
|
||||
},
|
||||
},
|
||||
q: {
|
||||
title: "Create a new cycle",
|
||||
description: "Create a new cycle in the current project",
|
||||
action: () => toggleCreateCycleModal(true),
|
||||
action: () => {
|
||||
toggleCreateCycleModal(true);
|
||||
captureClick({ elementName: CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM });
|
||||
},
|
||||
},
|
||||
v: {
|
||||
title: "Create a new view",
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import { RootStore } from "@/plane-web/store/root.store";
|
||||
import { CoreEventTrackerStore, ICoreEventTrackerStore } from "@/store/event-tracker.store";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface IEventTrackerStore extends ICoreEventTrackerStore {}
|
||||
|
||||
export class EventTrackerStore extends CoreEventTrackerStore implements IEventTrackerStore {
|
||||
constructor(_rootStore: RootStore) {
|
||||
super(_rootStore);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
captureSuccess({
|
||||
eventName:
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_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, {
|
||||
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({
|
||||
.then(() => {
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
changed_properties: [changedProperty],
|
||||
element: "Right side-peek",
|
||||
state: "SUCCESS",
|
||||
payload: {
|
||||
id: cycleDetails.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
.catch(() => {
|
||||
captureCycleEvent({
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
state: "ERROR",
|
||||
payload: {
|
||||
...data,
|
||||
element: "Right side-peek",
|
||||
state: "FAILED",
|
||||
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,15 +141,22 @@ 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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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, {
|
||||
captureSuccess({
|
||||
eventName: PRODUCT_TOUR_TRACKER_EVENTS.complete,
|
||||
payload: {
|
||||
user_id: currentUser?.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.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,13 +107,23 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
|||
const handleUpdateView = () => {
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
|
||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters).then((res) => {
|
||||
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",
|
||||
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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,14 +204,22 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
|
||||
if (!draggedIssueId || !draggedIssue) return;
|
||||
|
||||
await removeIssue(draggedIssue.project_id, draggedIssueId).finally(() => {
|
||||
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);
|
||||
captureIssueEvent({
|
||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||
payload: { id: draggedIssueId, state: "FAILED", element: "Kanban layout drag & drop" },
|
||||
path: pathname,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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,11 +111,24 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
|||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleId
|
||||
).then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
)
|
||||
.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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "SUCCESS",
|
||||
element: "Workspace invitations page",
|
||||
},
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
await fetchCurrentUserSettings();
|
||||
await handleNextStep();
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "FAILED",
|
||||
element: "Workspace invitations page",
|
||||
},
|
||||
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, {
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
},
|
||||
});
|
||||
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, {
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
||||
},
|
||||
});
|
||||
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,17 +31,14 @@ 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({
|
||||
const response = await createStateCallback({ ...formData, group: groupKey });
|
||||
if (shouldTrackEvents)
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
state_group: groupKey,
|
||||
id: response.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
|
|
@ -60,12 +50,11 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
|||
return { status: "success" };
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
if (shouldTrackEvents)
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
state_group: groupKey,
|
||||
},
|
||||
});
|
||||
if (errorStatus?.status === 400) {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
await updateStateCallback(state.id, formData);
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
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({
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
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({
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "SUCCESS",
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setIsDelete(false);
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "FAILED",
|
||||
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>
|
||||
|
|
|
|||
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