[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
|
// Dashboard Events
|
||||||
export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected";
|
export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected";
|
||||||
|
export const HEADER_GITHUB_ICON = "header_github_icon";
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics";
|
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 = {
|
export const WORKSPACE_TRACKER_EVENTS = {
|
||||||
create: "workspace_created",
|
create: "workspace_created",
|
||||||
update: "workspace_updated",
|
update: "workspace_updated",
|
||||||
delete: "workspace_deleted",
|
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 = {
|
export const PROJECT_TRACKER_EVENTS = {
|
||||||
create: "project_created",
|
create: "project_created",
|
||||||
update: "project_updated",
|
update: "project_updated",
|
||||||
delete: "project_deleted",
|
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 = {
|
export const CYCLE_TRACKER_EVENTS = {
|
||||||
create: "cycle_created",
|
create: "cycle_created",
|
||||||
|
|
@ -165,7 +48,18 @@ export const CYCLE_TRACKER_EVENTS = {
|
||||||
delete: "cycle_deleted",
|
delete: "cycle_deleted",
|
||||||
favorite: "cycle_favorited",
|
favorite: "cycle_favorited",
|
||||||
unfavorite: "cycle_unfavorited",
|
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 = {
|
export const MODULE_TRACKER_EVENTS = {
|
||||||
create: "module_created",
|
create: "module_created",
|
||||||
|
|
@ -173,32 +67,120 @@ export const MODULE_TRACKER_EVENTS = {
|
||||||
delete: "module_deleted",
|
delete: "module_deleted",
|
||||||
favorite: "module_favorited",
|
favorite: "module_favorited",
|
||||||
unfavorite: "module_unfavorited",
|
unfavorite: "module_unfavorited",
|
||||||
|
archive: "module_archived",
|
||||||
|
restore: "module_restored",
|
||||||
link: {
|
link: {
|
||||||
create: "module_link_created",
|
create: "module_link_created",
|
||||||
update: "module_link_updated",
|
update: "module_link_updated",
|
||||||
delete: "module_link_deleted",
|
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 = {
|
export const WORK_ITEM_TRACKER_EVENTS = {
|
||||||
create: "work_item_created",
|
create: "work_item_created",
|
||||||
|
add_existing: "work_item_add_existing",
|
||||||
update: "work_item_updated",
|
update: "work_item_updated",
|
||||||
delete: "work_item_deleted",
|
delete: "work_item_deleted",
|
||||||
archive: "work_item_archived",
|
archive: "work_item_archived",
|
||||||
restore: "work_item_restored",
|
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 = {
|
export const STATE_TRACKER_EVENTS = {
|
||||||
create: "state_created",
|
create: "state_created",
|
||||||
update: "state_updated",
|
update: "state_updated",
|
||||||
delete: "state_deleted",
|
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 = {
|
export const PROJECT_PAGE_TRACKER_EVENTS = {
|
||||||
create: "project_page_created",
|
create: "project_page_created",
|
||||||
update: "project_page_updated",
|
update: "project_page_updated",
|
||||||
delete: "project_page_deleted",
|
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 = {
|
export const MEMBER_TRACKER_EVENTS = {
|
||||||
invite: "member_invited",
|
invite: "member_invited",
|
||||||
|
|
@ -211,48 +193,78 @@ export const MEMBER_TRACKER_EVENTS = {
|
||||||
leave: "workspace_member_left",
|
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 = {
|
export const AUTH_TRACKER_EVENTS = {
|
||||||
navigate: {
|
|
||||||
sign_up: "navigate_to_sign_up_page",
|
|
||||||
sign_in: "navigate_to_sign_in_page",
|
|
||||||
},
|
|
||||||
code_verify: "code_verified",
|
code_verify: "code_verified",
|
||||||
sign_up_with_password: "sign_up_with_password",
|
sign_up_with_password: "sign_up_with_password",
|
||||||
sign_in_with_password: "sign_in_with_password",
|
sign_in_with_password: "sign_in_with_password",
|
||||||
sign_in_with_code: "sign_in_with_magic_link",
|
|
||||||
forgot_password: "forgot_password_clicked",
|
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 = {
|
export const GLOBAL_VIEW_TRACKER_EVENTS = {
|
||||||
start: "product_tour_started",
|
|
||||||
complete: "product_tour_completed",
|
|
||||||
skip: "product_tour_skipped",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GLOBAL_VIEW_TOUR_TRACKER_EVENTS = {
|
|
||||||
create: "global_view_created",
|
create: "global_view_created",
|
||||||
update: "global_view_updated",
|
update: "global_view_updated",
|
||||||
delete: "global_view_deleted",
|
delete: "global_view_deleted",
|
||||||
open: "global_view_opened",
|
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 = {
|
export const NOTIFICATION_TRACKER_EVENTS = {
|
||||||
archive: "notification_archived",
|
archive: "notification_archived",
|
||||||
|
unarchive: "notification_unarchived",
|
||||||
|
mark_read: "notification_marked_read",
|
||||||
|
mark_unread: "notification_marked_unread",
|
||||||
all_marked_read: "all_notifications_marked_read",
|
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 = {
|
export const USER_TRACKER_EVENTS = {
|
||||||
add_details: "user_details_added",
|
add_details: "user_details_added",
|
||||||
onboarding_complete: "user_onboarding_completed",
|
onboarding_complete: "user_onboarding_completed",
|
||||||
};
|
};
|
||||||
|
export const ONBOARDING_TRACKER_ELEMENTS = {
|
||||||
export const ONBOARDING_TRACKER_EVENTS = {
|
PROFILE_SETUP_FORM: "onboarding_profile_setup_form",
|
||||||
root: "onboarding",
|
|
||||||
step_1: "onboarding_step_1",
|
|
||||||
step_2: "onboarding_step_2",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SIDEBAR_TRACKER_EVENTS = {
|
export const SIDEBAR_TRACKER_ELEMENTS = {
|
||||||
click: "sidenav_clicked",
|
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) => {
|
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||||
const { mode, email, nextPath, handleEmailClear, generateEmailUniqueCode } = props;
|
const { mode, email, nextPath, handleEmailClear, generateEmailUniqueCode } = props;
|
||||||
// hooks
|
|
||||||
// const { captureEvent } = useEventTracker();
|
|
||||||
// derived values
|
// derived values
|
||||||
const defaultResetTimerValue = 5;
|
const defaultResetTimerValue = 5;
|
||||||
// states
|
// states
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useMemo } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
// plane package imports
|
// plane package imports
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { type TabItem, Tabs } from "@plane/ui";
|
import { type TabItem, Tabs } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
|
|
@ -12,7 +12,8 @@ import AnalyticsFilterActions from "@/components/analytics/analytics-filter-acti
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
// hooks
|
// 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 { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
|
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
|
||||||
|
|
||||||
|
|
@ -36,7 +37,6 @@ const AnalyticsPage = observer((props: Props) => {
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { workspaceProjectIds, loader } = useProject();
|
const { workspaceProjectIds, loader } = useProject();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
@ -101,8 +101,8 @@ const AnalyticsPage = observer((props: Props) => {
|
||||||
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||||
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Analytics empty state");
|
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
|
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||||
}}
|
}}
|
||||||
disabled={!canPerformEmptyStateActions}
|
disabled={!canPerformEmptyStateActions}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { Plus, Search } from "lucide-react";
|
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 { useTranslation } from "@plane/i18n";
|
||||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||||
import { cn, copyUrlToClipboard, orderJoinedProjects } from "@plane/utils";
|
import { cn, copyUrlToClipboard, orderJoinedProjects } from "@plane/utils";
|
||||||
|
|
@ -122,6 +122,7 @@ export const ExtendedProjectSidebar = observer(() => {
|
||||||
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="p-0.5 rounded hover:bg-custom-sidebar-background-80 flex-shrink-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsProjectModalOpen(true);
|
setIsProjectModalOpen(true);
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,17 @@ import { Home } from "lucide-react";
|
||||||
import githubBlackImage from "/public/logos/github-black.png";
|
import githubBlackImage from "/public/logos/github-black.png";
|
||||||
import githubWhiteImage from "/public/logos/github-white.png";
|
import githubWhiteImage from "/public/logos/github-white.png";
|
||||||
// ui
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { Breadcrumbs, Header } from "@plane/ui";
|
import { Breadcrumbs, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||||
|
|
||||||
export const WorkspaceDashboardHeader = () => {
|
export const WorkspaceDashboardHeader = () => {
|
||||||
// hooks
|
// hooks
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -39,8 +38,14 @@ export const WorkspaceDashboardHeader = () => {
|
||||||
<Header.RightItem>
|
<Header.RightItem>
|
||||||
<a
|
<a
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
captureEvent(GITHUB_REDIRECTED_TRACKER_EVENT, {
|
captureElementAndEvent({
|
||||||
element: "navbar",
|
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"
|
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,
|
EUserPermissionsLevel,
|
||||||
EProjectFeatureKey,
|
EProjectFeatureKey,
|
||||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
WORK_ITEM_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { usePlatformOS } from "@plane/hooks";
|
import { usePlatformOS } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -34,7 +35,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
||||||
import {
|
import {
|
||||||
useCommandPalette,
|
useCommandPalette,
|
||||||
useCycle,
|
useCycle,
|
||||||
useEventTracker,
|
|
||||||
useIssues,
|
useIssues,
|
||||||
useLabel,
|
useLabel,
|
||||||
useMember,
|
useMember,
|
||||||
|
|
@ -68,7 +68,6 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
const { currentProjectCycleIds, getCycleById } = useCycle();
|
const { currentProjectCycleIds, getCycleById } = useCycle();
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
const { projectStates } = useProjectState();
|
const { projectStates } = useProjectState();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
|
|
@ -263,9 +262,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||||
<Button
|
<Button
|
||||||
className="h-full self-start"
|
className="h-full self-start"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Cycle work items page");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||||
}}
|
}}
|
||||||
|
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.CYCLE}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{t("issue.add.label")}
|
{t("issue.add.label")}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// ui
|
// ui
|
||||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { CyclesViewHeader } from "@/components/cycles";
|
import { CyclesViewHeader } from "@/components/cycles";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// plane web
|
// plane web
|
||||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
||||||
|
|
@ -23,7 +23,6 @@ export const CyclesListHeader: FC = observer(() => {
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateCycleModal } = useCommandPalette();
|
const { toggleCreateCycleModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -51,8 +50,8 @@ export const CyclesListHeader: FC = observer(() => {
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
data-ph-element={CYCLE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Cycles page");
|
|
||||||
toggleCreateCycleModal(true);
|
toggleCreateCycleModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EUserProjectRoles, TCycleFilters } from "@plane/types";
|
import { EUserProjectRoles, TCycleFilters } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
|
|
@ -16,7 +16,7 @@ import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { CycleModuleListLayout } from "@/components/ui";
|
import { CycleModuleListLayout } from "@/components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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 { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
|
|
@ -24,7 +24,6 @@ const ProjectCyclesPage = observer(() => {
|
||||||
// states
|
// states
|
||||||
const [createModal, setCreateModal] = useState(false);
|
const [createModal, setCreateModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { currentProjectCycleIds, loader } = useCycle();
|
const { currentProjectCycleIds, loader } = useCycle();
|
||||||
const { getProjectById, currentProjectDetails } = useProject();
|
const { getProjectById, currentProjectDetails } = useProject();
|
||||||
// router
|
// router
|
||||||
|
|
@ -100,8 +99,8 @@ const ProjectCyclesPage = observer(() => {
|
||||||
label={t("project_cycles.empty_state.general.primary_button.text")}
|
label={t("project_cycles.empty_state.general.primary_button.text")}
|
||||||
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
||||||
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
||||||
|
data-ph-element={CYCLE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Cycle empty state");
|
|
||||||
setCreateModal(true);
|
setCreateModal(true);
|
||||||
}}
|
}}
|
||||||
disabled={!hasMemberLevelPermission}
|
disabled={!hasMemberLevelPermission}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
EUserPermissions,
|
EUserPermissions,
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
EProjectFeatureKey,
|
EProjectFeatureKey,
|
||||||
|
WORK_ITEM_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import {
|
import {
|
||||||
EIssuesStoreType,
|
EIssuesStoreType,
|
||||||
|
|
@ -31,7 +32,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import {
|
||||||
useEventTracker,
|
|
||||||
useLabel,
|
useLabel,
|
||||||
useMember,
|
useMember,
|
||||||
useModule,
|
useModule,
|
||||||
|
|
@ -66,7 +66,6 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||||
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
|
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
|
||||||
const { projectModuleIds, getModuleById } = useModule();
|
const { projectModuleIds, getModuleById } = useModule();
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
|
|
@ -259,9 +258,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||||
<Button
|
<Button
|
||||||
className="hidden sm:flex"
|
className="hidden sm:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Module work items page");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||||
}}
|
}}
|
||||||
|
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.MODULE}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Add work item
|
Add work item
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ModuleViewHeader } from "@/components/modules";
|
import { ModuleViewHeader } from "@/components/modules";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// plane web
|
// plane web
|
||||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
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 };
|
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateModuleModal } = useCommandPalette();
|
const { toggleCreateModuleModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
const { loader } = useProject();
|
const { loader } = useProject();
|
||||||
|
|
@ -55,8 +54,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
data-ph-element={MODULE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Modules page");
|
|
||||||
toggleCreateModuleModal(true);
|
toggleCreateModuleModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||||
// constants
|
// constants
|
||||||
import { EPageAccess, EProjectFeatureKey } from "@plane/constants";
|
import { EPageAccess, EProjectFeatureKey, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
// plane types
|
// plane types
|
||||||
import { TPage } from "@plane/types";
|
import { TPage } from "@plane/types";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject } from "@/hooks/store";
|
import { useProject } from "@/hooks/store";
|
||||||
// plane web
|
// plane web
|
||||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
||||||
// plane web hooks
|
// plane web hooks
|
||||||
|
|
@ -27,11 +27,9 @@ export const PagesListHeader = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
|
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
// handle page create
|
// handle page create
|
||||||
const handleCreatePage = async () => {
|
const handleCreatePage = async () => {
|
||||||
setIsCreatingPage(true);
|
setIsCreatingPage(true);
|
||||||
setTrackElement("Project pages page");
|
|
||||||
|
|
||||||
const payload: Partial<TPage> = {
|
const payload: Partial<TPage> = {
|
||||||
access: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
|
access: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
|
||||||
|
|
@ -66,7 +64,13 @@ export const PagesListHeader = observer(() => {
|
||||||
</Header.LeftItem>
|
</Header.LeftItem>
|
||||||
{canCurrentUserCreatePage ? (
|
{canCurrentUserCreatePage ? (
|
||||||
<Header.RightItem>
|
<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"}
|
{isCreatingPage ? "Adding" : "Add page"}
|
||||||
</Button>
|
</Button>
|
||||||
</Header.RightItem>
|
</Header.RightItem>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
EUserPermissions,
|
EUserPermissions,
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
EProjectFeatureKey,
|
EProjectFeatureKey,
|
||||||
|
WORK_ITEM_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
// types
|
// types
|
||||||
import {
|
import {
|
||||||
|
|
@ -30,11 +31,9 @@ import { SwitcherIcon, SwitcherLabel } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
import { ViewQuickActions } from "@/components/views";
|
import { ViewQuickActions } from "@/components/views";
|
||||||
// helpers
|
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import {
|
||||||
useCommandPalette,
|
useCommandPalette,
|
||||||
useEventTracker,
|
|
||||||
useIssues,
|
useIssues,
|
||||||
useLabel,
|
useLabel,
|
||||||
useMember,
|
useMember,
|
||||||
|
|
@ -57,7 +56,6 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { issueFilters, updateFilters },
|
||||||
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
|
|
@ -258,9 +256,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||||
{canUserCreateIssue ? (
|
{canUserCreateIssue ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("PROJECT_VIEW_PAGE_HEADER");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||||
}}
|
}}
|
||||||
|
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.PROJECT_VIEW}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Add work item
|
Add work item
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ import { Layers } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
EIssueFilterType,
|
EIssueFilterType,
|
||||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
DEFAULT_GLOBAL_VIEWS_LIST,
|
|
||||||
EIssueLayoutTypes,
|
EIssueLayoutTypes,
|
||||||
|
GLOBAL_VIEW_TRACKER_ELEMENTS,
|
||||||
|
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import {
|
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")}
|
{t("workspace_views.add_view")}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,17 @@ import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Search } from "lucide-react";
|
import { Search } from "lucide-react";
|
||||||
// types
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { cn, getUserRole } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||||
import { CountChip } from "@/components/common";
|
import { CountChip } from "@/components/common";
|
||||||
|
|
@ -19,7 +24,8 @@ import { SettingsContentWrapper } from "@/components/settings";
|
||||||
import { WorkspaceMembersList } from "@/components/workspace";
|
import { WorkspaceMembersList } from "@/components/workspace";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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
|
// plane web components
|
||||||
import { BillingActionsButton } from "@/plane-web/components/workspace/billing";
|
import { BillingActionsButton } from "@/plane-web/components/workspace/billing";
|
||||||
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members";
|
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members";
|
||||||
|
|
@ -32,7 +38,6 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const {
|
const {
|
||||||
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
|
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
|
@ -52,16 +57,11 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setInviteModal(false);
|
setInviteModal(false);
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
captureSuccess({
|
||||||
emails: [
|
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||||
...data.emails.map((email) => ({
|
payload: {
|
||||||
email: email.email,
|
emails: [...data.emails.map((email) => email.email)],
|
||||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
},
|
||||||
})),
|
|
||||||
],
|
|
||||||
project_id: undefined,
|
|
||||||
state: "SUCCESS",
|
|
||||||
element: "Workspace settings member page",
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -70,16 +70,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
captureError({
|
||||||
emails: [
|
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||||
...data.emails.map((email) => ({
|
payload: {
|
||||||
email: email.email,
|
emails: [...data.emails.map((email) => email.email)],
|
||||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
},
|
||||||
})),
|
error: err,
|
||||||
],
|
|
||||||
project_id: undefined,
|
|
||||||
state: "FAILED",
|
|
||||||
element: "Workspace settings member page",
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -129,7 +125,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{canPerformWorkspaceAdminActions && (
|
{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")}
|
{t("workspace_settings.settings.members.add_member")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { Button, getButtonStyling } from "@plane/ui";
|
import { Button, getButtonStyling } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
import { useCommandPalette } from "@/hooks/store";
|
import { useCommandPalette } from "@/hooks/store";
|
||||||
|
|
@ -27,7 +28,11 @@ const ProjectSettingsPage = () => {
|
||||||
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("neutral-primary", "sm"))}>
|
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("neutral-primary", "sm"))}>
|
||||||
Learn more about projects
|
Learn more about projects
|
||||||
</Link>
|
</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
|
Start your first project
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,15 @@ import { Controller, useForm } from "react-hook-form";
|
||||||
// icons
|
// icons
|
||||||
import { CircleCheck } from "lucide-react";
|
import { CircleCheck } from "lucide-react";
|
||||||
// plane imports
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
import { cn, checkEmailValidity } from "@plane/utils";
|
import { cn, checkEmailValidity } from "@plane/utils";
|
||||||
// helpers
|
// helpers
|
||||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// 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";
|
import useTimer from "@/hooks/use-timer";
|
||||||
// wrappers
|
// wrappers
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
|
|
@ -45,8 +46,6 @@ const ForgotPasswordPage = observer(() => {
|
||||||
const email = searchParams.get("email");
|
const email = searchParams.get("email");
|
||||||
// plane hooks
|
// plane hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { config } = useInstance();
|
const { config } = useInstance();
|
||||||
// hooks
|
// hooks
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
@ -71,8 +70,11 @@ const ForgotPasswordPage = observer(() => {
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
captureSuccess({
|
||||||
state: "SUCCESS",
|
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||||
|
payload: {
|
||||||
|
email: formData.email,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -82,8 +84,11 @@ const ForgotPasswordPage = observer(() => {
|
||||||
setResendCodeTimer(30);
|
setResendCodeTimer(30);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
captureError({
|
||||||
state: "FAILED",
|
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||||
|
payload: {
|
||||||
|
email: formData.email,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -120,7 +125,7 @@ const ForgotPasswordPage = observer(() => {
|
||||||
{t("auth.common.new_to_plane")}
|
{t("auth.common.new_to_plane")}
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
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"
|
className="font-semibold text-custom-primary-100 hover:underline"
|
||||||
>
|
>
|
||||||
{t("auth.common.create_account")}
|
{t("auth.common.create_account")}
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,21 @@ import { useTheme } from "next-themes";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
import { CheckCircle2 } from "lucide-react";
|
import { CheckCircle2 } from "lucide-react";
|
||||||
// plane imports
|
// 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";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { truncateText, getUserRole } from "@plane/utils";
|
import { truncateText } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { EmptyState } from "@/components/common";
|
import { EmptyState } from "@/components/common";
|
||||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// services
|
// services
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
|
|
@ -43,7 +44,6 @@ const UserInvitationsPage = observer(() => {
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { updateUserProfile } = useUserProfile();
|
const { updateUserProfile } = useUserProfile();
|
||||||
|
|
||||||
|
|
@ -85,15 +85,17 @@ const UserInvitationsPage = observer(() => {
|
||||||
const firstInviteId = invitationsRespond[0];
|
const firstInviteId = invitationsRespond[0];
|
||||||
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
||||||
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
||||||
joinWorkspaceMetricGroup(redirectWorkspace?.id);
|
if (redirectWorkspace?.id) {
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, redirectWorkspace?.id, {
|
||||||
member_id: invitation?.id,
|
date: new Date().toDateString(),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
workspace_id: redirectWorkspace?.id,
|
||||||
role: getUserRole((invitation?.role as unknown as EUserPermissions)!),
|
});
|
||||||
project_id: undefined,
|
}
|
||||||
accepted_from: "App",
|
captureSuccess({
|
||||||
state: "SUCCESS",
|
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||||
element: "Workspace invitations page",
|
payload: {
|
||||||
|
member_id: invitation?.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
updateUserProfile({ last_workspace_id: redirectWorkspace?.id })
|
updateUserProfile({ last_workspace_id: redirectWorkspace?.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
@ -111,12 +113,13 @@ const UserInvitationsPage = observer(() => {
|
||||||
setIsJoiningWorkspaces(false);
|
setIsJoiningWorkspaces(false);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
captureError({
|
||||||
project_id: undefined,
|
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||||
accepted_from: "App",
|
payload: {
|
||||||
state: "FAILED",
|
member_id: invitationsRespond?.[0],
|
||||||
element: "Workspace invitations page",
|
},
|
||||||
|
error: err,
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -194,6 +197,7 @@ const UserInvitationsPage = observer(() => {
|
||||||
onClick={submitInvitations}
|
onClick={submitInvitations}
|
||||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
||||||
loading={isJoiningWorkspaces}
|
loading={isJoiningWorkspaces}
|
||||||
|
data-ph-element={MEMBER_TRACKER_ELEMENTS.ACCEPT_INVITATION_BUTTON}
|
||||||
>
|
>
|
||||||
{t("accept_and_join")}
|
{t("accept_and_join")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||||
// helpers
|
// helpers
|
||||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store";
|
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useUser, useWorkspace, useUserProfile } from "@/hooks/store";
|
||||||
// wrappers
|
// wrappers
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
|
|
@ -35,7 +36,6 @@ const OnboardingPage = observer(() => {
|
||||||
const [step, setStep] = useState<EOnboardingSteps | null>(null);
|
const [step, setStep] = useState<EOnboardingSteps | null>(null);
|
||||||
const [totalSteps, setTotalSteps] = useState<number | null>(null);
|
const [totalSteps, setTotalSteps] = useState<number | null>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { isLoading: userLoader, data: user, updateCurrentUser } = useUser();
|
const { isLoading: userLoader, data: user, updateCurrentUser } = useUser();
|
||||||
const { data: profile, updateUserProfile, finishUserOnboarding } = useUserProfile();
|
const { data: profile, updateUserProfile, finishUserOnboarding } = useUserProfile();
|
||||||
const { workspaces, fetchWorkspaces } = useWorkspace();
|
const { workspaces, fetchWorkspaces } = useWorkspace();
|
||||||
|
|
@ -73,10 +73,12 @@ const OnboardingPage = observer(() => {
|
||||||
|
|
||||||
await finishUserOnboarding()
|
await finishUserOnboarding()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent(USER_TRACKER_EVENTS.onboarding_complete, {
|
captureSuccess({
|
||||||
email: user.email,
|
eventName: USER_TRACKER_EVENTS.onboarding_complete,
|
||||||
user_id: user.id,
|
payload: {
|
||||||
status: "SUCCESS",
|
email: user.email,
|
||||||
|
user_id: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,12 @@ import Link from "next/link";
|
||||||
// ui
|
// ui
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { AuthRoot } from "@/components/account";
|
import { AuthRoot } from "@/components/account";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
|
||||||
import { useEventTracker } from "@/hooks/store";
|
|
||||||
// assets
|
// assets
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg";
|
import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg";
|
||||||
|
|
@ -26,8 +24,6 @@ export type AuthType = "sign-in" | "sign-up";
|
||||||
const SignInPage = observer(() => {
|
const SignInPage = observer(() => {
|
||||||
// plane hooks
|
// plane hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// hooks
|
// hooks
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
|
@ -54,7 +50,7 @@ const SignInPage = observer(() => {
|
||||||
{t("auth.common.already_have_an_account")}
|
{t("auth.common.already_have_an_account")}
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
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"
|
className="font-semibold text-custom-primary-100 hover:underline"
|
||||||
>
|
>
|
||||||
{t("auth.common.login")}
|
{t("auth.common.login")}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
||||||
// ui
|
// ui
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { AuthRoot } from "@/components/account";
|
import { AuthRoot } from "@/components/account";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
|
|
@ -14,7 +14,7 @@ import { PageHead } from "@/components/core";
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "@/layouts/default-layout";
|
import DefaultLayout from "@/layouts/default-layout";
|
||||||
// wrappers
|
// wrappers
|
||||||
|
|
@ -29,8 +29,6 @@ const HomePage = observer(() => {
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
// plane hooks
|
// plane hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// store
|
// store
|
||||||
const { config } = useInstance();
|
const { config } = useInstance();
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -63,7 +61,7 @@ const HomePage = observer(() => {
|
||||||
{t("auth.common.new_to_plane")}
|
{t("auth.common.new_to_plane")}
|
||||||
<Link
|
<Link
|
||||||
href="/sign-up"
|
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"
|
className="font-semibold text-custom-primary-100 hover:underline"
|
||||||
>
|
>
|
||||||
{t("auth.common.create_account")}
|
{t("auth.common.create_account")}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
SPACE_BASE_PATH,
|
SPACE_BASE_PATH,
|
||||||
SPACE_BASE_URL,
|
SPACE_BASE_URL,
|
||||||
|
WORK_ITEM_TRACKER_ELEMENTS,
|
||||||
EProjectFeatureKey,
|
EProjectFeatureKey,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -21,7 +22,7 @@ import { CountChip } from "@/components/common";
|
||||||
import HeaderFilters from "@/components/issues/filters";
|
import HeaderFilters from "@/components/issues/filters";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
import { useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||||
import { useIssues } from "@/hooks/store/use-issues";
|
import { useIssues } from "@/hooks/store/use-issues";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
@ -42,7 +43,6 @@ export const IssuesHeader = observer(() => {
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
|
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
|
|
@ -104,9 +104,9 @@ export const IssuesHeader = observer(() => {
|
||||||
{canUserCreateIssue ? (
|
{canUserCreateIssue ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Project work items page");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||||
}}
|
}}
|
||||||
|
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.WORK_ITEMS}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<div className="block sm:hidden">{t("issue.label", { count: 1 })}</div>
|
<div className="block sm:hidden">{t("issue.label", { count: 1 })}</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ export interface CopyMenuHelperProps {
|
||||||
shouldRender: boolean;
|
shouldRender: boolean;
|
||||||
};
|
};
|
||||||
activeLayout: string;
|
activeLayout: string;
|
||||||
setTrackElement: (element: string) => void;
|
|
||||||
setCreateUpdateIssueModal: (open: boolean) => void;
|
setCreateUpdateIssueModal: (open: boolean) => void;
|
||||||
setDuplicateWorkItemModal?: (open: boolean) => void;
|
setDuplicateWorkItemModal?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import { useState, useEffect, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
|
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
|
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||||
// store
|
// store
|
||||||
|
|
@ -78,6 +81,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleLock}
|
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"
|
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"
|
aria-label="Lock"
|
||||||
>
|
>
|
||||||
|
|
@ -90,6 +94,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleLock}
|
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"
|
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"
|
aria-label="Locked"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import ProjectCommonAttributes from "@/components/project/create/common-attribut
|
||||||
import ProjectCreateHeader from "@/components/project/create/header";
|
import ProjectCreateHeader from "@/components/project/create/header";
|
||||||
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
|
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
|
||||||
// hooks
|
// 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";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web types
|
// plane web types
|
||||||
import { TProject } from "@/plane-web/types/projects";
|
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;
|
const { setToFavorite, workspaceSlug, data, onClose, handleNextStep, updateCoverImageStatus } = props;
|
||||||
// store
|
// store
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { captureProjectEvent } = useEventTracker();
|
|
||||||
const { addProjectToFavorites, createProject } = useProject();
|
const { addProjectToFavorites, createProject } = useProject();
|
||||||
// states
|
// states
|
||||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||||
|
|
@ -70,25 +70,30 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||||
if (coverImage) {
|
if (coverImage) {
|
||||||
await updateCoverImageStatus(res.id, coverImage);
|
await updateCoverImageStatus(res.id, coverImage);
|
||||||
}
|
}
|
||||||
const newPayload = {
|
captureSuccess({
|
||||||
...res,
|
|
||||||
state: "SUCCESS",
|
|
||||||
};
|
|
||||||
captureProjectEvent({
|
|
||||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||||
payload: newPayload,
|
payload: {
|
||||||
|
identifier: formData.identifier,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: t("success"),
|
title: t("success"),
|
||||||
message: t("project_created_successfully"),
|
message: t("project_created_successfully"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (setToFavorite) {
|
if (setToFavorite) {
|
||||||
handleAddToFavorites(res.id);
|
handleAddToFavorites(res.id);
|
||||||
}
|
}
|
||||||
handleNextStep(res.id);
|
handleNextStep(res.id);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
captureError({
|
||||||
|
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||||
|
payload: {
|
||||||
|
identifier: formData.identifier,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") {
|
if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
|
import { WORKSPACE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IWorkspace } from "@plane/types";
|
import { IWorkspace } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
|
|
@ -48,7 +49,11 @@ export const DeleteWorkspaceSection: FC<TDeleteWorkspace> = observer((props) =>
|
||||||
{t("workspace_settings.settings.general.delete_workspace_description")}
|
{t("workspace_settings.settings.general.delete_workspace_description")}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<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")}
|
{t("workspace_settings.settings.general.delete_btn")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
// types
|
// 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";
|
import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types";
|
||||||
// store
|
// store
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
import { store } from "@/lib/store-context";
|
import { store } from "@/lib/store-context";
|
||||||
|
|
||||||
export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
||||||
|
|
@ -10,7 +18,10 @@ export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
||||||
c: {
|
c: {
|
||||||
title: "Create a new work item",
|
title: "Create a new work item",
|
||||||
description: "Create a new work item in the current project",
|
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: {
|
p: {
|
||||||
title: "Create a new project",
|
title: "Create a new project",
|
||||||
description: "Create a new project in the current workspace",
|
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: {
|
d: {
|
||||||
title: "Create a new page",
|
title: "Create a new page",
|
||||||
description: "Create a new page in the current project",
|
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: {
|
m: {
|
||||||
title: "Create a new module",
|
title: "Create a new module",
|
||||||
description: "Create a new module in the current project",
|
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: {
|
q: {
|
||||||
title: "Create a new cycle",
|
title: "Create a new cycle",
|
||||||
description: "Create a new cycle in the current project",
|
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: {
|
v: {
|
||||||
title: "Create a new view",
|
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
|
// icons
|
||||||
import { Eye, EyeOff, Info, X, XCircle } from "lucide-react";
|
import { Eye, EyeOff, Info, X, XCircle } from "lucide-react";
|
||||||
// plane imports
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { Button, Input, Spinner } from "@plane/ui";
|
import { Button, Input, Spinner } from "@plane/ui";
|
||||||
import { getPasswordStrength } from "@plane/utils";
|
import { getPasswordStrength } from "@plane/utils";
|
||||||
|
|
@ -16,7 +16,7 @@ import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/accou
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/auth.service";
|
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;
|
const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props;
|
||||||
// plane imports
|
// plane imports
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// ref
|
// ref
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
// states
|
// states
|
||||||
|
|
@ -77,7 +75,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
|
|
||||||
const redirectToUniqueCodeSignIn = async () => {
|
const redirectToUniqueCodeSignIn = async () => {
|
||||||
handleAuthStep(EAuthSteps.UNIQUE_CODE);
|
handleAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||||
captureEvent(AUTH_TRACKER_EVENTS.sign_in_with_code);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const passwordSupport =
|
const passwordSupport =
|
||||||
|
|
@ -85,7 +82,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{isSMTPConfigured ? (
|
{isSMTPConfigured ? (
|
||||||
<Link
|
<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)}`}
|
href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`}
|
||||||
className="text-xs font-medium text-custom-primary-100"
|
className="text-xs font-medium text-custom-primary-100"
|
||||||
>
|
>
|
||||||
|
|
@ -154,17 +151,32 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
: true;
|
: true;
|
||||||
if (isPasswordValid) {
|
if (isPasswordValid) {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
captureEvent(
|
captureSuccess({
|
||||||
mode === EAuthModes.SIGN_IN
|
eventName:
|
||||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
mode === EAuthModes.SIGN_IN
|
||||||
: AUTH_TRACKER_EVENTS.sign_up_with_password
|
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||||
);
|
: AUTH_TRACKER_EVENTS.sign_up_with_password,
|
||||||
|
payload: {
|
||||||
|
email: passwordFormData.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met
|
if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met
|
||||||
} else {
|
} else {
|
||||||
setBannerMessage(true);
|
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" name="csrfmiddlewaretoken" />
|
||||||
<input type="hidden" value={passwordFormData.email} name="email" />
|
<input type="hidden" value={passwordFormData.email} name="email" />
|
||||||
|
|
@ -292,6 +304,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
{isSMTPConfigured && (
|
{isSMTPConfigured && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_WITH_UNIQUE_CODE}
|
||||||
onClick={redirectToUniqueCodeSignIn}
|
onClick={redirectToUniqueCodeSignIn}
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { CircleCheck, XCircle } from "lucide-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 { useTranslation } from "@plane/i18n";
|
||||||
import { Button, Input, Spinner } from "@plane/ui";
|
import { Button, Input, Spinner } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthModes } from "@/helpers/authentication.helper";
|
import { EAuthModes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import useTimer from "@/hooks/use-timer";
|
import useTimer from "@/hooks/use-timer";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/auth.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
|
@ -38,8 +38,6 @@ const defaultValues: TUniqueCodeFormValues = {
|
||||||
|
|
||||||
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||||
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
|
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
|
||||||
// hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// derived values
|
// derived values
|
||||||
const defaultResetTimerValue = 5;
|
const defaultResetTimerValue = 5;
|
||||||
// states
|
// states
|
||||||
|
|
@ -62,10 +60,22 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||||
setResendCodeTimer(defaultResetTimerValue);
|
setResendCodeTimer(defaultResetTimerValue);
|
||||||
handleFormChange("code", uniqueCode?.code || "");
|
handleFormChange("code", uniqueCode?.code || "");
|
||||||
setIsRequestingNewCode(false);
|
setIsRequestingNewCode(false);
|
||||||
|
captureSuccess({
|
||||||
|
eventName: AUTH_TRACKER_EVENTS.new_code_requested,
|
||||||
|
payload: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setResendCodeTimer(0);
|
setResendCodeTimer(0);
|
||||||
console.error("Error while requesting new code");
|
console.error("Error while requesting new code");
|
||||||
setIsRequestingNewCode(false);
|
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"}/`}
|
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
captureEvent(AUTH_TRACKER_EVENTS.code_verify, {
|
captureSuccess({
|
||||||
state: "SUCCESS",
|
eventName: AUTH_TRACKER_EVENTS.code_verify,
|
||||||
first_time: !isExistingEmail,
|
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" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||||
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
|
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
|
||||||
|
|
@ -145,6 +166,7 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-ph-element={AUTH_TRACKER_ELEMENTS.REQUEST_NEW_CODE}
|
||||||
onClick={() => generateNewCode(uniqueCodeFormData.email)}
|
onClick={() => generateNewCode(uniqueCodeFormData.email)}
|
||||||
className={
|
className={
|
||||||
isRequestNewCodeDisabled
|
isRequestNewCodeDisabled
|
||||||
|
|
@ -163,7 +185,14 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2.5">
|
<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 ? (
|
{isRequestingNewCode ? (
|
||||||
t("auth.common.unique_code.sending_code")
|
t("auth.common.unique_code.sending_code")
|
||||||
) : isSubmitting ? (
|
) : isSubmitting ? (
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
import { ContrastIcon, FileText, Layers } from "lucide-react";
|
import { ContrastIcon, FileText, Layers } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { CYCLE_TRACKER_ELEMENTS, MODULE_TRACKER_ELEMENTS, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { DiceIcon } from "@plane/ui";
|
import { DiceIcon } from "@plane/ui";
|
||||||
import { useCommandPalette, useEventTracker } from "@/hooks/store";
|
// hooks
|
||||||
|
import { useCommandPalette } from "@/hooks/store";
|
||||||
// ui
|
// ui
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -16,15 +18,14 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal } =
|
const { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal } =
|
||||||
useCommandPalette();
|
useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Command.Group heading="Cycle">
|
<Command.Group heading="Cycle">
|
||||||
<Command.Item
|
<Command.Item
|
||||||
|
data-ph-element={CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
|
||||||
toggleCreateCycleModal(true);
|
toggleCreateCycleModal(true);
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
@ -38,9 +39,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||||
</Command.Group>
|
</Command.Group>
|
||||||
<Command.Group heading="Module">
|
<Command.Group heading="Module">
|
||||||
<Command.Item
|
<Command.Item
|
||||||
|
data-ph-element={MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
|
||||||
toggleCreateModuleModal(true);
|
toggleCreateModuleModal(true);
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
@ -56,7 +57,6 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
|
||||||
toggleCreateViewModal(true);
|
toggleCreateViewModal(true);
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
@ -70,9 +70,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||||
</Command.Group>
|
</Command.Group>
|
||||||
<Command.Group heading="Page">
|
<Command.Group heading="Page">
|
||||||
<Command.Item
|
<Command.Item
|
||||||
|
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
|
||||||
toggleCreatePageModal({ isOpen: true });
|
toggleCreatePageModal({ isOpen: true });
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import useSWR from "swr";
|
||||||
import { CommandIcon, FolderPlus, Search, Settings, X } from "lucide-react";
|
import { CommandIcon, FolderPlus, Search, Settings, X } from "lucide-react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// plane imports
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { IWorkspaceSearchResults } from "@plane/types";
|
import { IWorkspaceSearchResults } from "@plane/types";
|
||||||
import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui";
|
import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui";
|
||||||
|
|
@ -28,14 +34,8 @@ import {
|
||||||
import { SimpleEmptyState } from "@/components/empty-state";
|
import { SimpleEmptyState } from "@/components/empty-state";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
useCommandPalette,
|
import { useCommandPalette, useIssueDetail, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||||
useEventTracker,
|
|
||||||
useIssueDetail,
|
|
||||||
useProject,
|
|
||||||
useUser,
|
|
||||||
useUserPermissions,
|
|
||||||
} from "@/hooks/store";
|
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import useDebounce from "@/hooks/use-debounce";
|
import useDebounce from "@/hooks/use-debounce";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
@ -74,7 +74,6 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
||||||
useCommandPalette();
|
useCommandPalette();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const projectIdentifier = workItem?.toString().split("-")[0];
|
const projectIdentifier = workItem?.toString().split("-")[0];
|
||||||
const sequence_id = workItem?.toString().split("-")[1];
|
const sequence_id = workItem?.toString().split("-")[1];
|
||||||
// fetch work item details using identifier
|
// fetch work item details using identifier
|
||||||
|
|
@ -346,7 +345,9 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command Palette");
|
captureClick({
|
||||||
|
elementName: WORK_ITEM_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_BUTTON,
|
||||||
|
});
|
||||||
toggleCreateIssueModal(true);
|
toggleCreateIssueModal(true);
|
||||||
}}
|
}}
|
||||||
className="focus:bg-custom-background-80"
|
className="focus:bg-custom-background-80"
|
||||||
|
|
@ -364,7 +365,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON });
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,15 @@ import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// ui
|
// ui
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { COMMAND_PALETTE_TRACKER_ELEMENTS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { copyTextToClipboard } from "@plane/utils";
|
import { copyTextToClipboard } from "@plane/utils";
|
||||||
import { CommandModal, ShortcutsModal } from "@/components/command-palette";
|
import { CommandModal, ShortcutsModal } from "@/components/command-palette";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
useEventTracker,
|
import { useUser, useAppTheme, useCommandPalette, useUserPermissions, useIssueDetail } from "@/hooks/store";
|
||||||
useUser,
|
|
||||||
useAppTheme,
|
|
||||||
useCommandPalette,
|
|
||||||
useUserPermissions,
|
|
||||||
useIssueDetail,
|
|
||||||
} from "@/hooks/store";
|
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// plane web components
|
||||||
import {
|
import {
|
||||||
|
|
@ -42,7 +36,6 @@ export const CommandPalette: FC = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { fetchIssueWithIdentifier } = useIssueDetail();
|
const { fetchIssueWithIdentifier } = useIssueDetail();
|
||||||
const { toggleSidebar } = useAppTheme();
|
const { toggleSidebar } = useAppTheme();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { platform } = usePlatformOS();
|
const { platform } = usePlatformOS();
|
||||||
const { data: currentUser, canPerformAnyCreateAction } = useUser();
|
const { data: currentUser, canPerformAnyCreateAction } = useUser();
|
||||||
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette();
|
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette();
|
||||||
|
|
@ -203,7 +196,7 @@ export const CommandPalette: FC = observer(() => {
|
||||||
toggleSidebar();
|
toggleSidebar();
|
||||||
}
|
}
|
||||||
} else if (!isAnyModalOpen) {
|
} else if (!isAnyModalOpen) {
|
||||||
setTrackElement("Shortcut key");
|
captureClick({ elementName: COMMAND_PALETTE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_KEY });
|
||||||
if (
|
if (
|
||||||
Object.keys(shortcutsList.global).includes(keyPressed) &&
|
Object.keys(shortcutsList.global).includes(keyPressed) &&
|
||||||
((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions())
|
((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions())
|
||||||
|
|
@ -242,7 +235,6 @@ export const CommandPalette: FC = observer(() => {
|
||||||
performProjectCreateActions,
|
performProjectCreateActions,
|
||||||
performWorkspaceCreateActions,
|
performWorkspaceCreateActions,
|
||||||
projectId,
|
projectId,
|
||||||
setTrackElement,
|
|
||||||
shortcutsList,
|
shortcutsList,
|
||||||
toggleCommandPaletteModal,
|
toggleCommandPaletteModal,
|
||||||
toggleShortcutModal,
|
toggleShortcutModal,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { FC, useEffect, useState } from "react";
|
import React, { FC, useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { ArrowRight, ChevronRight } from "lucide-react";
|
import { ArrowRight, ChevronRight } from "lucide-react";
|
||||||
// Plane Imports
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
|
|
@ -13,13 +19,11 @@ import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||||
// hooks
|
// 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";
|
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||||
// services
|
// services
|
||||||
import { CycleService } from "@/services/cycle.service";
|
import { CycleService } from "@/services/cycle.service";
|
||||||
// local imports
|
|
||||||
import { ArchiveCycleModal } from "../archived-cycles";
|
|
||||||
import { CycleDeleteModal } from "../delete-modal";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -38,13 +42,9 @@ const cycleService = new CycleService();
|
||||||
|
|
||||||
export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, cycleDetails, handleClose, isArchived = false } = props;
|
const { workspaceSlug, projectId, cycleDetails, handleClose, isArchived = false } = props;
|
||||||
// states
|
|
||||||
const [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
|
||||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
|
||||||
// hooks
|
// hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { updateCycleDetails } = useCycle();
|
const { updateCycleDetails } = useCycle();
|
||||||
const { captureCycleEvent } = useEventTracker();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { renderFormattedDateInUserTimezone, getProjectUTCOffset } = useTimeZoneConverter(projectId);
|
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 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;
|
if (!workspaceSlug || !projectId || !cycleDetails.id) return;
|
||||||
|
|
||||||
await updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
await updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
captureCycleEvent({
|
captureElementAndEvent({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
element: {
|
||||||
payload: {
|
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||||
...res,
|
},
|
||||||
changed_properties: [changedProperty],
|
event: {
|
||||||
element: "Right side-peek",
|
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
|
payload: {
|
||||||
|
id: cycleDetails.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
captureCycleEvent({
|
captureElementAndEvent({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
element: {
|
||||||
payload: {
|
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||||
...data,
|
},
|
||||||
element: "Right side-peek",
|
event: {
|
||||||
state: "FAILED",
|
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||||
|
state: "ERROR",
|
||||||
|
payload: {
|
||||||
|
id: cycleDetails.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -122,7 +129,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
isDateValid = true;
|
isDateValid = true;
|
||||||
}
|
}
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
submitChanges(payload, "date_range");
|
submitChanges(payload);
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: t("project_cycles.action.update.success.title"),
|
title: t("project_cycles.action.update.success.title"),
|
||||||
|
|
@ -145,24 +152,6 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
return (
|
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="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">
|
<div className="flex items-center justify-center size-5">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@
|
||||||
import { useState, Fragment } from "react";
|
import { useState, Fragment } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// ui
|
// ui
|
||||||
|
import { CYCLE_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useCycle } from "@/hooks/store";
|
import { useCycle } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
|
||||||
|
|
@ -42,16 +44,28 @@ export const ArchiveCycleModal: React.FC<Props> = (props) => {
|
||||||
title: "Archive success",
|
title: "Archive success",
|
||||||
message: "Your archives can be found in project archives.",
|
message: "Your archives can be found in project archives.",
|
||||||
});
|
});
|
||||||
|
captureSuccess({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
onClose();
|
onClose();
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Cycle could not be archived. Please try again.",
|
message: "Cycle could not be archived. Please try again.",
|
||||||
})
|
});
|
||||||
)
|
captureError({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
.finally(() => setIsArchiving(false));
|
.finally(() => setIsArchiving(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ import { useTranslation } from "@plane/i18n";
|
||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// constants
|
// helpers
|
||||||
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useCycle } from "@/hooks/store";
|
import { useCycle } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
|
||||||
interface ICycleDelete {
|
interface ICycleDelete {
|
||||||
|
|
@ -27,7 +28,6 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||||
// states
|
// states
|
||||||
const [loader, setLoader] = useState(false);
|
const [loader, setLoader] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureCycleEvent } = useEventTracker();
|
|
||||||
const { deleteCycle } = useCycle();
|
const { deleteCycle } = useCycle();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
|
|
@ -49,9 +49,11 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Cycle deleted successfully.",
|
message: "Cycle deleted successfully.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureSuccess({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||||
payload: { ...cycle, state: "SUCCESS" },
|
payload: {
|
||||||
|
id: cycle.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
|
|
@ -64,13 +66,16 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureError({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||||
payload: { ...cycle, state: "FAILED" },
|
payload: {
|
||||||
|
id: cycle.id,
|
||||||
|
},
|
||||||
|
error: errors,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => handleClose());
|
.finally(() => handleClose());
|
||||||
} catch (error) {
|
} catch {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Warning!",
|
title: "Warning!",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
||||||
import { useUser } from "@/hooks/store/user/user-user";
|
import { useUser } from "@/hooks/store/user/user-user";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
@ -40,7 +40,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||||
const { projectsWithCreatePermissions } = useUser();
|
const { projectsWithCreatePermissions } = useUser();
|
||||||
// form data
|
// form data
|
||||||
const {
|
const {
|
||||||
formState: { errors, isSubmitting, dirtyFields },
|
formState: { errors, isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
|
|
@ -64,7 +64,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
|
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData))}>
|
||||||
<div className="space-y-5 p-5">
|
<div className="space-y-5 p-5">
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
{!status && (
|
{!status && (
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,13 @@ import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Eye, Users, ArrowRight, CalendarDays } from "lucide-react";
|
import { Eye, Users, ArrowRight, CalendarDays } from "lucide-react";
|
||||||
// types
|
// 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 { useLocalStorage } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ICycle, TCycleGroups } from "@plane/types";
|
import { ICycle, TCycleGroups } from "@plane/types";
|
||||||
|
|
@ -19,7 +25,8 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import { MergedDateDisplay } from "@/components/dropdowns/merged-date";
|
import { MergedDateDisplay } from "@/components/dropdowns/merged-date";
|
||||||
// hooks
|
// 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 { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||||
|
|
@ -57,7 +64,6 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { addCycleToFavorites, removeCycleFromFavorites } = useCycle();
|
const { addCycleToFavorites, removeCycleFromFavorites } = useCycle();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
|
|
@ -98,16 +104,25 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
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);
|
if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
|
||||||
captureEvent(CYCLE_TRACKER_EVENTS.favorite, {
|
captureSuccess({
|
||||||
cycle_id: cycleId,
|
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||||
element: "List layout",
|
payload: {
|
||||||
state: "SUCCESS",
|
id: cycleId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
);
|
.catch((error) => {
|
||||||
|
captureError({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
setPromiseToast(addToFavoritePromise, {
|
setPromiseToast(addToFavoritePromise, {
|
||||||
loading: t("project_cycles.action.favorite.loading"),
|
loading: t("project_cycles.action.favorite.loading"),
|
||||||
|
|
@ -126,17 +141,24 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const removeFromFavoritePromise = removeCycleFromFavorites(
|
const removeFromFavoritePromise = removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||||
workspaceSlug?.toString(),
|
.then(() => {
|
||||||
projectId.toString(),
|
captureSuccess({
|
||||||
cycleId
|
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||||
).then(() => {
|
payload: {
|
||||||
captureEvent(CYCLE_TRACKER_EVENTS.unfavorite, {
|
id: cycleId,
|
||||||
cycle_id: cycleId,
|
},
|
||||||
element: "List layout",
|
});
|
||||||
state: "SUCCESS",
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
captureError({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPromiseToast(removeFromFavoritePromise, {
|
setPromiseToast(removeFromFavoritePromise, {
|
||||||
loading: t("project_cycles.action.unfavorite.loading"),
|
loading: t("project_cycles.action.unfavorite.loading"),
|
||||||
|
|
@ -292,6 +314,7 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
{isEditingAllowed && !cycleDetails.archived_at && (
|
{isEditingAllowed && !cycleDetails.archived_at && (
|
||||||
<FavoriteStar
|
<FavoriteStar
|
||||||
|
data-ph-element={CYCLE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
||||||
import { CycleForm } from "@/components/cycles";
|
import { CycleForm } from "@/components/cycles";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// 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 useKeypress from "@/hooks/use-keypress";
|
||||||
import useLocalStorage from "@/hooks/use-local-storage";
|
import useLocalStorage from "@/hooks/use-local-storage";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
@ -35,7 +36,6 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||||
// states
|
// states
|
||||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureCycleEvent } = useEventTracker();
|
|
||||||
const { workspaceProjectIds } = useProject();
|
const { workspaceProjectIds } = useProject();
|
||||||
const { createCycle, updateCycleDetails } = useCycle();
|
const { createCycle, updateCycleDetails } = useCycle();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
@ -63,9 +63,11 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Cycle created successfully.",
|
message: "Cycle created successfully.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureSuccess({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
eventName: CYCLE_TRACKER_EVENTS.create,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: {
|
||||||
|
id: res.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -74,23 +76,24 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.detail ?? "Error in creating cycle. Please try again.",
|
message: err?.detail ?? "Error in creating cycle. Please try again.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureError({
|
||||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
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;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||||
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const changed_properties = Object.keys(dirtyFields);
|
captureSuccess({
|
||||||
captureCycleEvent({
|
|
||||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||||
payload: { ...res, changed_properties: changed_properties, state: "SUCCESS" },
|
payload: {
|
||||||
|
id: res.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -99,15 +102,15 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureCycleEvent({
|
|
||||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
|
||||||
payload: { ...payload, state: "FAILED" },
|
|
||||||
});
|
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.detail ?? "Error in updating cycle. Please try again.",
|
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;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (formData: Partial<ICycle>, dirtyFields: any) => {
|
const handleFormSubmit = async (formData: Partial<ICycle>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload: Partial<ICycle> = {
|
const payload: Partial<ICycle> = {
|
||||||
|
|
@ -145,7 +148,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
if (data) await handleUpdateCycle(data.id, payload, dirtyFields);
|
if (data) await handleUpdateCycle(data.id, payload);
|
||||||
else {
|
else {
|
||||||
await handleCreateCycle(payload).then(() => {
|
await handleCreateCycle(payload).then(() => {
|
||||||
setCycleTab("all");
|
setCycleTab("all");
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
||||||
// icons
|
// icons
|
||||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||||
// ui
|
// 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 { useTranslation } from "@plane/i18n";
|
||||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { copyUrlToClipboard, cn } from "@plane/utils";
|
import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||||
|
|
@ -14,7 +19,8 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||||
import { ArchiveCycleModal, CycleCreateUpdateModal, CycleDeleteModal } from "@/components/cycles";
|
import { ArchiveCycleModal, CycleCreateUpdateModal, CycleDeleteModal } from "@/components/cycles";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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 { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { useEndCycle, EndCycleModal } from "@/plane-web/components/cycles";
|
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 [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { getCycleById, restoreCycle } = useCycle();
|
const { getCycleById, restoreCycle } = useCycle();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -69,7 +74,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
|
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
|
||||||
|
|
||||||
const handleEditCycle = () => {
|
const handleEditCycle = () => {
|
||||||
setTrackElement("Cycles page list layout");
|
|
||||||
setUpdateModal(true);
|
setUpdateModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -83,18 +87,29 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
title: t("project_cycles.action.restore.success.title"),
|
title: t("project_cycles.action.restore.success.title"),
|
||||||
message: t("project_cycles.action.restore.success.description"),
|
message: t("project_cycles.action.restore.success.description"),
|
||||||
});
|
});
|
||||||
|
captureSuccess({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/cycles`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/archives/cycles`);
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("project_cycles.action.restore.failed.title"),
|
title: t("project_cycles.action.restore.failed.title"),
|
||||||
message: t("project_cycles.action.restore.failed.description"),
|
message: t("project_cycles.action.restore.failed.description"),
|
||||||
})
|
});
|
||||||
);
|
captureError({
|
||||||
|
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||||
|
payload: {
|
||||||
|
id: cycleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handleDeleteCycle = () => {
|
const handleDeleteCycle = () => {
|
||||||
setTrackElement("Cycles page list layout");
|
|
||||||
setDeleteModal(true);
|
setDeleteModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -149,6 +164,16 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{cycleDetails && (
|
{cycleDetails && (
|
||||||
|
|
@ -187,7 +212,7 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect maxHeight="lg" buttonClassName={customClassName}>
|
<CustomMenu ellipsis placement="bottom-end" closeOnSelect maxHeight="lg" buttonClassName={customClassName}>
|
||||||
{MENU_ITEMS.map((item) => {
|
{MENU_ITEMS.map((item) => {
|
||||||
if (item.shouldRender === false) return null;
|
if (item.shouldRender === false) return null;
|
||||||
|
|
@ -197,6 +222,9 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({
|
||||||
|
elementName: CYCLE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||||
|
});
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// components
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// plane imports
|
||||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { ContentWrapper } from "@plane/ui";
|
import { ContentWrapper } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
// components
|
||||||
import { TourRoot } from "@/components/onboarding";
|
import { TourRoot } from "@/components/onboarding";
|
||||||
// constants
|
|
||||||
// helpers
|
// helpers
|
||||||
|
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUserProfile, useEventTracker, useUser } from "@/hooks/store";
|
import { useUserProfile, useUser } from "@/hooks/store";
|
||||||
import { useHome } from "@/hooks/store/use-home";
|
import { useHome } from "@/hooks/store/use-home";
|
||||||
import useSize from "@/hooks/use-window-size";
|
import useSize from "@/hooks/use-window-size";
|
||||||
|
// plane web components
|
||||||
import { HomePeekOverviewsRoot } from "@/plane-web/components/home";
|
import { HomePeekOverviewsRoot } from "@/plane-web/components/home";
|
||||||
|
// local imports
|
||||||
import { DashboardWidgets } from "./home-dashboard-widgets";
|
import { DashboardWidgets } from "./home-dashboard-widgets";
|
||||||
import { UserGreetingsView } from "./user-greetings";
|
import { UserGreetingsView } from "./user-greetings";
|
||||||
|
|
||||||
|
|
@ -21,7 +24,6 @@ export const WorkspaceHomeView = observer(() => {
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
|
const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { toggleWidgetSettings, fetchWidgets } = useHome();
|
const { toggleWidgetSettings, fetchWidgets } = useHome();
|
||||||
const [windowWidth] = useSize();
|
const [windowWidth] = useSize();
|
||||||
|
|
||||||
|
|
@ -38,9 +40,11 @@ export const WorkspaceHomeView = observer(() => {
|
||||||
const handleTourCompleted = () => {
|
const handleTourCompleted = () => {
|
||||||
updateTourCompleted()
|
updateTourCompleted()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.complete, {
|
captureSuccess({
|
||||||
user_id: currentUser?.id,
|
eventName: PRODUCT_TOUR_TRACKER_EVENTS.complete,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
user_id: currentUser?.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,14 @@ import Link from "next/link";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
|
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useLocalStorage } from "@plane/hooks";
|
import { useLocalStorage } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { cn, getFileURL } from "@plane/utils";
|
import { cn, getFileURL } from "@plane/utils";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import {
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
useCommandPalette,
|
import { useCommandPalette, useProject, useUser, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||||
useEventTracker,
|
|
||||||
useProject,
|
|
||||||
useUser,
|
|
||||||
useUserPermissions,
|
|
||||||
useWorkspace,
|
|
||||||
} from "@/hooks/store";
|
|
||||||
// plane web constants
|
// plane web constants
|
||||||
|
|
||||||
export const NoProjectsEmptyState = observer(() => {
|
export const NoProjectsEmptyState = observer(() => {
|
||||||
|
|
@ -27,7 +21,6 @@ export const NoProjectsEmptyState = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { joinedProjectIds } = useProject();
|
const { joinedProjectIds } = useProject();
|
||||||
const { currentWorkspace: activeWorkspace } = useWorkspace();
|
const { currentWorkspace: activeWorkspace } = useWorkspace();
|
||||||
|
|
@ -59,8 +52,8 @@ export const NoProjectsEmptyState = observer(() => {
|
||||||
if (!canCreateProject) return;
|
if (!canCreateProject) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setTrackElement("Sidebar");
|
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
|
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||||
},
|
},
|
||||||
disabled: !canCreateProject,
|
disabled: !canCreateProject,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { Dispatch, SetStateAction, useEffect, useMemo, useRef } from "react";
|
import { Dispatch, SetStateAction, useEffect, useMemo, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// plane imports
|
// plane imports
|
||||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { EditorRefApi } from "@plane/editor";
|
import { EditorRefApi } from "@plane/editor";
|
||||||
|
|
@ -22,7 +21,8 @@ import {
|
||||||
} from "@/components/issues";
|
} from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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";
|
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
||||||
// store types
|
// store types
|
||||||
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
|
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
|
||||||
|
|
@ -45,8 +45,6 @@ type Props = {
|
||||||
|
|
||||||
export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
|
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
|
||||||
// navigation
|
|
||||||
const pathname = usePathname();
|
|
||||||
// refs
|
// refs
|
||||||
const editorRef = useRef<EditorRefApi>(null);
|
const editorRef = useRef<EditorRefApi>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -57,8 +55,6 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
||||||
const { removeIssue, archiveIssue } = useIssueDetail();
|
const { removeIssue, archiveIssue } = useIssueDetail();
|
||||||
// reload confirmation
|
// reload confirmation
|
||||||
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
||||||
// event tracker
|
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSubmitting === "submitted") {
|
if (isSubmitting === "submitted") {
|
||||||
|
|
@ -104,10 +100,9 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
message: "Work item deleted successfully",
|
message: "Work item deleted successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: _issueId, state: "SUCCESS", element: "Work item detail page" },
|
payload: { id: _issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in deleting work item:", 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,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: "Work item delete failed",
|
message: "Work item delete failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: _issueId, state: "FAILED", element: "Work item detail page" },
|
payload: { id: _issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: async (_workspaceSlug: string, _projectId: string, _issueId: string, data: Partial<TIssue>) => {
|
update: async (_workspaceSlug: string, _projectId: string, _issueId: string, data: Partial<TIssue>) => {
|
||||||
try {
|
try {
|
||||||
await inboxIssue.updateIssue(data);
|
await inboxIssue.updateIssue(data);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Inbox work item updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...data, state: "SUCCESS", element: "Inbox" },
|
payload: { id: _issueId },
|
||||||
updates: {
|
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
title: "Work item update failed",
|
title: "Work item update failed",
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: "Work item update failed",
|
message: "Work item update failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Inbox work item updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "SUCCESS", element: "Inbox" },
|
payload: { id: _issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
await archiveIssue(workspaceSlug, projectId, issueId);
|
await archiveIssue(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Work item details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in archiving issue:", error);
|
console.log("Error in archiving issue:", error);
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Work item details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { FC, FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
import { FC, FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// plane imports
|
// plane imports
|
||||||
import { ETabIndices, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
import { ETabIndices, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { EditorRefApi } from "@plane/editor";
|
import { EditorRefApi } from "@plane/editor";
|
||||||
|
|
@ -14,9 +13,9 @@ import { renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { InboxIssueTitle, InboxIssueDescription, InboxIssueProperties } from "@/components/inbox/modals/create-modal";
|
import { InboxIssueTitle, InboxIssueDescription, InboxIssueProperties } from "@/components/inbox/modals/create-modal";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
|
||||||
// hooks
|
// 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 { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import useKeypress from "@/hooks/use-keypress";
|
import useKeypress from "@/hooks/use-keypress";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
@ -53,14 +52,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
||||||
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
|
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const pathname = usePathname();
|
|
||||||
// refs
|
// refs
|
||||||
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
||||||
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
|
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
|
||||||
const formRef = useRef<HTMLFormElement | null>(null);
|
const formRef = useRef<HTMLFormElement | null>(null);
|
||||||
const modalContainerRef = useRef<HTMLDivElement | null>(null);
|
const modalContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { createInboxIssue } = useProjectInbox();
|
const { createInboxIssue } = useProjectInbox();
|
||||||
const { getWorkspaceBySlug } = useWorkspace();
|
const { getWorkspaceBySlug } = useWorkspace();
|
||||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||||
|
|
@ -167,14 +164,11 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
||||||
descriptionEditorRef?.current?.clearEditor();
|
descriptionEditorRef?.current?.clearEditor();
|
||||||
setFormData(defaultIssueData);
|
setFormData(defaultIssueData);
|
||||||
}
|
}
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: {
|
payload: {
|
||||||
...formData,
|
id: res?.issue?.id,
|
||||||
state: "SUCCESS",
|
|
||||||
element: "Inbox page",
|
|
||||||
},
|
},
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -184,14 +178,12 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: {
|
payload: {
|
||||||
...formData,
|
id: formData?.id,
|
||||||
state: "FAILED",
|
|
||||||
element: "Inbox page",
|
|
||||||
},
|
},
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,20 @@ import { observer } from "mobx-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFormContext, Controller } from "react-hook-form";
|
import { useFormContext, Controller } from "react-hook-form";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { IJiraImporterForm } from "@plane/types";
|
import { IJiraImporterForm } from "@plane/types";
|
||||||
// hooks
|
// hooks
|
||||||
// components
|
// components
|
||||||
import { CustomSelect, Input } from "@plane/ui";
|
import { CustomSelect, Input } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "@plane/utils";
|
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
|
// types
|
||||||
|
|
||||||
export const JiraGetImportDetail: React.FC = observer(() => {
|
export const JiraGetImportDetail: React.FC = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { workspaceProjectIds, getProjectById } = useProject();
|
const { workspaceProjectIds, getProjectById } = useProject();
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
|
|
@ -201,8 +202,9 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Jira import detail page");
|
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE });
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
}}
|
}}
|
||||||
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
|
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";
|
"use client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { EIssueServiceType, TIssueServiceType } from "@plane/types";
|
import { EIssueServiceType, TIssueServiceType } from "@plane/types";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { TAttachmentUploadStatus } from "@/store/issue/issue-details/attachment.store";
|
import { TAttachmentUploadStatus } from "@/store/issue/issue-details/attachment.store";
|
||||||
|
|
||||||
|
|
@ -31,7 +33,6 @@ export const useAttachmentOperations = (
|
||||||
const {
|
const {
|
||||||
attachment: { createAttachment, removeAttachment, getAttachmentsUploadStatusByIssueId },
|
attachment: { createAttachment, removeAttachment, getAttachmentsUploadStatusByIssueId },
|
||||||
} = useIssueDetail(issueServiceType);
|
} = useIssueDetail(issueServiceType);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
|
|
||||||
const attachmentOperations: TAttachmentOperations = useMemo(
|
const attachmentOperations: TAttachmentOperations = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
@ -51,19 +52,16 @@ export const useAttachmentOperations = (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await attachmentUploadPromise;
|
await attachmentUploadPromise;
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Issue attachment added",
|
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.add,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "attachment",
|
|
||||||
change_details: res.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Issue attachment added",
|
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.add,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
|
error: error as Error,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -77,22 +75,15 @@ export const useAttachmentOperations = (
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Attachment removed",
|
title: "Attachment removed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Issue attachment deleted",
|
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.remove,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "attachment",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Issue attachment deleted",
|
eventName: WORK_ITEM_TRACKER_EVENTS.attachment.remove,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "attachment",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
message: "The Attachment could not be removed",
|
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);
|
const attachmentsUploadStatus = getAttachmentsUploadStatusByIssueId(issueId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// plane imports
|
// plane imports
|
||||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -8,7 +7,8 @@ import { EIssueServiceType, TIssue, TIssueServiceType } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { copyUrlToClipboard } from "@plane/utils";
|
import { copyUrlToClipboard } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
export type TRelationIssueOperations = {
|
export type TRelationIssueOperations = {
|
||||||
copyLink: (path: string) => void;
|
copyLink: (path: string) => void;
|
||||||
|
|
@ -20,8 +20,6 @@ export const useRelationOperations = (
|
||||||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||||
): TRelationIssueOperations => {
|
): TRelationIssueOperations => {
|
||||||
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
const entityName = issueServiceType === EIssueServiceType.ISSUES ? "Work item" : "Epic";
|
const entityName = issueServiceType === EIssueServiceType.ISSUES ? "Work item" : "Epic";
|
||||||
|
|
@ -40,29 +38,20 @@ export const useRelationOperations = (
|
||||||
update: async (workspaceSlug, projectId, issueId, data) => {
|
update: async (workspaceSlug, projectId, issueId, data) => {
|
||||||
try {
|
try {
|
||||||
await updateIssue(workspaceSlug, projectId, issueId, data);
|
await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
title: t("toast.success"),
|
title: t("toast.success"),
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
message: t("entity.update.success", { entity: entityName }),
|
message: t("entity.update.success", { entity: entityName }),
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
|
|
@ -74,22 +63,21 @@ export const useRelationOperations = (
|
||||||
remove: async (workspaceSlug, projectId, issueId) => {
|
remove: async (workspaceSlug, projectId, issueId) => {
|
||||||
try {
|
try {
|
||||||
return removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
return removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[captureIssueEvent, entityName, pathname, removeIssue, t, updateIssue]
|
[entityName, removeIssue, t, updateIssue]
|
||||||
);
|
);
|
||||||
|
|
||||||
return issueOperations;
|
return issueOperations;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssueServiceType, TIssueServiceType, TSubIssueOperations } from "@plane/types";
|
import { EIssueServiceType, TIssueServiceType, TSubIssueOperations } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { copyUrlToClipboard } from "@plane/utils";
|
import { copyUrlToClipboard } from "@plane/utils";
|
||||||
// hooks
|
// 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
|
// plane web helpers
|
||||||
import { updateEpicAnalytics } from "@/plane-web/helpers/epic-analytics";
|
import { updateEpicAnalytics } from "@/plane-web/helpers/epic-analytics";
|
||||||
|
|
||||||
export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSubIssueOperations => {
|
export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSubIssueOperations => {
|
||||||
// router
|
// router
|
||||||
const { epicId: epicIdParam } = useParams();
|
const { epicId: epicIdParam } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -32,7 +33,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
const { peekIssue: epicPeekIssue } = useIssueDetail(EIssueServiceType.EPICS);
|
const { peekIssue: epicPeekIssue } = useIssueDetail(EIssueServiceType.EPICS);
|
||||||
// const { updateEpicAnalytics } = useIssueTypes();
|
// const { updateEpicAnalytics } = useIssueTypes();
|
||||||
const { updateAnalytics } = updateEpicAnalytics();
|
const { updateAnalytics } = updateEpicAnalytics();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const epicId = epicIdParam || epicPeekIssue?.issueId;
|
const epicId = epicIdParam || epicPeekIssue?.issueId;
|
||||||
|
|
@ -128,14 +128,9 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Sub-issue updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.update,
|
||||||
payload: { ...oldIssue, ...issueData, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
updates: {
|
|
||||||
changed_property: Object.keys(issueData).join(","),
|
|
||||||
change_details: Object.values(issueData).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -143,15 +138,11 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
message: t("sub_work_item.update.success"),
|
message: t("sub_work_item.update.success"),
|
||||||
});
|
});
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Sub-issue updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.update,
|
||||||
payload: { ...oldIssue, ...issueData, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: Object.keys(issueData).join(","),
|
|
||||||
change_details: Object.values(issueData).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -179,25 +170,16 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
title: t("toast.success"),
|
title: t("toast.success"),
|
||||||
message: t("sub_work_item.remove.success"),
|
message: t("sub_work_item.remove.success"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Sub-issue removed",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.remove,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
updates: {
|
|
||||||
changed_property: "parent_id",
|
|
||||||
change_details: parentIssueId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Sub-issue removed",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.remove,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "parent_id",
|
|
||||||
change_details: parentIssueId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -210,18 +192,17 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
try {
|
try {
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
return deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId).then(() => {
|
return deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Sub-issue deleted",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.delete,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Sub-issue removed",
|
eventName: WORK_ITEM_TRACKER_EVENTS.sub_issue.delete,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, parent_id: parentIssueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -232,7 +213,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
captureIssueEvent,
|
|
||||||
createSubIssues,
|
createSubIssues,
|
||||||
deleteSubIssue,
|
deleteSubIssue,
|
||||||
epicId,
|
epicId,
|
||||||
|
|
@ -240,7 +220,6 @@ export const useSubIssueOperations = (issueServiceType: TIssueServiceType): TSub
|
||||||
getIssueById,
|
getIssueById,
|
||||||
getStateById,
|
getStateById,
|
||||||
issueServiceType,
|
issueServiceType,
|
||||||
pathname,
|
|
||||||
removeSubIssue,
|
removeSubIssue,
|
||||||
setSubIssueHelpers,
|
setSubIssueHelpers,
|
||||||
t,
|
t,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { LayersIcon, Plus } from "lucide-react";
|
import { LayersIcon, Plus } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
|
|
@ -28,7 +30,6 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
setIssueCrudOperationState,
|
setIssueCrudOperationState,
|
||||||
issueCrudOperationState,
|
issueCrudOperationState,
|
||||||
} = useIssueDetail(issueServiceType);
|
} = useIssueDetail(issueServiceType);
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const issue = getIssueById(issueId);
|
const issue = getIssueById(issueId);
|
||||||
|
|
@ -52,13 +53,13 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateNew = () => {
|
const handleCreateNew = () => {
|
||||||
setTrackElement("Issue detail nested sub-issue");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.create });
|
||||||
handleIssueCrudState("create", issueId, null);
|
handleIssueCrudState("create", issueId, null);
|
||||||
toggleCreateIssueModal(true);
|
toggleCreateIssueModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddExisting = () => {
|
const handleAddExisting = () => {
|
||||||
setTrackElement("Issue detail nested sub-issue");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.sub_issue.add_existing });
|
||||||
handleIssueCrudState("existing", issueId, null);
|
handleIssueCrudState("existing", issueId, null);
|
||||||
toggleSubIssuesModal(issue.id);
|
toggleSubIssuesModal(issue.id);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import React, { FC, useState } from "react";
|
import React, { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import { ArchiveIcon, ArchiveRestoreIcon, LinkIcon, Trash2 } from "lucide-react";
|
import { ArchiveIcon, ArchiveRestoreIcon, LinkIcon, Trash2 } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ARCHIVABLE_STATE_GROUPS,
|
ARCHIVABLE_STATE_GROUPS,
|
||||||
|
|
@ -18,8 +17,8 @@ import { cn, generateWorkItemLink, copyTextToClipboard } from "@plane/utils";
|
||||||
import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues";
|
import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import {
|
import {
|
||||||
useEventTracker,
|
|
||||||
useIssueDetail,
|
useIssueDetail,
|
||||||
useIssues,
|
useIssues,
|
||||||
useProject,
|
useProject,
|
||||||
|
|
@ -64,8 +63,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues: { removeIssue: removeArchivedIssue },
|
issues: { removeIssue: removeArchivedIssue },
|
||||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const issue = getIssueById(issueId);
|
const issue = getIssueById(issueId);
|
||||||
|
|
@ -103,22 +100,21 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
return deleteIssue(workspaceSlug, projectId, issueId).then(() => {
|
return deleteIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||||
router.push(redirectionPath);
|
router.push(redirectionPath);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Work item detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
title: t("toast.error "),
|
title: t("toast.error "),
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Work item detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -128,16 +124,15 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
|
||||||
await archiveIssue(workspaceSlug, projectId, issueId).then(() => {
|
await archiveIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/issues/${issue.id}`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/archives/issues/${issue.id}`);
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { FC, useMemo } from "react";
|
import { FC, useMemo } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// types
|
// types
|
||||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -14,7 +13,8 @@ import { EmptyState } from "@/components/common";
|
||||||
import { IssueDetailsSidebar, IssuePeekOverview } from "@/components/issues";
|
import { IssueDetailsSidebar, IssuePeekOverview } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// 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";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// images
|
// images
|
||||||
import emptyIssue from "@/public/empty-state/issue.svg";
|
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;
|
const { workspaceSlug, projectId, issueId, is_archived = false } = props;
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const pathname = usePathname();
|
|
||||||
// hooks
|
// hooks
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
|
|
@ -74,7 +73,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues: { removeIssue: removeArchivedIssue },
|
issues: { removeIssue: removeArchivedIssue },
|
||||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { issueDetailSidebarCollapsed } = useAppTheme();
|
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>) => {
|
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||||
try {
|
try {
|
||||||
await updateIssue(workspaceSlug, projectId, issueId, data);
|
await updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in updating issue:", error);
|
console.log("Error in updating issue:", error);
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
title: t("common.error.label"),
|
title: t("common.error.label"),
|
||||||
|
|
@ -126,10 +115,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
message: t("entity.delete.success", { entity: t("issue.label") }),
|
message: t("entity.delete.success", { entity: t("issue.label") }),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in deleting issue:", error);
|
console.log("Error in deleting issue:", error);
|
||||||
|
|
@ -138,85 +126,66 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: t("entity.delete.failed", { entity: t("issue.label") }),
|
message: t("entity.delete.failed", { entity: t("issue.label") }),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
await archiveIssue(workspaceSlug, projectId, issueId);
|
await archiveIssue(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in archiving issue:", error);
|
console.log("Error in archiving issue:", error);
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue details page" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addCycleToIssue: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
|
addCycleToIssue: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
await addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
await addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("common.error.label"),
|
title: t("common.error.label"),
|
||||||
message: t("issue.add.cycle.failed"),
|
message: t("issue.add.cycle.failed"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
||||||
try {
|
try {
|
||||||
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issueIds, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("common.error.label"),
|
title: t("common.error.label"),
|
||||||
message: t("issue.add.cycle.failed"),
|
message: t("issue.add.cycle.failed"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -235,24 +204,15 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await removeFromCyclePromise;
|
await removeFromCyclePromise;
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -271,24 +231,15 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await removeFromModulePromise;
|
await removeFromModulePromise;
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -300,14 +251,9 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
removeModuleIds: string[]
|
removeModuleIds: string[]
|
||||||
) => {
|
) => {
|
||||||
const promise = await changeModulesInIssue(workspaceSlug, projectId, issueId, addModuleIds, removeModuleIds);
|
const promise = await changeModulesInIssue(workspaceSlug, projectId, issueId, addModuleIds, removeModuleIds);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: { addModuleIds, removeModuleIds },
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
@ -324,9 +270,8 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||||
removeIssueFromCycle,
|
removeIssueFromCycle,
|
||||||
changeModulesInIssue,
|
changeModulesInIssue,
|
||||||
removeIssueFromModule,
|
removeIssueFromModule,
|
||||||
captureIssueEvent,
|
|
||||||
pathname,
|
|
||||||
t,
|
t,
|
||||||
|
issueId,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,15 @@ import size from "lodash/size";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "@/components/core";
|
import { ExistingIssuesListModal } from "@/components/core";
|
||||||
import { DetailedEmptyState } from "@/components/empty-state";
|
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";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
export const CycleEmptyState: React.FC = observer(() => {
|
export const CycleEmptyState: React.FC = observer(() => {
|
||||||
|
|
@ -27,7 +28,6 @@ export const CycleEmptyState: React.FC = observer(() => {
|
||||||
const { getCycleById } = useCycle();
|
const { getCycleById } = useCycle();
|
||||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
|
|
@ -133,7 +133,7 @@ export const CycleEmptyState: React.FC = observer(() => {
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: t("project_cycles.empty_state.no_issues.primary_button.text"),
|
text: t("project_cycles.empty_state.no_issues.primary_button.text"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("Cycle issue empty state");
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE });
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||||
},
|
},
|
||||||
disabled: !canPerformEmptyStateActions,
|
disabled: !canPerformEmptyStateActions,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssuesStoreType, EUserWorkspaceRoles } from "@plane/types";
|
import { EIssuesStoreType, EUserWorkspaceRoles } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
// hooks
|
// 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";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||||
|
|
@ -17,7 +18,6 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { workspaceProjectIds } = useProject();
|
const { workspaceProjectIds } = useProject();
|
||||||
const { toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const hasMemberLevelPermission = allowPermissions(
|
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")}
|
title={t("workspace_projects.empty_state.no_projects.primary_button.comic.title")}
|
||||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("All issues empty state");
|
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||||
}}
|
}}
|
||||||
disabled={!hasMemberLevelPermission}
|
disabled={!hasMemberLevelPermission}
|
||||||
/>
|
/>
|
||||||
|
|
@ -67,7 +67,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||||
? {
|
? {
|
||||||
text: t(`workspace_views.empty_state.${resolvedCurrentView}.primary_button.text`),
|
text: t(`workspace_views.empty_state.${resolvedCurrentView}.primary_button.text`),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("All issues empty state");
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||||
},
|
},
|
||||||
disabled: !hasMemberLevelPermission,
|
disabled: !hasMemberLevelPermission,
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ import size from "lodash/size";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions, ISearchIssueResponse } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "@/components/core";
|
import { ExistingIssuesListModal } from "@/components/core";
|
||||||
import { DetailedEmptyState } from "@/components/empty-state";
|
import { DetailedEmptyState } from "@/components/empty-state";
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useIssues, useUserPermissions } from "@/hooks/store";
|
||||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
export const ModuleEmptyState: React.FC = observer(() => {
|
export const ModuleEmptyState: React.FC = observer(() => {
|
||||||
|
|
@ -26,7 +27,6 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const userFilters = issuesFilter?.issueFilters?.filters;
|
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||||
|
|
@ -119,7 +119,7 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: t("project_module.empty_state.no_issues.primary_button.text"),
|
text: t("project_module.empty_state.no_issues.primary_button.text"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("Module issue empty state");
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.MODULE });
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||||
},
|
},
|
||||||
disabled: !canPerformEmptyStateActions,
|
disabled: !canPerformEmptyStateActions,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ import size from "lodash/size";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EIssueFilterType, EUserPermissionsLevel } from "@plane/constants";
|
import { EIssueFilterType, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions } from "@plane/types";
|
import { EIssuesStoreType, EUserProjectRoles, IIssueFilterOptions } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useIssues, useUserPermissions } from "@/hooks/store";
|
||||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
export const ProjectEmptyState: React.FC = observer(() => {
|
export const ProjectEmptyState: React.FC = observer(() => {
|
||||||
|
|
@ -18,7 +19,6 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -76,7 +76,7 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
||||||
title={t("project_issues.empty_state.no_issues.primary_button.comic.title")}
|
title={t("project_issues.empty_state.no_issues.primary_button.comic.title")}
|
||||||
description={t("project_issues.empty_state.no_issues.primary_button.comic.description")}
|
description={t("project_issues.empty_state.no_issues.primary_button.comic.description")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Project issue empty state");
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.WORK_ITEMS });
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||||
}}
|
}}
|
||||||
disabled={!canPerformEmptyStateActions}
|
disabled={!canPerformEmptyStateActions}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
// components
|
// components
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { EIssuesStoreType } from "@plane/types";
|
import { EIssuesStoreType } from "@plane/types";
|
||||||
import { EmptyState } from "@/components/common";
|
import { EmptyState } from "@/components/common";
|
||||||
// constants
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import emptyIssue from "@/public/empty-state/issue.svg";
|
import emptyIssue from "@/public/empty-state/issue.svg";
|
||||||
|
|
||||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateIssueModal } = useCommandPalette();
|
const { toggleCreateIssueModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
|
|
@ -34,7 +33,7 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||||
text: "New work item",
|
text: "New work item",
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("View work item empty state");
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.PROJECT_VIEW });
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
EIssueFilterType,
|
EIssueFilterType,
|
||||||
EUserPermissions,
|
EUserPermissions,
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
GLOBAL_VIEW_TOUR_TRACKER_EVENTS,
|
GLOBAL_VIEW_TRACKER_EVENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { EIssuesStoreType, EViewAccess, IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
import { EIssuesStoreType, EViewAccess, IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
||||||
import { Header, EHeaderVariant, Loader } from "@plane/ui";
|
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 { UpdateViewComponent } from "@/components/views/update-view-component";
|
||||||
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||||
// hooks
|
// 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";
|
import { getAreFiltersEqual } from "../../../utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -39,7 +40,6 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
const { workspaceLabels } = useLabel();
|
const { workspaceLabels } = useLabel();
|
||||||
const { globalViewMap, updateGlobalView } = useGlobalView();
|
const { globalViewMap, updateGlobalView } = useGlobalView();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { data } = useUser();
|
const { data } = useUser();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
|
|
@ -107,15 +107,25 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
||||||
const handleUpdateView = () => {
|
const handleUpdateView = () => {
|
||||||
if (!workspaceSlug || !globalViewId) return;
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
|
|
||||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters).then((res) => {
|
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters)
|
||||||
if (res)
|
.then((res) => {
|
||||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, {
|
if (res)
|
||||||
view_id: res.id,
|
captureSuccess({
|
||||||
applied_filters: res.filters,
|
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
element: "Spreadsheet view",
|
view_id: globalViewId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
captureError({
|
||||||
|
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||||
|
payload: {
|
||||||
|
view_id: globalViewId,
|
||||||
|
},
|
||||||
|
error: error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// add a placeholder object instead of appliedFilters if it is undefined
|
// add a placeholder object instead of appliedFilters if it is undefined
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
EIssueLayoutTypes,
|
EIssueLayoutTypes,
|
||||||
EIssueFilterType,
|
EIssueFilterType,
|
||||||
|
|
@ -17,7 +17,8 @@ import { EIssueServiceType, EIssuesStoreType } from "@plane/types";
|
||||||
import { DeleteIssueModal } from "@/components/issues";
|
import { DeleteIssueModal } from "@/components/issues";
|
||||||
//constants
|
//constants
|
||||||
//hooks
|
//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 { useGroupIssuesDragNDrop } from "@/hooks/use-group-dragndrop";
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||||
|
|
@ -62,11 +63,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||||
} = props;
|
} = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const storeType = useIssueStoreType() as KanbanStoreType;
|
const storeType = useIssueStoreType() as KanbanStoreType;
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { issueMap, issuesFilter, issues } = useIssues(storeType);
|
const { issueMap, issuesFilter, issues } = useIssues(storeType);
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
|
|
@ -205,15 +204,23 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||||
|
|
||||||
if (!draggedIssueId || !draggedIssue) return;
|
if (!draggedIssueId || !draggedIssue) return;
|
||||||
|
|
||||||
await removeIssue(draggedIssue.project_id, draggedIssueId).finally(() => {
|
await removeIssue(draggedIssue.project_id, draggedIssueId)
|
||||||
setDeleteIssueModal(false);
|
.then(() => {
|
||||||
setDraggedIssueId(undefined);
|
captureSuccess({
|
||||||
captureIssueEvent({
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
payload: { id: draggedIssueId },
|
||||||
payload: { id: draggedIssueId, state: "FAILED", element: "Kanban layout drag & drop" },
|
});
|
||||||
path: pathname,
|
})
|
||||||
|
.catch(() => {
|
||||||
|
captureError({
|
||||||
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
|
payload: { id: draggedIssueId },
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setDeleteIssueModal(false);
|
||||||
|
setDraggedIssueId(undefined);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCollapsedGroups = useCallback(
|
const handleCollapsedGroups = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
// lucide icons
|
// lucide icons
|
||||||
import { Minimize2, Maximize2, Circle, Plus } from "lucide-react";
|
import { Minimize2, Maximize2, Circle, Plus } from "lucide-react";
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { TIssue, ISearchIssueResponse, TIssueKanbanFilters, TIssueGroupByOptions } from "@plane/types";
|
import { TIssue, ISearchIssueResponse, TIssueKanbanFilters, TIssueGroupByOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/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 { ExistingIssuesListModal } from "@/components/core";
|
||||||
import { CreateUpdateIssueModal } from "@/components/issues";
|
import { CreateUpdateIssueModal } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
import { useEventTracker } from "@/hooks/store";
|
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal";
|
import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal";
|
||||||
// types
|
// types
|
||||||
|
|
@ -56,7 +56,6 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||||
// hooks
|
// hooks
|
||||||
const storeType = useIssueStoreType();
|
const storeType = useIssueStoreType();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
@ -167,7 +166,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Kanban layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -175,7 +174,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Kanban layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing });
|
||||||
setOpenExistingIssueListModal(true);
|
setOpenExistingIssueListModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -186,7 +185,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
<div
|
<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"
|
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={() => {
|
onClick={() => {
|
||||||
setTrackElement("Kanban layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
import { CircleDashed, Plus } from "lucide-react";
|
import { CircleDashed, Plus } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { TIssue, ISearchIssueResponse, TIssueGroupByOptions } from "@plane/types";
|
import { TIssue, ISearchIssueResponse, TIssueGroupByOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/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 { ExistingIssuesListModal, MultipleSelectGroupAction } from "@/components/core";
|
||||||
import { CreateUpdateIssueModal } from "@/components/issues";
|
import { CreateUpdateIssueModal } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
|
||||||
import { useEventTracker } from "@/hooks/store";
|
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
// plane-web
|
// plane-web
|
||||||
|
|
@ -59,8 +58,6 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
// hooks
|
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const storeType = useIssueStoreType();
|
const storeType = useIssueStoreType();
|
||||||
// derived values
|
// derived values
|
||||||
const isDraftIssue = pathname.includes("draft-issue");
|
const isDraftIssue = pathname.includes("draft-issue");
|
||||||
|
|
@ -134,7 +131,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("List layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -142,7 +139,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("List layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.add_existing });
|
||||||
setOpenExistingIssueListModal(true);
|
setOpenExistingIssueListModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -153,7 +150,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
<div
|
<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"
|
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={() => {
|
onClick={() => {
|
||||||
setTrackElement("List layout");
|
captureClick({ elementName: WORK_ITEM_TRACKER_EVENTS.create });
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useCallback, useMemo, SyntheticEvent } from "react";
|
import { useCallback, useMemo, SyntheticEvent } from "react";
|
||||||
import xor from "lodash/xor";
|
import xor from "lodash/xor";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// icons
|
// icons
|
||||||
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
|
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
|
|
@ -34,7 +34,8 @@ import {
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// 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 { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
@ -55,13 +56,12 @@ export interface IIssueProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
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
|
// i18n
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
const { labelMap } = useLabel();
|
const { labelMap } = useLabel();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const storeType = useIssueStoreType();
|
const storeType = useIssueStoreType();
|
||||||
const {
|
const {
|
||||||
issues: { changeModulesInIssue },
|
issues: { changeModulesInIssue },
|
||||||
|
|
@ -77,9 +77,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const currentLayout = `${activeLayout} layout`;
|
|
||||||
// derived values
|
// derived values
|
||||||
const stateDetails = getStateById(issue.state_id);
|
const stateDetails = getStateById(issue.state_id);
|
||||||
const subIssueCount = issue?.sub_issues_count ?? 0;
|
const subIssueCount = issue?.sub_issues_count ?? 0;
|
||||||
|
|
@ -109,14 +107,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
const handleState = (stateId: string) => {
|
const handleState = (stateId: string) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => {
|
updateIssue(issue.project_id, issue.id, { state_id: stateId }).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "state",
|
|
||||||
change_details: stateId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -124,14 +117,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
const handlePriority = (value: TIssuePriorities) => {
|
const handlePriority = (value: TIssuePriorities) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { priority: value }).then(() => {
|
updateIssue(issue.project_id, issue.id, { priority: value }).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "priority",
|
|
||||||
change_details: value,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -139,14 +127,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
const handleLabel = (ids: string[]) => {
|
const handleLabel = (ids: string[]) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => {
|
updateIssue(issue.project_id, issue.id, { label_ids: ids }).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "labels",
|
|
||||||
change_details: ids,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -154,14 +137,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
const handleAssignee = (ids: string[]) => {
|
const handleAssignee = (ids: string[]) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => {
|
updateIssue(issue.project_id, issue.id, { assignee_ids: ids }).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "assignees",
|
|
||||||
change_details: ids,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -179,14 +157,12 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
if (modulesToAdd.length > 0) issueOperations.addModulesToIssue(modulesToAdd);
|
if (modulesToAdd.length > 0) issueOperations.addModulesToIssue(modulesToAdd);
|
||||||
if (modulesToRemove.length > 0) issueOperations.removeModulesFromIssue(modulesToRemove);
|
if (modulesToRemove.length > 0) issueOperations.removeModulesFromIssue(modulesToRemove);
|
||||||
|
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: { changed_property: "module_ids", change_details: { module_ids: moduleIds } },
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[issueOperations, captureIssueEvent, currentLayout, pathname, issue]
|
[issueOperations, issue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCycle = useCallback(
|
const handleCycle = useCallback(
|
||||||
|
|
@ -195,28 +171,21 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
||||||
else issueOperations.removeIssueFromCycle?.();
|
else issueOperations.removeIssueFromCycle?.();
|
||||||
|
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: { changed_property: "cycle", change_details: { cycle_id: cycleId } },
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[issue, issueOperations, captureIssueEvent, currentLayout, pathname]
|
[issue, issueOperations]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStartDate = (date: Date | null) => {
|
const handleStartDate = (date: Date | null) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
updateIssue(issue.project_id, issue.id, { start_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
||||||
() => {
|
() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "start_date",
|
|
||||||
change_details: date ? renderFormattedPayloadDate(date) : null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -226,14 +195,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
updateIssue(issue.project_id, issue.id, { target_date: date ? renderFormattedPayloadDate(date) : null }).then(
|
||||||
() => {
|
() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "target_date",
|
|
||||||
change_details: date ? renderFormattedPayloadDate(date) : null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -242,14 +206,9 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||||
const handleEstimate = (value: string | undefined) => {
|
const handleEstimate = (value: string | undefined) => {
|
||||||
if (updateIssue)
|
if (updateIssue)
|
||||||
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
|
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { id: issue.id },
|
||||||
path: pathname,
|
|
||||||
updates: {
|
|
||||||
changed_property: "estimate_point",
|
|
||||||
change_details: value,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@ import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants";
|
import { ARCHIVABLE_STATE_GROUPS, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject, useProjectState } from "@/hooks/store";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useProject, useProjectState } from "@/hooks/store";
|
||||||
// plane-web components
|
// plane-web components
|
||||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
@ -39,8 +40,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
||||||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { getStateById } = useProjectState();
|
const { getStateById } = useProjectState();
|
||||||
const { getProjectIdentifierById } = useProject();
|
const { getProjectIdentifierById } = useProject();
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -70,7 +69,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
||||||
isArchivingAllowed,
|
isArchivingAllowed,
|
||||||
isDeletingAllowed: isEditingAllowed,
|
isDeletingAllowed: isEditingAllowed,
|
||||||
isInArchivableGroup,
|
isInArchivableGroup,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -84,6 +82,14 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
||||||
|
|
||||||
const MENU_ITEMS = useAllIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* 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
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
customButton={customActionButton}
|
customButton={customActionButton}
|
||||||
|
|
@ -171,6 +177,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.GLOBAL_VIEW });
|
||||||
nestedItem.action();
|
nestedItem.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -208,6 +215,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.GLOBAL_VIEW });
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@ import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// ui
|
// ui
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { EIssuesStoreType } from "@plane/types";
|
import { EIssuesStoreType } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { DeleteIssueModal } from "@/components/issues";
|
import { DeleteIssueModal } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useIssues, useUserPermissions } from "@/hooks/store";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
// helper
|
// helper
|
||||||
|
|
@ -36,7 +37,6 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
||||||
// store hooks
|
// store hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
|
const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
|
||||||
// derived values
|
// derived values
|
||||||
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
|
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
|
||||||
|
|
@ -54,7 +54,6 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
||||||
isEditingAllowed,
|
isEditingAllowed,
|
||||||
isDeletingAllowed: isEditingAllowed,
|
isDeletingAllowed: isEditingAllowed,
|
||||||
isRestoringAllowed: !!isRestoringAllowed,
|
isRestoringAllowed: !!isRestoringAllowed,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit: () => {},
|
setIssueToEdit: () => {},
|
||||||
setCreateUpdateIssueModal: () => {},
|
setCreateUpdateIssueModal: () => {},
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -64,6 +63,13 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
||||||
|
|
||||||
const MENU_ITEMS = useArchivedIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
|
|
@ -74,7 +80,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
||||||
onSubmit={handleDelete}
|
onSubmit={handleDelete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
customButton={customActionButton}
|
customButton={customActionButton}
|
||||||
|
|
@ -94,6 +100,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
item.action();
|
item.action();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.ARCHIVED });
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2",
|
"flex items-center gap-2",
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { 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 { EIssuesStoreType, TIssue } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
// hooks
|
// 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
|
// plane-web components
|
||||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||||
// types
|
// types
|
||||||
|
|
@ -41,8 +47,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, cycleId } = useParams();
|
const { workspaceSlug, cycleId } = useParams();
|
||||||
// store hooks
|
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { getStateById } = useProjectState();
|
const { getStateById } = useProjectState();
|
||||||
|
|
@ -78,7 +82,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
isArchivingAllowed,
|
isArchivingAllowed,
|
||||||
isDeletingAllowed,
|
isDeletingAllowed,
|
||||||
isInArchivableGroup,
|
isInArchivableGroup,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -94,6 +97,14 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
|
|
||||||
const MENU_ITEMS = useCycleIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* 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
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
placement={placements}
|
placement={placements}
|
||||||
|
|
@ -181,6 +192,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.CYCLE });
|
||||||
nestedItem.action();
|
nestedItem.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -218,6 +230,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.CYCLE });
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@ import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
// plane imports
|
// 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 { EIssuesStoreType, TIssue } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useUserPermissions } from "@/hooks/store";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useUserPermissions } from "@/hooks/store";
|
||||||
// local imports
|
// local imports
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
import { useDraftIssueMenuItems, MenuItemFactoryProps } from "./helper";
|
import { useDraftIssueMenuItems, MenuItemFactoryProps } from "./helper";
|
||||||
|
|
@ -37,7 +38,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
// derived values
|
// derived values
|
||||||
const activeLayout = "Draft Issues";
|
const activeLayout = "Draft Issues";
|
||||||
// auth
|
// auth
|
||||||
|
|
@ -70,7 +70,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
isEditingAllowed,
|
isEditingAllowed,
|
||||||
isDeletingAllowed,
|
isDeletingAllowed,
|
||||||
isDraftIssue,
|
isDraftIssue,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -81,6 +80,14 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
|
|
||||||
const MENU_ITEMS = useDraftIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
|
|
@ -104,7 +111,7 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
isDraft={isDraftIssue}
|
isDraft={isDraftIssue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
placement={placements}
|
placement={placements}
|
||||||
|
|
@ -122,6 +129,7 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.DRAFT });
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ export interface MenuItemFactoryProps {
|
||||||
issueTypeDetail?: { is_active?: boolean };
|
issueTypeDetail?: { is_active?: boolean };
|
||||||
isDraftIssue?: boolean;
|
isDraftIssue?: boolean;
|
||||||
// Action handlers
|
// Action handlers
|
||||||
setTrackElement: (element: string) => void;
|
|
||||||
setIssueToEdit: (issue: TIssue | undefined) => void;
|
setIssueToEdit: (issue: TIssue | undefined) => void;
|
||||||
setCreateUpdateIssueModal: (open: boolean) => void;
|
setCreateUpdateIssueModal: (open: boolean) => void;
|
||||||
setDeleteIssueModal: (open: boolean) => void;
|
setDeleteIssueModal: (open: boolean) => void;
|
||||||
|
|
@ -144,7 +143,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
||||||
isRestoringAllowed = false,
|
isRestoringAllowed = false,
|
||||||
isInArchivableGroup = false,
|
isInArchivableGroup = false,
|
||||||
issueTypeDetail,
|
issueTypeDetail,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -160,7 +158,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
||||||
action:
|
action:
|
||||||
customEditAction ||
|
customEditAction ||
|
||||||
(() => {
|
(() => {
|
||||||
setTrackElement(activeLayout);
|
|
||||||
setIssueToEdit(issue);
|
setIssueToEdit(issue);
|
||||||
setCreateUpdateIssueModal(true);
|
setCreateUpdateIssueModal(true);
|
||||||
}),
|
}),
|
||||||
|
|
@ -173,7 +170,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
||||||
title: t("common.actions.make_a_copy"),
|
title: t("common.actions.make_a_copy"),
|
||||||
icon: Copy,
|
icon: Copy,
|
||||||
action: () => {
|
action: () => {
|
||||||
setTrackElement(activeLayout);
|
|
||||||
setCreateUpdateIssueModal(true);
|
setCreateUpdateIssueModal(true);
|
||||||
},
|
},
|
||||||
shouldRender: isEditingAllowed && (issueTypeDetail?.is_active ?? true),
|
shouldRender: isEditingAllowed && (issueTypeDetail?.is_active ?? true),
|
||||||
|
|
@ -182,7 +178,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
||||||
return createCopyMenuWithDuplication({
|
return createCopyMenuWithDuplication({
|
||||||
baseItem,
|
baseItem,
|
||||||
activeLayout,
|
activeLayout,
|
||||||
setTrackElement,
|
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDuplicateWorkItemModal,
|
setDuplicateWorkItemModal,
|
||||||
});
|
});
|
||||||
|
|
@ -243,7 +238,6 @@ export const useMenuItemFactory = (props: MenuItemFactoryProps) => {
|
||||||
title: t("common.actions.delete"),
|
title: t("common.actions.delete"),
|
||||||
icon: Trash2,
|
icon: Trash2,
|
||||||
action: () => {
|
action: () => {
|
||||||
setTrackElement(activeLayout);
|
|
||||||
setDeleteIssueModal(true);
|
setDeleteIssueModal(true);
|
||||||
},
|
},
|
||||||
shouldRender: isDeletingAllowed,
|
shouldRender: isDeletingAllowed,
|
||||||
|
|
@ -304,7 +298,6 @@ export const useCycleIssueMenuItems = (props: MenuItemFactoryProps): TContextMen
|
||||||
...props.issue,
|
...props.issue,
|
||||||
cycle_id: props.cycleId ?? null,
|
cycle_id: props.cycleId ?? null,
|
||||||
});
|
});
|
||||||
props.setTrackElement(props.activeLayout || "");
|
|
||||||
props.setCreateUpdateIssueModal(true);
|
props.setCreateUpdateIssueModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -330,7 +323,6 @@ export const useModuleIssueMenuItems = (props: MenuItemFactoryProps): TContextMe
|
||||||
...props.issue,
|
...props.issue,
|
||||||
module_ids: props.moduleId ? [props.moduleId] : [],
|
module_ids: props.moduleId ? [props.moduleId] : [],
|
||||||
});
|
});
|
||||||
props.setTrackElement(props.activeLayout || "");
|
|
||||||
props.setCreateUpdateIssueModal(true);
|
props.setCreateUpdateIssueModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { 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 { EIssuesStoreType, TIssue } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
// hooks
|
// 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
|
// plane-web components
|
||||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
@ -41,7 +47,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, moduleId } = useParams();
|
const { workspaceSlug, moduleId } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { getStateById } = useProjectState();
|
const { getStateById } = useProjectState();
|
||||||
|
|
@ -77,7 +82,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
||||||
isArchivingAllowed,
|
isArchivingAllowed,
|
||||||
isDeletingAllowed,
|
isDeletingAllowed,
|
||||||
isInArchivableGroup,
|
isInArchivableGroup,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -93,6 +97,13 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
||||||
|
|
||||||
const MENU_ITEMS = useModuleIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* 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
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
placement={placements}
|
placement={placements}
|
||||||
|
|
@ -180,6 +191,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.MODULE });
|
||||||
nestedItem.action();
|
nestedItem.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -217,6 +229,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.MODULE });
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,20 @@ import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
// plane imports
|
// 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 { EIssuesStoreType, TIssue } from "@plane/types";
|
||||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
// hooks
|
// 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
|
// plane-web components
|
||||||
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
@ -42,7 +48,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||||
const { getStateById } = useProjectState();
|
const { getStateById } = useProjectState();
|
||||||
const { getProjectIdentifierById } = useProject();
|
const { getProjectIdentifierById } = useProject();
|
||||||
|
|
@ -85,7 +90,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
isDeletingAllowed,
|
isDeletingAllowed,
|
||||||
isInArchivableGroup,
|
isInArchivableGroup,
|
||||||
isDraftIssue,
|
isDraftIssue,
|
||||||
setTrackElement,
|
|
||||||
setIssueToEdit,
|
setIssueToEdit,
|
||||||
setCreateUpdateIssueModal,
|
setCreateUpdateIssueModal,
|
||||||
setDeleteIssueModal,
|
setDeleteIssueModal,
|
||||||
|
|
@ -99,6 +103,14 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
|
|
||||||
const MENU_ITEMS = useProjectIssueMenuItems(menuItemProps);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modals */}
|
{/* 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
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
placement={placements}
|
placement={placements}
|
||||||
|
|
@ -185,6 +197,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW });
|
||||||
nestedItem.action();
|
nestedItem.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -222,6 +235,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW });
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-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 { useForm, UseFormRegister } from "react-hook-form";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
|
|
@ -18,7 +18,7 @@ import { CreateIssueToastActionItems } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues";
|
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues";
|
||||||
|
|
||||||
|
|
@ -69,11 +69,8 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
// states
|
// states
|
||||||
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
|
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
|
||||||
// store hooks
|
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
|
|
@ -136,17 +133,16 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
|
|
||||||
await quickAddPromise
|
await quickAddPromise
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: { ...res, state: "SUCCESS", element: ` ${layout} quick add` },
|
payload: { id: res?.id },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: { ...payload, state: "FAILED", element: `${layout} quick ad` },
|
payload: { id: payload.id },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// types
|
// types
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { CycleDropdown } from "@/components/dropdowns";
|
import { CycleDropdown } from "@/components/dropdowns";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -19,9 +20,7 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
||||||
const { issue, disabled, onClose } = props;
|
const { issue, disabled, onClose } = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const {
|
const {
|
||||||
issues: { addCycleToIssue, removeCycleFromIssue },
|
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||||
} = useIssuesStore();
|
} = 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 (!workspaceSlug || !issue || !issue.project_id || issue.cycle_id === cycleId) return;
|
||||||
if (cycleId) await addCycleToIssue(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
if (cycleId) await addCycleToIssue(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||||
else await removeCycleFromIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
else await removeCycleFromIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Work item updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: {
|
payload: {
|
||||||
...issue,
|
id: issue.id,
|
||||||
cycle_id: cycleId,
|
|
||||||
element: "Spreadsheet layout",
|
|
||||||
},
|
},
|
||||||
updates: { changed_property: "cycle", change_details: { cycle_id: cycleId } },
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue, captureIssueEvent, pathname]
|
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import xor from "lodash/xor";
|
import xor from "lodash/xor";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// types
|
// types
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { ModuleDropdown } from "@/components/dropdowns";
|
import { ModuleDropdown } from "@/components/dropdowns";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -21,9 +22,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
||||||
const { issue, disabled, onClose } = props;
|
const { issue, disabled, onClose } = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
const pathname = usePathname();
|
|
||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const {
|
const {
|
||||||
issues: { changeModulesInIssue },
|
issues: { changeModulesInIssue },
|
||||||
} = useIssuesStore();
|
} = useIssuesStore();
|
||||||
|
|
@ -41,18 +40,14 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
||||||
}
|
}
|
||||||
changeModulesInIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd, modulesToRemove);
|
changeModulesInIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd, modulesToRemove);
|
||||||
|
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Work item updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: {
|
payload: {
|
||||||
...issue,
|
id: issue.id,
|
||||||
module_ids: moduleIds,
|
|
||||||
element: "Spreadsheet layout",
|
|
||||||
},
|
},
|
||||||
updates: { changed_property: "module_ids", change_details: { module_ids: moduleIds } },
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, issue, changeModulesInIssue, captureIssueEvent, pathname]
|
[workspaceSlug, issue, changeModulesInIssue]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// types
|
// types
|
||||||
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { IIssueDisplayProperties, TIssue } from "@plane/types";
|
import { IIssueDisplayProperties, TIssue } from "@plane/types";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// components
|
// components
|
||||||
import { SPREADSHEET_COLUMNS } from "@/plane-web/components/issues/issue-layouts/utils";
|
import { SPREADSHEET_COLUMNS } from "@/plane-web/components/issues/issue-layouts/utils";
|
||||||
import { shouldRenderColumn } from "@/plane-web/helpers/issue-filter.helper";
|
import { shouldRenderColumn } from "@/plane-web/helpers/issue-filter.helper";
|
||||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||||
// utils
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties;
|
||||||
|
|
@ -23,9 +22,7 @@ type Props = {
|
||||||
export const IssueColumn = observer((props: Props) => {
|
export const IssueColumn = observer((props: Props) => {
|
||||||
const { displayProperties, issueDetail, disableUserActions, property, updateIssue } = props;
|
const { displayProperties, issueDetail, disableUserActions, property, updateIssue } = props;
|
||||||
// router
|
// router
|
||||||
const pathname = usePathname();
|
|
||||||
const tableCellRef = useRef<HTMLTableCellElement | null>(null);
|
const tableCellRef = useRef<HTMLTableCellElement | null>(null);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
|
|
||||||
const shouldRenderProperty = shouldRenderColumn(property);
|
const shouldRenderProperty = shouldRenderColumn(property);
|
||||||
|
|
||||||
|
|
@ -46,18 +43,14 @@ export const IssueColumn = observer((props: Props) => {
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
issue={issueDetail}
|
issue={issueDetail}
|
||||||
onChange={(issue: TIssue, data: Partial<TIssue>, updates: any) =>
|
onChange={(issue: TIssue, data: Partial<TIssue>) =>
|
||||||
updateIssue &&
|
updateIssue &&
|
||||||
updateIssue(issue.project_id, issue.id, data).then(() => {
|
updateIssue(issue.project_id, issue.id, data).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Issue updated",
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: {
|
payload: {
|
||||||
...issue,
|
id: issue.id,
|
||||||
...data,
|
|
||||||
element: "Spreadsheet layout",
|
|
||||||
},
|
},
|
||||||
updates: updates,
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// Plane imports
|
|
||||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||||
|
// Plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EIssuesStoreType, TBaseIssue, TIssue } from "@plane/types";
|
import { EIssuesStoreType, TBaseIssue, TIssue } from "@plane/types";
|
||||||
import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issues";
|
import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||||
import { useCycle } from "@/hooks/store/use-cycle";
|
import { useCycle } from "@/hooks/store/use-cycle";
|
||||||
import { useEventTracker } from "@/hooks/store/use-event-tracker";
|
|
||||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||||
import { useIssues } from "@/hooks/store/use-issues";
|
import { useIssues } from "@/hooks/store/use-issues";
|
||||||
import { useModule } from "@/hooks/store/use-module";
|
import { useModule } from "@/hooks/store/use-module";
|
||||||
|
|
@ -61,7 +61,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
|
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId, workItem } = useParams();
|
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId, workItem } = useParams();
|
||||||
const { fetchCycleDetails } = useCycle();
|
const { fetchCycleDetails } = useCycle();
|
||||||
const { fetchModuleDetails } = useModule();
|
const { fetchModuleDetails } = useModule();
|
||||||
|
|
@ -71,8 +70,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
const { fetchIssue } = useIssueDetail();
|
const { fetchIssue } = useIssueDetail();
|
||||||
const { allowedProjectIds, handleCreateUpdatePropertyValues } = useIssueModal();
|
const { allowedProjectIds, handleCreateUpdatePropertyValues } = useIssueModal();
|
||||||
const { getProjectByIdentifier } = useProject();
|
const { getProjectByIdentifier } = useProject();
|
||||||
// pathname
|
|
||||||
const pathname = usePathname();
|
|
||||||
// current store details
|
// current store details
|
||||||
const { createIssue, updateIssue } = useIssuesActions(storeType);
|
const { createIssue, updateIssue } = useIssuesActions(storeType);
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -239,10 +236,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: { ...response, state: "SUCCESS" },
|
payload: { id: response.id },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
if (!createMore) handleClose();
|
if (!createMore) handleClose();
|
||||||
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
|
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
|
||||||
|
|
@ -255,10 +251,10 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
title: t("error"),
|
title: t("error"),
|
||||||
message: error?.error ?? t(is_draft_issue ? "draft_creation_failed" : "issue_creation_failed"),
|
message: error?.error ?? t(is_draft_issue ? "draft_creation_failed" : "issue_creation_failed"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { id: payload.id },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -301,10 +297,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
title: t("success"),
|
title: t("success"),
|
||||||
message: t("issue_updated_successfully"),
|
message: t("issue_updated_successfully"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...payload, issueId: data.id, state: "SUCCESS" },
|
payload: { id: data.id },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -314,10 +309,10 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
title: t("error"),
|
title: t("error"),
|
||||||
message: error?.error ?? t("issue_could_not_be_updated"),
|
message: error?.error ?? t("issue_could_not_be_updated"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { id: data.id },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import { observer } from "mobx-react";
|
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";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import type { TIssue } from "@plane/types";
|
import type { TIssue } from "@plane/types";
|
||||||
|
|
@ -14,8 +15,9 @@ import { isEmptyHtmlString } from "@plane/utils";
|
||||||
import { ConfirmIssueDiscard } from "@/components/issues";
|
import { ConfirmIssueDiscard } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||||
import { useEventTracker, useWorkspaceDraftIssues } from "@/hooks/store";
|
import { useWorkspaceDraftIssues } from "@/hooks/store";
|
||||||
// local components
|
// local components
|
||||||
import { IssueFormRoot, type IssueFormProps } from "./form";
|
import { IssueFormRoot, type IssueFormProps } from "./form";
|
||||||
|
|
||||||
|
|
@ -30,10 +32,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
||||||
// router params
|
// router params
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// pathname
|
|
||||||
const pathname = usePathname();
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { handleCreateUpdatePropertyValues } = useIssueModal();
|
const { handleCreateUpdatePropertyValues } = useIssueModal();
|
||||||
const { createIssue } = useWorkspaceDraftIssues();
|
const { createIssue } = useWorkspaceDraftIssues();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -92,26 +91,25 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
title: `${t("success")}!`,
|
title: `${t("success")}!`,
|
||||||
message: t("workspace_draft_issues.toasts.created.success"),
|
message: t("workspace_draft_issues.toasts.created.success"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: "Draft work item created",
|
eventName: WORK_ITEM_TRACKER_EVENTS.draft.create,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: { id: res?.id },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
onChange(null);
|
onChange(null);
|
||||||
setIssueDiscardModal(false);
|
setIssueDiscardModal(false);
|
||||||
onClose();
|
onClose();
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: `${t("error")}!`,
|
title: `${t("error")}!`,
|
||||||
message: t("workspace_draft_issues.toasts.created.error"),
|
message: t("workspace_draft_issues.toasts.created.error"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: "Draft work item created",
|
eventName: WORK_ITEM_TRACKER_EVENTS.draft.create,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { id: payload.id },
|
||||||
path: pathname,
|
error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { IssueView, TIssueOperations } from "@/components/issues";
|
import { IssueView, TIssueOperations } from "@/components/issues";
|
||||||
// hooks
|
// 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 { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||||
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
|
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
|
||||||
|
|
||||||
|
|
@ -40,7 +41,6 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
const issueStoreType = useIssueStoreType();
|
const issueStoreType = useIssueStoreType();
|
||||||
const storeType = issueStoreFromProps ?? issueStoreType;
|
const storeType = issueStoreFromProps ?? issueStoreType;
|
||||||
const { issues } = useIssues(storeType);
|
const { issues } = useIssues(storeType);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
|
|
||||||
useWorkItemProperties(
|
useWorkItemProperties(
|
||||||
peekIssue?.projectId,
|
peekIssue?.projectId,
|
||||||
|
|
@ -73,21 +73,16 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
.updateIssue(workspaceSlug, projectId, issueId, data)
|
.updateIssue(workspaceSlug, projectId, issueId, data)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
fetchActivities(workspaceSlug, projectId, issueId);
|
fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...data, issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: Object.keys(data).join(","),
|
|
||||||
change_details: Object.values(data).join(","),
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
|
|
@ -100,23 +95,22 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => {
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
removeRoutePeekId();
|
removeRoutePeekId();
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
eventName: WORK_ITEM_TRACKER_EVENTS.delete,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -124,16 +118,15 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
try {
|
try {
|
||||||
if (!issues?.archiveIssue) return;
|
if (!issues?.archiveIssue) return;
|
||||||
await issues.archiveIssue(workspaceSlug, projectId, issueId);
|
await issues.archiveIssue(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
eventName: WORK_ITEM_TRACKER_EVENTS.archive,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -145,21 +138,20 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
title: t("issue.restore.success.title"),
|
title: t("issue.restore.success.title"),
|
||||||
message: t("issue.restore.success.message"),
|
message: t("issue.restore.success.message"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
message: t("issue.restore.failed.message"),
|
message: t("issue.restore.failed.message"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
eventName: WORK_ITEM_TRACKER_EVENTS.restore,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
path: pathname,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -167,58 +159,40 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
try {
|
try {
|
||||||
await issues.addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
await issues.addCycleToIssue(workspaceSlug, projectId, cycleId, issueId);
|
||||||
fetchActivities(workspaceSlug, projectId, issueId);
|
fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
message: t("issue.add.cycle.failed"),
|
message: t("issue.add.cycle.failed"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
|
||||||
try {
|
try {
|
||||||
await issues.addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
await issues.addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { ...issueIds, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueIds },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
message: t("issue.add.cycle.failed"),
|
message: t("issue.add.cycle.failed"),
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueIds },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: cycleId,
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -238,24 +212,15 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
});
|
});
|
||||||
await removeFromCyclePromise;
|
await removeFromCyclePromise;
|
||||||
fetchActivities(workspaceSlug, projectId, issueId);
|
fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "cycle_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -274,14 +239,9 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
removeModuleIds
|
removeModuleIds
|
||||||
);
|
);
|
||||||
fetchActivities(workspaceSlug, projectId, issueId);
|
fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: { addModuleIds, removeModuleIds },
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
@ -301,30 +261,21 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
||||||
});
|
});
|
||||||
await removeFromModulePromise;
|
await removeFromModulePromise;
|
||||||
fetchActivities(workspaceSlug, projectId, issueId);
|
fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
captureIssueEvent({
|
captureSuccess({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId },
|
||||||
updates: {
|
error: error as Error,
|
||||||
changed_property: "module_id",
|
|
||||||
change_details: "",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ import { FC, Fragment } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EDraftIssuePaginationType } from "@plane/constants";
|
import { EDraftIssuePaginationType, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { EUserPermissionsLevel } from "@plane/constants/src/user";
|
import { EUserPermissionsLevel } from "@plane/constants/src/user";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EUserWorkspaceRoles } from "@plane/types";
|
import { EUserWorkspaceRoles } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -77,6 +78,7 @@ export const WorkspaceDraftIssuesRoot: FC<TWorkspaceDraftIssuesRoot> = observer(
|
||||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
|
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||||
}}
|
}}
|
||||||
disabled={!hasMemberLevelPermission}
|
disabled={!hasMemberLevelPermission}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
EEstimateSystem,
|
EEstimateSystem,
|
||||||
MODULE_TRACKER_EVENTS,
|
MODULE_TRACKER_EVENTS,
|
||||||
|
MODULE_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
// plane types
|
// plane types
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -22,15 +23,10 @@ import { Loader, LayersIcon, CustomSelect, ModuleStatusIcon, TOAST_TYPE, setToas
|
||||||
// helpers
|
// helpers
|
||||||
import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||||
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||||
import {
|
import { CreateUpdateModuleLinkModal, ModuleAnalyticsProgress, ModuleLinksList } from "@/components/modules";
|
||||||
ArchiveModuleModal,
|
import { captureElementAndEvent, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||||
DeleteModuleModal,
|
|
||||||
CreateUpdateModuleLinkModal,
|
|
||||||
ModuleAnalyticsProgress,
|
|
||||||
ModuleLinksList,
|
|
||||||
} from "@/components/modules";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useModule, useEventTracker, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
import { useModule, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
||||||
// plane web constants
|
// plane web constants
|
||||||
const defaultValues: Partial<IModule> = {
|
const defaultValues: Partial<IModule> = {
|
||||||
lead_id: "",
|
lead_id: "",
|
||||||
|
|
@ -50,8 +46,6 @@ type Props = {
|
||||||
export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
const { moduleId, handleClose, isArchived } = props;
|
const { moduleId, handleClose, isArchived } = props;
|
||||||
// states
|
// states
|
||||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
|
||||||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
|
||||||
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
||||||
// router
|
// router
|
||||||
|
|
@ -62,7 +56,6 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
||||||
const { captureModuleEvent, captureEvent } = useEventTracker();
|
|
||||||
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();
|
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -79,15 +72,22 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data)
|
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
captureModuleEvent({
|
captureElementAndEvent({
|
||||||
eventName: MODULE_TRACKER_EVENTS.update,
|
element: {
|
||||||
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
elementName: MODULE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.update,
|
||||||
|
payload: { id: res.id },
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
captureModuleEvent({
|
captureError({
|
||||||
eventName: MODULE_TRACKER_EVENTS.update,
|
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 };
|
const payload = { metadata: {}, ...formData };
|
||||||
|
|
||||||
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload).then(() =>
|
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload)
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.link.create, {
|
.then(() =>
|
||||||
module_id: moduleId,
|
captureSuccess({
|
||||||
state: "SUCCESS",
|
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) => {
|
const handleUpdateLink = async (formData: ModuleLink, linkId: string) => {
|
||||||
|
|
@ -110,13 +118,20 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
const payload = { metadata: {}, ...formData };
|
const payload = { metadata: {}, ...formData };
|
||||||
|
|
||||||
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload).then(
|
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload)
|
||||||
() =>
|
.then(() =>
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.link.update, {
|
captureSuccess({
|
||||||
module_id: moduleId,
|
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||||
state: "SUCCESS",
|
payload: { id: moduleId },
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
captureError({
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||||
|
payload: { id: moduleId },
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteLink = async (linkId: string) => {
|
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)
|
deleteModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.link.delete, {
|
captureSuccess({
|
||||||
module_id: moduleId,
|
eventName: MODULE_TRACKER_EVENTS.link.delete,
|
||||||
state: "SUCCESS",
|
payload: { id: moduleId },
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -140,6 +155,10 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Some error occurred",
|
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}
|
createLink={handleCreateLink}
|
||||||
updateLink={handleUpdateLink}
|
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
|
<div
|
||||||
className={`sticky z-10 top-0 flex items-center justify-between bg-custom-sidebar-background-100 pb-5 pt-5`}
|
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
|
// ui
|
||||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
|
// helpers
|
||||||
|
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useModule } from "@/hooks/store";
|
import { useModule } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -28,7 +30,6 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId, moduleId, peekModule } = useParams();
|
const { workspaceSlug, projectId, moduleId, peekModule } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureModuleEvent } = useEventTracker();
|
|
||||||
const { deleteModule } = useModule();
|
const { deleteModule } = useModule();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -51,9 +52,9 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Module deleted successfully.",
|
message: "Module deleted successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureSuccess({
|
||||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||||
payload: { ...data, state: "SUCCESS" },
|
payload: { id: data.id },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
|
|
@ -66,9 +67,10 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureError({
|
||||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||||
payload: { ...data, state: "FAILED" },
|
payload: { id: data.id },
|
||||||
|
error: errors,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => handleClose());
|
.finally(() => handleClose());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Copy, Pencil, Trash2 } from "lucide-react";
|
import { Copy, Pencil, Trash2 } from "lucide-react";
|
||||||
// plane types
|
// plane types
|
||||||
|
import { MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { ILinkDetails } from "@plane/types";
|
import { ILinkDetails } from "@plane/types";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||||
|
|
@ -58,6 +59,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||||
|
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -77,6 +79,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||||
|
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
||||||
// components
|
// components
|
||||||
import { ModuleForm } from "@/components/modules";
|
import { ModuleForm } from "@/components/modules";
|
||||||
// constants
|
// constants
|
||||||
|
// helpers
|
||||||
|
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useModule, useProject } from "@/hooks/store";
|
import { useModule, useProject } from "@/hooks/store";
|
||||||
import useKeypress from "@/hooks/use-keypress";
|
import useKeypress from "@/hooks/use-keypress";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
||||||
|
|
@ -37,7 +39,6 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
// states
|
// states
|
||||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureModuleEvent } = useEventTracker();
|
|
||||||
const { workspaceProjectIds } = useProject();
|
const { workspaceProjectIds } = useProject();
|
||||||
const { createModule, updateModuleDetails } = useModule();
|
const { createModule, updateModuleDetails } = useModule();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
@ -63,9 +64,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Module created successfully.",
|
message: "Module created successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureSuccess({
|
||||||
eventName: MODULE_TRACKER_EVENTS.create,
|
eventName: MODULE_TRACKER_EVENTS.create,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: { id: res.id },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -74,14 +75,15 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.detail ?? err?.error ?? "Module could not be created. Please try again.",
|
message: err?.detail ?? err?.error ?? "Module could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureError({
|
||||||
eventName: MODULE_TRACKER_EVENTS.create,
|
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;
|
if (!workspaceSlug || !projectId || !data) return;
|
||||||
|
|
||||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||||
|
|
@ -94,9 +96,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Module updated successfully.",
|
message: "Module updated successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureSuccess({
|
||||||
eventName: MODULE_TRACKER_EVENTS.update,
|
eventName: MODULE_TRACKER_EVENTS.update,
|
||||||
payload: { ...res, changed_properties: Object.keys(dirtyFields || {}), state: "SUCCESS" },
|
payload: { id: res.id },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -105,21 +107,22 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.detail ?? err?.error ?? "Module could not be updated. Please try again.",
|
message: err?.detail ?? err?.error ?? "Module could not be updated. Please try again.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureError({
|
||||||
eventName: MODULE_TRACKER_EVENTS.update,
|
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;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload: Partial<IModule> = {
|
const payload: Partial<IModule> = {
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
if (!data) await handleCreateModule(payload);
|
if (!data) await handleCreateModule(payload);
|
||||||
else await handleUpdateModule(payload, dirtyFields);
|
else await handleUpdateModule(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
IS_FAVORITE_MENU_OPEN,
|
IS_FAVORITE_MENU_OPEN,
|
||||||
MODULE_TRACKER_EVENTS,
|
MODULE_TRACKER_EVENTS,
|
||||||
|
MODULE_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { useLocalStorage } from "@plane/hooks";
|
import { useLocalStorage } from "@plane/hooks";
|
||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
|
|
@ -33,8 +34,9 @@ import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web constants
|
// plane web constants
|
||||||
|
|
@ -56,7 +58,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
const { setValue: toggleFavoriteMenu, storedValue } = useLocalStorage<boolean>(IS_FAVORITE_MENU_OPEN, false);
|
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(
|
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then(
|
||||||
() => {
|
() => {
|
||||||
if (!storedValue) toggleFavoriteMenu(true);
|
if (!storedValue) toggleFavoriteMenu(true);
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
captureElementAndEvent({
|
||||||
module_id: moduleId,
|
element: {
|
||||||
element: "Grid layout",
|
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||||
state: "SUCCESS",
|
},
|
||||||
|
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(),
|
projectId.toString(),
|
||||||
moduleId
|
moduleId
|
||||||
).then(() => {
|
).then(() => {
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
captureElementAndEvent({
|
||||||
module_id: moduleId,
|
element: {
|
||||||
element: "Grid layout",
|
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||||
state: "SUCCESS",
|
},
|
||||||
|
event: {
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||||
|
payload: { id: moduleId },
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
IS_FAVORITE_MENU_OPEN,
|
IS_FAVORITE_MENU_OPEN,
|
||||||
MODULE_TRACKER_EVENTS,
|
MODULE_TRACKER_EVENTS,
|
||||||
|
MODULE_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { useLocalStorage } from "@plane/hooks";
|
import { useLocalStorage } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -24,8 +25,10 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||||
// constants
|
// constants
|
||||||
|
// helpers
|
||||||
|
import { captureElementAndEvent, captureError } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||||
import { ButtonAvatars } from "../dropdowns/member/avatar";
|
import { ButtonAvatars } from "../dropdowns/member/avatar";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -42,7 +45,6 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
const { addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -64,17 +66,28 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
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
|
// open favorites menu if closed
|
||||||
if (!storedValue) toggleFavoriteMenu(true);
|
if (!storedValue) toggleFavoriteMenu(true);
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
captureElementAndEvent({
|
||||||
module_id: moduleId,
|
element: {
|
||||||
element: "Grid layout",
|
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||||
state: "SUCCESS",
|
},
|
||||||
|
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, {
|
setPromiseToast(addToFavoritePromise, {
|
||||||
loading: "Adding module to favorites...",
|
loading: "Adding module to favorites...",
|
||||||
|
|
@ -98,13 +111,26 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||||
workspaceSlug.toString(),
|
workspaceSlug.toString(),
|
||||||
projectId.toString(),
|
projectId.toString(),
|
||||||
moduleId
|
moduleId
|
||||||
).then(() => {
|
)
|
||||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
.then(() => {
|
||||||
module_id: moduleId,
|
captureElementAndEvent({
|
||||||
element: "Grid layout",
|
element: {
|
||||||
state: "SUCCESS",
|
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||||
|
payload: { id: moduleId },
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
captureError({
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||||
|
payload: { id: moduleId },
|
||||||
|
error,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPromiseToast(removeFromFavoritePromise, {
|
setPromiseToast(removeFromFavoritePromise, {
|
||||||
loading: "Removing module from favorites...",
|
loading: "Removing module from favorites...",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { observer } from "mobx-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EUserProjectRoles } from "@plane/types";
|
import { EUserProjectRoles } from "@plane/types";
|
||||||
import { ContentWrapper, Row, ERowVariant } from "@plane/ui";
|
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 { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "@/components/modules";
|
||||||
import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "@/components/ui";
|
import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "@/components/ui";
|
||||||
// hooks
|
// 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 { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
import AllFiltersImage from "@/public/empty-state/module/all-filters.svg";
|
import AllFiltersImage from "@/public/empty-state/module/all-filters.svg";
|
||||||
import NameFilterImage from "@/public/empty-state/module/name-filter.svg";
|
import NameFilterImage from "@/public/empty-state/module/name-filter.svg";
|
||||||
|
|
@ -25,7 +25,6 @@ export const ModulesListView: React.FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateModuleModal } = useCommandPalette();
|
const { toggleCreateModuleModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { getProjectModuleIds, getFilteredModuleIds, loader } = useModule();
|
const { getProjectModuleIds, getFilteredModuleIds, loader } = useModule();
|
||||||
const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter();
|
const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
@ -60,8 +59,8 @@ export const ModulesListView: React.FC = observer(() => {
|
||||||
label={t("project_module.empty_state.general.primary_button.text")}
|
label={t("project_module.empty_state.general.primary_button.text")}
|
||||||
title={t("project_module.empty_state.general.primary_button.comic.title")}
|
title={t("project_module.empty_state.general.primary_button.comic.title")}
|
||||||
description={t("project_module.empty_state.general.primary_button.comic.description")}
|
description={t("project_module.empty_state.general.primary_button.comic.description")}
|
||||||
|
data-ph-element={MODULE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Module empty state");
|
|
||||||
toggleCreateModuleModal(true);
|
toggleCreateModuleModal(true);
|
||||||
}}
|
}}
|
||||||
disabled={!canPerformEmptyStateActions}
|
disabled={!canPerformEmptyStateActions}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
||||||
// icons
|
// icons
|
||||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||||
// plane imports
|
// 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";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
|
@ -14,8 +19,9 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules";
|
import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { captureClick, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useModule, useEventTracker, useUserPermissions } from "@/hooks/store";
|
import { useModule, useUserPermissions } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -35,7 +41,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
const { getModuleById, restoreModule } = useModule();
|
const { getModuleById, restoreModule } = useModule();
|
||||||
|
|
@ -67,7 +72,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank");
|
const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank");
|
||||||
|
|
||||||
const handleEditModule = () => {
|
const handleEditModule = () => {
|
||||||
setTrackElement("Modules page list layout");
|
|
||||||
setEditModal(true);
|
setEditModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -81,18 +85,26 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
title: "Restore success",
|
title: "Restore success",
|
||||||
message: "Your module can be found in project modules.",
|
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`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/archives/modules`);
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch((error) => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Module could not be restored. Please try again.",
|
message: "Module could not be restored. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
captureError({
|
||||||
|
eventName: MODULE_TRACKER_EVENTS.restore,
|
||||||
|
payload: { id: moduleId },
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handleDeleteModule = () => {
|
const handleDeleteModule = () => {
|
||||||
setTrackElement("Modules page list layout");
|
|
||||||
setDeleteModal(true);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{moduleDetails && (
|
{moduleDetails && (
|
||||||
|
|
@ -166,7 +188,7 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
||||||
{MENU_ITEMS.map((item) => {
|
{MENU_ITEMS.map((item) => {
|
||||||
if (item.shouldRender === false) return null;
|
if (item.shouldRender === false) return null;
|
||||||
|
|
@ -176,6 +198,9 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({
|
||||||
|
elementName: MODULE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||||
|
});
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import { observer } from "mobx-react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// constants
|
// constants
|
||||||
import {
|
import {
|
||||||
ONBOARDING_TRACKER_EVENTS,
|
|
||||||
ORGANIZATION_SIZE,
|
ORGANIZATION_SIZE,
|
||||||
RESTRICTED_URLS,
|
RESTRICTED_URLS,
|
||||||
WORKSPACE_TRACKER_EVENTS,
|
WORKSPACE_TRACKER_EVENTS,
|
||||||
|
WORKSPACE_TRACKER_ELEMENTS,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
// types
|
// types
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -16,7 +16,8 @@ import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// hooks
|
// 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
|
// services
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
|
|
||||||
|
|
@ -41,7 +42,6 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||||
const { updateUserProfile } = useUserProfile();
|
const { updateUserProfile } = useUserProfile();
|
||||||
const { fetchCurrentUserSettings } = useUserSettings();
|
const { fetchCurrentUserSettings } = useUserSettings();
|
||||||
const { createWorkspace, fetchWorkspaces } = useWorkspace();
|
const { createWorkspace, fetchWorkspaces } = useWorkspace();
|
||||||
const { captureWorkspaceEvent } = useEventTracker();
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|
@ -73,26 +73,18 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||||
title: t("workspace_creation.toast.success.title"),
|
title: t("workspace_creation.toast.success.title"),
|
||||||
message: t("workspace_creation.toast.success.message"),
|
message: t("workspace_creation.toast.success.message"),
|
||||||
});
|
});
|
||||||
captureWorkspaceEvent({
|
captureSuccess({
|
||||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||||
payload: {
|
payload: { slug: formData.slug },
|
||||||
...workspaceResponse,
|
|
||||||
state: "SUCCESS",
|
|
||||||
first_time: true,
|
|
||||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await fetchWorkspaces();
|
await fetchWorkspaces();
|
||||||
await completeStep(workspaceResponse.id);
|
await completeStep(workspaceResponse.id);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
captureWorkspaceEvent({
|
captureError({
|
||||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||||
payload: {
|
payload: { slug: formData.slug },
|
||||||
state: "FAILED",
|
error: new Error("Error creating workspace"),
|
||||||
first_time: true,
|
|
||||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -290,7 +282,14 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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")}
|
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("workspace_creation.button.default")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { ROLE, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceMemberInvitation } from "@plane/types";
|
import { IWorkspaceMemberInvitation } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Checkbox, Spinner } from "@plane/ui";
|
import { Button, Checkbox, Spinner } from "@plane/ui";
|
||||||
import { truncateText, getUserRole } from "@plane/utils";
|
import { truncateText } from "@plane/utils";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useUserSettings, useWorkspace } from "@/hooks/store";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
|
|
||||||
|
|
@ -29,7 +30,6 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { fetchWorkspaces } = useWorkspace();
|
const { fetchWorkspaces } = useWorkspace();
|
||||||
const { fetchCurrentUserSettings } = useUserSettings();
|
const { fetchCurrentUserSettings } = useUserSettings();
|
||||||
|
|
||||||
|
|
@ -50,26 +50,23 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await workspaceService.joinWorkspaces({ invitations: invitationsRespond });
|
await workspaceService.joinWorkspaces({ invitations: invitationsRespond });
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
captureSuccess({
|
||||||
member_id: invitation?.id,
|
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||||
role: getUserRole(invitation?.role as any),
|
payload: {
|
||||||
project_id: undefined,
|
member_id: invitation?.id,
|
||||||
accepted_from: "App",
|
},
|
||||||
state: "SUCCESS",
|
|
||||||
element: "Workspace invitations page",
|
|
||||||
});
|
});
|
||||||
await fetchWorkspaces();
|
await fetchWorkspaces();
|
||||||
await fetchCurrentUserSettings();
|
await fetchCurrentUserSettings();
|
||||||
await handleNextStep();
|
await handleNextStep();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
captureError({
|
||||||
member_id: invitation?.id,
|
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||||
role: getUserRole(invitation?.role as any),
|
payload: {
|
||||||
project_id: undefined,
|
member_id: invitation?.id,
|
||||||
accepted_from: "App",
|
},
|
||||||
state: "FAILED",
|
error: error,
|
||||||
element: "Workspace invitations page",
|
|
||||||
});
|
});
|
||||||
setIsJoiningWorkspaces(false);
|
setIsJoiningWorkspaces(false);
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +114,7 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={submitInvitations}
|
onClick={submitInvitations}
|
||||||
disabled={isJoiningWorkspaces || !invitationsRespond.length}
|
disabled={isJoiningWorkspaces || !invitationsRespond.length}
|
||||||
|
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_JOIN_WORKSPACE}
|
||||||
>
|
>
|
||||||
{isJoiningWorkspaces ? <Spinner height="20px" width="20px" /> : "Continue to workspace"}
|
{isJoiningWorkspaces ? <Spinner height="20px" width="20px" /> : "Continue to workspace"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { usePopper } from "react-popper";
|
||||||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||||
import { Listbox } from "@headlessui/react";
|
import { Listbox } from "@headlessui/react";
|
||||||
// plane imports
|
// 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";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IUser, IWorkspace } from "@plane/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";
|
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
import { getUserRole } from "@plane/utils";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
// assets
|
// assets
|
||||||
|
|
@ -276,8 +275,6 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||||
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
|
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
|
||||||
|
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
// store hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -311,16 +308,11 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
captureSuccess({
|
||||||
emails: [
|
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||||
...payload.emails.map((email) => ({
|
payload: {
|
||||||
email: email.email,
|
workspace: workspace.slug,
|
||||||
role: getUserRole(email.role),
|
},
|
||||||
})),
|
|
||||||
],
|
|
||||||
project_id: undefined,
|
|
||||||
state: "SUCCESS",
|
|
||||||
element: "Onboarding",
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -331,10 +323,12 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||||
await nextStep();
|
await nextStep();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
captureError({
|
||||||
project_id: undefined,
|
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||||
state: "FAILED",
|
payload: {
|
||||||
element: "Onboarding",
|
workspace: workspace.slug,
|
||||||
|
},
|
||||||
|
error: err,
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -426,6 +420,7 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
disabled={isInvitationDisabled || !isValid || isSubmitting}
|
disabled={isInvitationDisabled || !isValid || isSubmitting}
|
||||||
|
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_INVITE_MEMBER}
|
||||||
>
|
>
|
||||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
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
|
// types
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IUser, TUserProfile, TOnboardingSteps } from "@plane/types";
|
import { IUser, TUserProfile, TOnboardingSteps } from "@plane/types";
|
||||||
|
|
@ -20,7 +20,8 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useUser, useUserProfile } from "@/hooks/store";
|
import { captureError, captureSuccess, captureView } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useUser, useUserProfile } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp";
|
import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp";
|
||||||
import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp";
|
import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp";
|
||||||
|
|
@ -98,7 +99,6 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateCurrentUser } = useUser();
|
const { updateCurrentUser } = useUser();
|
||||||
const { updateUserProfile } = useUserProfile();
|
const { updateUserProfile } = useUserProfile();
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
getValues,
|
getValues,
|
||||||
|
|
@ -143,11 +143,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
updateUserProfile(profileUpdatePayload),
|
updateUserProfile(profileUpdatePayload),
|
||||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||||
]);
|
]);
|
||||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
captureSuccess({
|
||||||
use_case: formData.use_case,
|
eventName: USER_TRACKER_EVENTS.add_details,
|
||||||
role: formData.role,
|
payload: {
|
||||||
state: "SUCCESS",
|
use_case: formData.use_case,
|
||||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
role: formData.role,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -159,9 +160,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
finishOnboarding();
|
finishOnboarding();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
captureError({
|
||||||
state: "FAILED",
|
eventName: USER_TRACKER_EVENTS.add_details,
|
||||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -183,9 +183,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
formData.password && handleSetPassword(formData.password),
|
formData.password && handleSetPassword(formData.password),
|
||||||
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
|
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
|
||||||
} catch {
|
} catch {
|
||||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
captureError({
|
||||||
state: "FAILED",
|
eventName: USER_TRACKER_EVENTS.add_details,
|
||||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -205,11 +204,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
updateUserProfile(profileUpdatePayload),
|
updateUserProfile(profileUpdatePayload),
|
||||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||||
]);
|
]);
|
||||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
captureSuccess({
|
||||||
use_case: formData.use_case,
|
eventName: USER_TRACKER_EVENTS.add_details,
|
||||||
role: formData.role,
|
payload: {
|
||||||
state: "SUCCESS",
|
use_case: formData.use_case,
|
||||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
role: formData.role,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
|
@ -221,9 +221,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
finishOnboarding();
|
finishOnboarding();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
captureError({
|
||||||
state: "FAILED",
|
eventName: USER_TRACKER_EVENTS.add_details,
|
||||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -235,6 +234,9 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
const onSubmit = async (formData: TProfileSetupFormValues) => {
|
const onSubmit = async (formData: TProfileSetupFormValues) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
captureView({
|
||||||
|
elementName: ONBOARDING_TRACKER_ELEMENTS.PROFILE_SETUP_FORM,
|
||||||
|
});
|
||||||
if (profileSetupStep === EProfileSetupSteps.ALL) await handleSubmitProfileSetup(formData);
|
if (profileSetupStep === EProfileSetupSteps.ALL) await handleSubmitProfileSetup(formData);
|
||||||
if (profileSetupStep === EProfileSetupSteps.USER_DETAILS) await handleSubmitUserDetail(formData);
|
if (profileSetupStep === EProfileSetupSteps.USER_DETAILS) await handleSubmitUserDetail(formData);
|
||||||
if (profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION) await handleSubmitUserPersonalization(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 Image, { StaticImageData } from "next/image";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
import { PRODUCT_TOUR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { TourSidebar } from "@/components/onboarding";
|
import { TourSidebar } from "@/components/onboarding";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useUser } from "@/hooks/store";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useCommandPalette, useUser } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import CyclesTour from "@/public/onboarding/cycles.webp";
|
import CyclesTour from "@/public/onboarding/cycles.webp";
|
||||||
import IssuesTour from "@/public/onboarding/issues.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");
|
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement, captureEvent } = useEventTracker();
|
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
|
|
||||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||||
|
|
@ -112,7 +112,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.start);
|
captureClick({
|
||||||
|
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.START_BUTTON,
|
||||||
|
});
|
||||||
setStep("work-items");
|
setStep("work-items");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -122,7 +124,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.skip);
|
captureClick({
|
||||||
|
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.SKIP_BUTTON,
|
||||||
|
});
|
||||||
onComplete();
|
onComplete();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -171,7 +175,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Product tour");
|
captureClick({
|
||||||
|
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.CREATE_PROJECT_BUTTON,
|
||||||
|
});
|
||||||
onComplete();
|
onComplete();
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
// constants
|
// constants
|
||||||
import { EPageAccess } from "@plane/constants";
|
import { EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
// plane editor
|
// plane editor
|
||||||
import { EditorRefApi } from "@plane/editor";
|
import { EditorRefApi } from "@plane/editor";
|
||||||
// plane ui
|
// plane ui
|
||||||
|
|
@ -26,6 +26,7 @@ import { cn } from "@plane/utils";
|
||||||
import { DeletePageModal } from "@/components/pages";
|
import { DeletePageModal } from "@/components/pages";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { MovePageModal } from "@/plane-web/components/pages";
|
import { MovePageModal } from "@/plane-web/components/pages";
|
||||||
|
|
@ -92,14 +93,24 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
||||||
const menuItems: (TContextMenuItem & { key: TPageActions })[] = [
|
const menuItems: (TContextMenuItem & { key: TPageActions })[] = [
|
||||||
{
|
{
|
||||||
key: "toggle-lock",
|
key: "toggle-lock",
|
||||||
action: pageOperations.toggleLock,
|
action: () => {
|
||||||
|
captureClick({
|
||||||
|
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON,
|
||||||
|
});
|
||||||
|
pageOperations.toggleLock();
|
||||||
|
},
|
||||||
title: is_locked ? "Unlock" : "Lock",
|
title: is_locked ? "Unlock" : "Lock",
|
||||||
icon: is_locked ? LockKeyholeOpen : LockKeyhole,
|
icon: is_locked ? LockKeyholeOpen : LockKeyhole,
|
||||||
shouldRender: canCurrentUserLockPage,
|
shouldRender: canCurrentUserLockPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "toggle-access",
|
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",
|
title: access === EPageAccess.PUBLIC ? "Make private" : "Make public",
|
||||||
icon: access === EPageAccess.PUBLIC ? Lock : Globe2,
|
icon: access === EPageAccess.PUBLIC ? Lock : Globe2,
|
||||||
shouldRender: canCurrentUserChangeAccess && !archived_at,
|
shouldRender: canCurrentUserChangeAccess && !archived_at,
|
||||||
|
|
@ -120,21 +131,36 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "make-a-copy",
|
key: "make-a-copy",
|
||||||
action: pageOperations.duplicate,
|
action: () => {
|
||||||
|
captureClick({
|
||||||
|
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.DUPLICATE_BUTTON,
|
||||||
|
});
|
||||||
|
pageOperations.duplicate();
|
||||||
|
},
|
||||||
title: "Make a copy",
|
title: "Make a copy",
|
||||||
icon: Copy,
|
icon: Copy,
|
||||||
shouldRender: canCurrentUserDuplicatePage,
|
shouldRender: canCurrentUserDuplicatePage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "archive-restore",
|
key: "archive-restore",
|
||||||
action: pageOperations.toggleArchive,
|
action: () => {
|
||||||
|
captureClick({
|
||||||
|
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.ARCHIVE_BUTTON,
|
||||||
|
});
|
||||||
|
pageOperations.toggleArchive();
|
||||||
|
},
|
||||||
title: archived_at ? "Restore" : "Archive",
|
title: archived_at ? "Restore" : "Archive",
|
||||||
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
||||||
shouldRender: canCurrentUserArchivePage,
|
shouldRender: canCurrentUserArchivePage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "delete",
|
key: "delete",
|
||||||
action: () => setDeletePageModal(true),
|
action: () => {
|
||||||
|
captureClick({
|
||||||
|
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||||
|
});
|
||||||
|
setDeletePageModal(true);
|
||||||
|
},
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
icon: Trash2,
|
icon: Trash2,
|
||||||
shouldRender: canCurrentUserDeletePage && !!archived_at,
|
shouldRender: canCurrentUserDeletePage && !!archived_at,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// plane imports
|
// constants
|
||||||
|
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
|
// ui
|
||||||
import { FavoriteStar } from "@plane/ui";
|
import { FavoriteStar } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||||
// store
|
// store
|
||||||
|
|
@ -23,7 +27,12 @@ export const PageFavoriteControl = observer(({ page }: Props) => {
|
||||||
return (
|
return (
|
||||||
<FavoriteStar
|
<FavoriteStar
|
||||||
selected={is_favorite}
|
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"
|
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"
|
iconClassName="size-3.5 text-custom-text-200 group-hover:text-custom-text-10"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Earth, Info, Lock, Minus } from "lucide-react";
|
import { Earth, Info, Lock, Minus } from "lucide-react";
|
||||||
|
// constants
|
||||||
|
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, FavoriteStar, Tooltip } from "@plane/ui";
|
import { Avatar, FavoriteStar, Tooltip } from "@plane/ui";
|
||||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { PageActions } from "@/components/pages";
|
import { PageActions } from "@/components/pages";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember } from "@/hooks/store";
|
import { useMember } from "@/hooks/store";
|
||||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||||
|
|
@ -64,6 +67,9 @@ export const BlockItemAction: FC<Props> = observer((props) => {
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
captureClick({
|
||||||
|
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
|
||||||
|
});
|
||||||
pageOperations.toggleFavorite();
|
pageOperations.toggleFavorite();
|
||||||
}}
|
}}
|
||||||
selected={is_favorite}
|
selected={is_favorite}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageForm } from "@/components/pages";
|
import { PageForm } from "@/components/pages";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
// plane web hooks
|
// plane web hooks
|
||||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||||
|
|
@ -42,7 +42,6 @@ export const CreatePageModal: FC<Props> = (props) => {
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { createPage } = usePageStore(storeType);
|
const { createPage } = usePageStore(storeType);
|
||||||
const { capturePageEvent } = useEventTracker();
|
|
||||||
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
||||||
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
||||||
|
|
||||||
|
|
@ -62,22 +61,19 @@ export const CreatePageModal: FC<Props> = (props) => {
|
||||||
try {
|
try {
|
||||||
const pageData = await createPage(pageFormData);
|
const pageData = await createPage(pageFormData);
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
capturePageEvent({
|
captureSuccess({
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||||
payload: {
|
payload: {
|
||||||
...pageData,
|
id: pageData.id,
|
||||||
state: "SUCCESS",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
handleStateClear();
|
handleStateClear();
|
||||||
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error: any) {
|
||||||
capturePageEvent({
|
captureError({
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||||
payload: {
|
error,
|
||||||
state: "FAILED",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { PROJECT_PAGE_TRACKER_EVENTS } from "@plane/constants";
|
||||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
// plane web hooks
|
// plane web hooks
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
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);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { removePage } = usePageStore(storeType);
|
const { removePage } = usePageStore(storeType);
|
||||||
const { capturePageEvent } = useEventTracker();
|
|
||||||
if (!page || !page.id) return null;
|
if (!page || !page.id) return null;
|
||||||
// derived values
|
// derived values
|
||||||
const { id: pageId, name } = page;
|
const { id: pageId, name } = page;
|
||||||
|
|
@ -45,11 +44,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
await removePage(pageId)
|
await removePage(pageId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
capturePageEvent({
|
captureSuccess({
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||||
payload: {
|
payload: {
|
||||||
...page,
|
id: pageId,
|
||||||
state: "SUCCESS",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
@ -64,11 +62,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
capturePageEvent({
|
captureError({
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||||
payload: {
|
payload: {
|
||||||
...page,
|
id: pageId,
|
||||||
state: "FAILED",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EUserPermissionsLevel, EPageAccess } from "@plane/constants";
|
import { EUserPermissionsLevel, EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EUserProjectRoles, TPageNavigationTabs } from "@plane/types";
|
import { EUserProjectRoles, TPageNavigationTabs } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { DetailedEmptyState } from "@/components/empty-state";
|
import { DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { PageLoader } from "@/components/pages";
|
import { PageLoader } from "@/components/pages";
|
||||||
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
// plane web hooks
|
// 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"),
|
text: t("project_page.empty_state.general.primary_button.text"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
toggleCreatePageModal({ isOpen: true });
|
toggleCreatePageModal({ isOpen: true });
|
||||||
|
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||||
},
|
},
|
||||||
disabled: !canPerformEmptyStateActions,
|
disabled: !canPerformEmptyStateActions,
|
||||||
}}
|
}}
|
||||||
|
|
@ -79,6 +81,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||||
text: t("project_page.empty_state.public.primary_button.text"),
|
text: t("project_page.empty_state.public.primary_button.text"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
||||||
|
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||||
},
|
},
|
||||||
disabled: !canPerformEmptyStateActions,
|
disabled: !canPerformEmptyStateActions,
|
||||||
}}
|
}}
|
||||||
|
|
@ -94,6 +97,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||||
text: t("project_page.empty_state.private.primary_button.text"),
|
text: t("project_page.empty_state.private.primary_button.text"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
||||||
|
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||||
},
|
},
|
||||||
disabled: !canPerformEmptyStateActions,
|
disabled: !canPerformEmptyStateActions,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-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 { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { StateForm } from "@/components/project-states";
|
import { StateForm } from "@/components/project-states";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
|
||||||
type TStateCreate = {
|
type TStateCreate = {
|
||||||
groupKey: TStateGroups;
|
groupKey: TStateGroups;
|
||||||
|
|
@ -19,17 +19,10 @@ type TStateCreate = {
|
||||||
|
|
||||||
export const StateCreate: FC<TStateCreate> = observer((props) => {
|
export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||||
const { groupKey, shouldTrackEvents, createStateCallback, handleClose } = props;
|
const { groupKey, shouldTrackEvents, createStateCallback, handleClose } = props;
|
||||||
// hooks
|
|
||||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
|
||||||
// states
|
// states
|
||||||
const [loader, setLoader] = useState(false);
|
const [loader, setLoader] = useState(false);
|
||||||
|
|
||||||
const captureEventIfEnabled = (props: EventProps) => {
|
|
||||||
if (shouldTrackEvents) {
|
|
||||||
captureProjectStateEvent(props);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
setLoader(false);
|
setLoader(false);
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
@ -38,19 +31,16 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||||
const onSubmit = async (formData: Partial<IState>) => {
|
const onSubmit = async (formData: Partial<IState>) => {
|
||||||
if (!groupKey) return { status: "error" };
|
if (!groupKey) return { status: "error" };
|
||||||
|
|
||||||
if (shouldTrackEvents) {
|
|
||||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const stateResponse = await createStateCallback({ ...formData, group: groupKey });
|
const response = await createStateCallback({ ...formData, group: groupKey });
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents)
|
||||||
eventName: STATE_TRACKER_EVENTS.create,
|
captureSuccess({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.create,
|
||||||
...stateResponse,
|
payload: {
|
||||||
state: "SUCCESS",
|
state_group: groupKey,
|
||||||
element: "Project settings states page",
|
id: response.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
|
|
@ -60,14 +50,13 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||||
return { status: "success" };
|
return { status: "success" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents)
|
||||||
eventName: STATE_TRACKER_EVENTS.create,
|
captureError({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.create,
|
||||||
...formData,
|
payload: {
|
||||||
state: "FAILED",
|
state_group: groupKey,
|
||||||
element: "Project settings states page",
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
if (errorStatus?.status === 400) {
|
if (errorStatus?.status === 400) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-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 { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { StateForm } from "@/components/project-states";
|
import { StateForm } from "@/components/project-states";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
|
||||||
type TStateUpdate = {
|
type TStateUpdate = {
|
||||||
state: IState;
|
state: IState;
|
||||||
|
|
@ -19,8 +19,6 @@ type TStateUpdate = {
|
||||||
|
|
||||||
export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||||
const { state, updateStateCallback, shouldTrackEvents, handleClose } = props;
|
const { state, updateStateCallback, shouldTrackEvents, handleClose } = props;
|
||||||
// hooks
|
|
||||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
|
||||||
// states
|
// states
|
||||||
const [loader, setLoader] = useState(false);
|
const [loader, setLoader] = useState(false);
|
||||||
|
|
||||||
|
|
@ -29,28 +27,20 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const captureEventIfEnabled = (props: EventProps) => {
|
|
||||||
if (shouldTrackEvents) {
|
|
||||||
captureProjectStateEvent(props);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (formData: Partial<IState>) => {
|
const onSubmit = async (formData: Partial<IState>) => {
|
||||||
if (!state.id) return { status: "error" };
|
if (!state.id) return { status: "error" };
|
||||||
|
|
||||||
if (shouldTrackEvents) {
|
|
||||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const stateResponse = await updateStateCallback(state.id, formData);
|
await updateStateCallback(state.id, formData);
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents) {
|
||||||
eventName: STATE_TRACKER_EVENTS.update,
|
captureSuccess({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.update,
|
||||||
...stateResponse,
|
payload: {
|
||||||
state: "SUCCESS",
|
state_group: state.group,
|
||||||
element: "Project settings states page",
|
id: state.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
|
|
@ -73,14 +63,15 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "State could not be updated. Please try again.",
|
message: "State could not be updated. Please try again.",
|
||||||
});
|
});
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents) {
|
||||||
eventName: STATE_TRACKER_EVENTS.update,
|
captureError({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.update,
|
||||||
...formData,
|
payload: {
|
||||||
state: "FAILED",
|
state_group: state.group,
|
||||||
element: "Project settings states page",
|
id: state.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return { status: "error" };
|
return { status: "error" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { FC, useState, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ChevronDown, Plus } from "lucide-react";
|
import { ChevronDown, Plus } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EIconSize } from "@plane/constants";
|
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
|
|
@ -81,6 +81,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_GROUP_ADD_BUTTON}
|
||||||
className={cn(
|
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",
|
"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"
|
(!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 { observer } from "mobx-react";
|
||||||
import { Loader, X } from "lucide-react";
|
import { Loader, X } from "lucide-react";
|
||||||
// plane imports
|
// 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 { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||||
import { AlertModalCore, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
import { AlertModalCore, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
||||||
type TStateDelete = {
|
type TStateDelete = {
|
||||||
|
|
@ -23,45 +23,39 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
||||||
const { totalStates, state, deleteStateCallback, shouldTrackEvents } = props;
|
const { totalStates, state, deleteStateCallback, shouldTrackEvents } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
|
||||||
// states
|
// states
|
||||||
const [isDeleteModal, setIsDeleteModal] = useState(false);
|
const [isDeleteModal, setIsDeleteModal] = useState(false);
|
||||||
const [isDelete, setIsDelete] = useState(false);
|
const [isDelete, setIsDelete] = useState(false);
|
||||||
// derived values
|
// derived values
|
||||||
const isDeleteDisabled = state.default ? true : totalStates === 1 ? true : false;
|
const isDeleteDisabled = state.default ? true : totalStates === 1 ? true : false;
|
||||||
|
|
||||||
const captureEventIfEnabled = (props: EventProps) => {
|
|
||||||
if (shouldTrackEvents) {
|
|
||||||
captureProjectStateEvent(props);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteState = async () => {
|
const handleDeleteState = async () => {
|
||||||
if (isDeleteDisabled) return;
|
if (isDeleteDisabled) return;
|
||||||
if (shouldTrackEvents) {
|
|
||||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
|
||||||
}
|
|
||||||
setIsDelete(true);
|
setIsDelete(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteStateCallback(state.id);
|
await deleteStateCallback(state.id);
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents) {
|
||||||
eventName: STATE_TRACKER_EVENTS.delete,
|
captureSuccess({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.delete,
|
||||||
...state,
|
payload: {
|
||||||
state: "SUCCESS",
|
id: state.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsDelete(false);
|
setIsDelete(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||||
captureEventIfEnabled({
|
if (shouldTrackEvents) {
|
||||||
eventName: STATE_TRACKER_EVENTS.delete,
|
captureError({
|
||||||
payload: {
|
eventName: STATE_TRACKER_EVENTS.delete,
|
||||||
...state,
|
payload: {
|
||||||
state: "FAILED",
|
id: state.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (errorStatus.status === 400) {
|
if (errorStatus.status === 400) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
|
@ -107,6 +101,7 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
disabled={isDeleteDisabled}
|
disabled={isDeleteDisabled}
|
||||||
onClick={() => setIsDeleteModal(true)}
|
onClick={() => setIsDeleteModal(true)}
|
||||||
|
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_DELETE_BUTTON}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent={
|
tooltipContent={
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import type { IState } from "@plane/types";
|
||||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProjectState } from "@/hooks/store";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
|
import { useProjectState } from "@/hooks/store";
|
||||||
|
|
||||||
type TStateDeleteModal = {
|
type TStateDeleteModal = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -24,8 +25,6 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
|
||||||
const { captureProjectStateEvent } = useEventTracker();
|
|
||||||
const { deleteState } = useProjectState();
|
const { deleteState } = useProjectState();
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
|
@ -40,11 +39,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||||
|
|
||||||
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureProjectStateEvent({
|
captureSuccess({
|
||||||
eventName: STATE_TRACKER_EVENTS.delete,
|
eventName: STATE_TRACKER_EVENTS.delete,
|
||||||
payload: {
|
payload: {
|
||||||
...data,
|
id: data.id,
|
||||||
state: "SUCCESS",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
@ -63,11 +61,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "State could not be deleted. Please try again.",
|
message: "State could not be deleted. Please try again.",
|
||||||
});
|
});
|
||||||
captureProjectStateEvent({
|
captureError({
|
||||||
eventName: STATE_TRACKER_EVENTS.delete,
|
eventName: STATE_TRACKER_EVENTS.delete,
|
||||||
payload: {
|
payload: {
|
||||||
...data,
|
id: data.id,
|
||||||
state: "FAILED",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { SetStateAction } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { GripVertical, Pencil } from "lucide-react";
|
import { GripVertical, Pencil } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EIconSize } from "@plane/constants";
|
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// local imports
|
// local imports
|
||||||
|
|
@ -70,6 +70,7 @@ export const StateItemTitle = observer((props: TStateItemTitleProps) => {
|
||||||
<button
|
<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"
|
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)}
|
onClick={() => setUpdateStateModal(true)}
|
||||||
|
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_EDIT_BUTTON}
|
||||||
>
|
>
|
||||||
<Pencil className="w-3 h-3" />
|
<Pencil className="w-3 h-3" />
|
||||||
</button>
|
</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