From a439433977321188266ff38cdef09642e4166080 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 17:27:12 +0530 Subject: [PATCH 01/16] feat: Init posthog telemetry to analyse crm usage --- crm/api/__init__.py | 12 ++++- frontend/src/main.js | 15 +++--- frontend/src/telemetry.ts | 97 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 frontend/src/telemetry.ts diff --git a/crm/api/__init__.py b/crm/api/__init__.py index 533a30c0..f0909118 100644 --- a/crm/api/__init__.py +++ b/crm/api/__init__.py @@ -2,6 +2,7 @@ from bs4 import BeautifulSoup import frappe from frappe.translate import get_all_translations from frappe.utils import cstr +from frappe.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD @frappe.whitelist(allow_guest=True) @@ -44,4 +45,13 @@ def get_user_signature(): content = "" if (cstr(_signature) or signature): content = f'

{signature}

' - return content \ No newline at end of file + return content + +@frappe.whitelist() +def get_posthog_settings(): + return { + "posthog_project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD), + "posthog_host": frappe.conf.get(POSTHOG_HOST_FIELD), + "enable_telemetry": frappe.get_system_settings("enable_telemetry"), + "telemetry_site_age": frappe.utils.telemetry.site_age(), + } \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js index 66d44ea2..b0209480 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,10 +1,5 @@ import './index.css' -import { createApp } from 'vue' -import router from './router' -import App from './App.vue' -import { createPinia } from 'pinia' - import { FrappeUI, Button, @@ -19,9 +14,14 @@ import { frappeRequest, FeatherIcon, } from 'frappe-ui' -import translationPlugin from './translation' +import { createApp } from 'vue' +import { createPinia } from 'pinia' import { createDialog } from './utils/dialogs' import { initSocket } from './socket' +import router from './router' +import translationPlugin from './translation' +import { posthogPlugin } from './telemetry' +import App from './App.vue' let globalComponents = { Button, @@ -45,6 +45,7 @@ app.use(FrappeUI) app.use(pinia) app.use(router) app.use(translationPlugin) +app.use(posthogPlugin) for (let key in globalComponents) { app.component(key, globalComponents[key]) } @@ -61,7 +62,7 @@ if (import.meta.env.DEV) { socket = initSocket() app.config.globalProperties.$socket = socket app.mount('#app') - } + }, ) } else { socket = initSocket() diff --git a/frontend/src/telemetry.ts b/frontend/src/telemetry.ts new file mode 100644 index 00000000..e77d8a36 --- /dev/null +++ b/frontend/src/telemetry.ts @@ -0,0 +1,97 @@ +import '../../../frappe/frappe/public/js/lib/posthog.js' +import { createResource } from 'frappe-ui' +import { computed } from 'vue' + +declare global { + interface Window { + posthog: any + } +} +type PosthogSettings = { + posthog_project_id: string + posthog_host: string + enable_telemetry: boolean + telemetry_site_age: number +} +interface CaptureOptions { + data: { + user: string + [key: string]: string | number | boolean | object + } +} + +let posthog: typeof window.posthog = window.posthog + +// Posthog Settings +let posthogSettings = createResource({ + url: 'crm.api.get_posthog_settings', + cache: 'posthog_settings', + onSuccess: (ps: PosthogSettings) => initPosthog(ps), +}) + +let isTelemetryEnabled = () => { + if (!posthogSettings.data) return false + + return ( + posthogSettings.data.enable_telemetry && + posthogSettings.data.posthog_project_id && + posthogSettings.data.posthog_host + ) +} + +// Posthog Initialization +function initPosthog(ps: PosthogSettings) { + if (!isTelemetryEnabled()) return + + posthog.init(ps.posthog_project_id, { + api_host: ps.posthog_host, + person_profiles: 'identified_only', + autocapture: false, + capture_pageview: false, + capture_pageleave: false, + enable_heatmaps: false, + disable_session_recording: true, + loaded: (ph: typeof posthog) => { + window.posthog = ph + ph.identify(window.location.host) + }, + }) +} + +// Posthog Functions +function capture( + event: string, + options: CaptureOptions = { data: { user: '' } }, +) { + if (!isTelemetryEnabled()) return + window.posthog.capture(`crm_${event}`, options) +} + +function startRecording() { + if (!isTelemetryEnabled()) return + if (window.posthog?.__loaded) { + window.posthog.startSessionRecording() + } +} + +function stopRecording() { + if (!isTelemetryEnabled()) return + if (window.posthog?.__loaded && window.posthog.sessionRecordingStarted()) { + window.posthog.stopSessionRecording() + } +} + +// Posthog Plugin +function posthogPlugin(app: any) { + app.config.globalProperties.posthog = posthog + if (!window.posthog?.length) posthogSettings.fetch() +} + +export { + posthog, + posthogSettings, + posthogPlugin, + capture, + startRecording, + stopRecording, +} From 916a2c53afd6c8d39d483097cbe916174cd46c93 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 18:40:00 +0530 Subject: [PATCH 02/16] fix: stop recording when app is unmounted --- frontend/src/components/Apps.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/Apps.vue b/frontend/src/components/Apps.vue index aea4c002..a91c0b28 100644 --- a/frontend/src/components/Apps.vue +++ b/frontend/src/components/Apps.vue @@ -39,6 +39,8 @@ From 3ed038ef4a90af9b06217b4bb4085829c0d21d8b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 18:40:12 +0530 Subject: [PATCH 03/16] fix: capture convert to deal event --- frontend/src/pages/Lead.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index d2e51893..ddb75b7d 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -312,6 +312,7 @@ import { organizationsStore } from '@/stores/organizations' import { statusesStore } from '@/stores/statuses' import { usersStore } from '@/stores/users' import { whatsappEnabled, callEnabled } from '@/composables/settings' +import { capture } from '@/telemetry' import { createResource, FileUploader, @@ -587,6 +588,7 @@ async function convertToDeal(updated) { }, ) if (deal) { + capture('convert_lead_to_deal', { lead: lead.data.name, deal }) if (updated) { await organizations.reload() await contacts.reload() From f971178750bd6da6b8774a674bd6af2f937ae3f6 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 18:45:29 +0530 Subject: [PATCH 04/16] fix: capture note/task/email_template creation --- frontend/src/components/Modals/EmailTemplateModal.vue | 6 +++++- frontend/src/components/Modals/NoteModal.vue | 2 ++ frontend/src/components/Modals/TaskModal.vue | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Modals/EmailTemplateModal.vue b/frontend/src/components/Modals/EmailTemplateModal.vue index 37bd3184..0b2924e4 100644 --- a/frontend/src/components/Modals/EmailTemplateModal.vue +++ b/frontend/src/components/Modals/EmailTemplateModal.vue @@ -96,6 +96,7 @@ diff --git a/frontend/src/components/Settings/SidePanelModal.vue b/frontend/src/components/Settings/SidePanelModal.vue index 92d93a70..401d71cb 100644 --- a/frontend/src/components/Settings/SidePanelModal.vue +++ b/frontend/src/components/Settings/SidePanelModal.vue @@ -74,6 +74,7 @@ import Section from '@/components/Section.vue' import SectionFields from '@/components/SectionFields.vue' import SidePanelLayoutBuilder from '@/components/Settings/SidePanelLayoutBuilder.vue' import { useDebounceFn } from '@vueuse/core' +import { capture } from '@/telemetry' import { Dialog, Badge, Switch, call, createResource } from 'frappe-ui' import { ref, watch, onMounted, nextTick } from 'vue' @@ -143,6 +144,7 @@ function saveChanges() { ).then(() => { loading.value = false show.value = false + capture('side_panel_layout_builder', { doctype: _doctype.value }) emit('reload') }) } From 53a352358bf96a0188e95dd48b855bd986dd9d13 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 19:50:19 +0530 Subject: [PATCH 13/16] fix: capture sla status change event --- frontend/src/components/SLASection.vue | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/SLASection.vue b/frontend/src/components/SLASection.vue index 4ec2af41..4c728b8a 100644 --- a/frontend/src/components/SLASection.vue +++ b/frontend/src/components/SLASection.vue @@ -5,7 +5,9 @@ :key="s.label" class="flex items-center gap-2 text-base leading-5" > -
{{ __(s.label) }}
+
+ {{ __(s.label) }} +
@@ -43,6 +45,7 @@ import { Dropdown, Tooltip } from 'frappe-ui' import { timeAgo, dateFormat, formatTime, dateTooltipFormat } from '@/utils' import { statusesStore } from '@/stores/statuses' +import { capture } from '@/telemetry' import { computed, defineModel } from 'vue' const data = defineModel() @@ -58,8 +61,8 @@ let slaSection = computed(() => { data.value.sla_status == 'Failed' ? 'red' : data.value.sla_status == 'Fulfilled' - ? 'green' - : 'orange' + ? 'green' + : 'orange' if (status == 'First Response Due') { status = timeAgo(data.value.response_by) @@ -94,11 +97,13 @@ let slaSection = computed(() => { options: communicationStatuses.data?.map((status) => ({ label: status.name, value: status.name, - onClick: () => - emit('updateField', 'communication_status', status.name), + onClick: () => { + capture('sla_status_change') + emit('updateField', 'communication_status', status.name) + }, })), }, - ] + ], ) return sections }) From 7c434db5d8c31f2eb1f972ee55d3ee1427c49504 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 16 Aug 2024 19:54:47 +0530 Subject: [PATCH 14/16] fix: capture notification mark as read event --- frontend/src/components/Notifications.vue | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Notifications.vue b/frontend/src/components/Notifications.vue index 719046d2..74c2eca3 100644 --- a/frontend/src/components/Notifications.vue +++ b/frontend/src/components/Notifications.vue @@ -18,10 +18,7 @@
-