refactor: telemetry

* only works with frappe v15.96+
This commit is contained in:
Saqib Ansari
2026-01-14 14:48:05 +05:30
parent 4ace8b2ec0
commit 0aeada4549
15 changed files with 40 additions and 115 deletions

View File

@@ -34,7 +34,7 @@
"dayjs": "1.11.10",
"dompurify": "3.2.6",
"feather-icons": "4.28.0",
"frappe-ui": "^0.1.254",
"frappe-ui": "^0.1.256",
"highlight.js": "11.11.1",
"lucide-vue-next": "0.383.0",
"markdown-it": "14.0.0",

View File

@@ -15,7 +15,6 @@ import { useScreenSize } from './utils/composables'
import { usersStore } from '@/stores/user'
import { useSettings } from '@/stores/settings'
import { useRouter } from 'vue-router'
import { posthogSettings } from '@/telemetry'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
@@ -50,9 +49,4 @@ onUnmounted(() => {
noSidebar.value = false
})
watch(userResource, () => {
if (userResource.data) {
posthogSettings.reload()
}
})
</script>

View File

@@ -189,15 +189,16 @@ import {
import { computed, inject, ref } from 'vue'
import { Badge, Button, call, createResource, toast } from 'frappe-ui'
import { formatAmount } from '@/utils/'
import { capture } from '@/telemetry'
import { useRouter } from 'vue-router'
import CertificationLinks from '@/components/CertificationLinks.vue'
import CourseProgressSummary from '@/components/Modals/CourseProgressSummary.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const router = useRouter()
const user = inject('$user')
const showProgressModal = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
course: {
@@ -284,6 +285,9 @@ const certificate = createResource({
}&format=${encodeURIComponent(data.template)}`,
'_blank'
)
capture('certificate_issued', {
course: props.course.data.name,
})
},
})

View File

@@ -80,13 +80,13 @@ import {
} from 'frappe-ui'
import { reactive, watch, inject } from 'vue'
import { getFileSize } from '@/utils/'
import { capture } from '@/telemetry'
import { FileText, X } from 'lucide-vue-next'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
const show = defineModel()
const outline = defineModel('outline')
const user = inject('$user')
const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning')
const props = defineProps({

View File

@@ -66,6 +66,7 @@ import { inject, reactive, watch } from 'vue'
import { User } from '@/components/Settings/types'
import { openSettings, cleanError } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import { useTelemetry } from 'frappe-ui/frappe'
interface ZoomAccount {
name: string
@@ -97,6 +98,7 @@ interface ZoomAccounts {
const show = defineModel('show')
const user = inject<User | null>('$user')
const zoomAccounts = defineModel<ZoomAccounts>('zoomAccounts')
const { capture } = useTelemetry()
const account = reactive({
name: '',
@@ -154,6 +156,7 @@ const createAccount = (close: () => void) => {
},
{
onSuccess() {
capture('zoom_account_linked')
zoomAccounts.value?.reload()
close()
toast.success(__('Zoom Account created successfully'))

View File

@@ -52,12 +52,14 @@ import {
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import SettingFields from '@/components/Settings/SettingFields.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const show = defineModel<boolean>({ required: true, default: false })
const paymentGateways = defineModel<any>('paymentGateways')
const newGateway = ref(null)
const newGatewayFields = ref([])
const newGatewayData = ref<Record<string, any>>({})
const { capture } = useTelemetry()
const props = defineProps<{
gatewayID: string | null
@@ -158,6 +160,7 @@ const saveNewGateway = (close: () => void) => {
...newGatewayData.value,
},
}).then((data: any) => {
capture('payment_gateway_configured')
paymentGateways.value.reload()
close()
})

View File

@@ -199,7 +199,6 @@ import { useSidebar } from '@/stores/sidebar'
import { useSettings } from '@/stores/settings'
import { Button, call, createResource, Tooltip, toast } from 'frappe-ui'
import PageModal from '@/components/Modals/PageModal.vue'
import { capture } from '@/telemetry'
import LMSLogo from '@/components/Icons/LMSLogo.vue'
import { useRouter } from 'vue-router'
import {
@@ -233,6 +232,7 @@ import {
showHelpModal,
minimize,
IntermediateStepModal,
useTelemetry,
} from 'frappe-ui/frappe'
import InviteIcon from '@/components/Icons/InviteIcon.vue'
import UserDropdown from '@/components/Sidebar/UserDropdown.vue'
@@ -246,6 +246,7 @@ let sidebarStore = useSidebar()
const socket = inject('$socket')
const unreadCount = ref(0)
const sidebarLinks = ref(null)
const { capture } = useTelemetry()
const showPageModal = ref(false)
const isModerator = ref(false)
const isInstructor = ref(false)

View File

@@ -9,6 +9,7 @@ import translationPlugin from './translation'
import { usersStore } from './stores/user'
import { initSocket } from './socket'
import { FrappeUI, setConfig, frappeRequest, pageMetaPlugin } from 'frappe-ui'
import { telemetryPlugin } from 'frappe-ui/frappe'
let pinia = createPinia()
let app = createApp(App)
@@ -18,6 +19,7 @@ app.use(FrappeUI)
app.use(pinia)
app.use(router)
app.use(translationPlugin)
app.use(telemetryPlugin, { app_name: 'lms' })
app.use(pageMetaPlugin)
app.provide('$dayjs', dayjs)
app.provide('$socket', initSocket())

View File

@@ -292,8 +292,7 @@ import {
} from 'frappe-ui'
import { useRouter } from 'vue-router'
import { Image, Trash2 } from 'lucide-vue-next'
import { capture } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
import { sessionStore } from '../stores/session'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import Link from '@/components/Controls/Link.vue'
@@ -312,6 +311,7 @@ const { brand } = sessionStore()
const { updateOnboardingStep } = useOnboarding('learning')
const instructors = ref([])
const app = getCurrentInstance()
const { capture } = useTelemetry()
const { $dialog } = app.appContext.config.globalProperties
const props = defineProps({

View File

@@ -234,10 +234,12 @@ import { sessionStore } from '../stores/session'
import Link from '@/components/Controls/Link.vue'
import NotPermitted from '@/components/NotPermitted.vue'
import { X } from 'lucide-vue-next'
import { useTelemetry } from 'frappe-ui/frappe'
const user = inject('$user')
const { brand } = sessionStore()
const showConsentWarning = ref(false)
const { capture } = useTelemetry()
onMounted(() => {
const script = document.createElement('script')
@@ -339,6 +341,7 @@ const generatePaymentLink = () => {
return validateAddress()
},
onSuccess(data) {
capture('checkout_initiated', { type: props.type })
window.location.href = data
},
onError(err) {

View File

@@ -349,8 +349,7 @@ import {
} from 'vue'
import { Image, Trash2, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { capture, startRecording, stopRecording } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
import { sessionStore } from '../stores/session'
import {
escapeHTML,
@@ -372,6 +371,7 @@ const router = useRouter()
const instructors = ref([])
const related_courses = ref([])
const app = getCurrentInstance()
const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning')
const { $dialog } = app.appContext.config.globalProperties
@@ -418,7 +418,6 @@ onMounted(() => {
fetchCourseInfo()
} else {
capture('course_form_opened')
startRecording()
}
window.addEventListener('keydown', keyboardShortcut)
})
@@ -441,7 +440,6 @@ const keyboardShortcut = (e) => {
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
stopRecording()
})
const courseCreationResource = createResource({
@@ -582,12 +580,16 @@ const createCourse = () => {
}
const editCourse = () => {
let was_published = courseResource.data.published
courseEditResource.submit(
{
course: courseResource.data.name,
},
{
onSuccess() {
if (!was_published && course.published) {
capture('publish_course')
}
updateMetaInfo('courses', props.courseName, meta)
toast.success(__('Course updated successfully'))
},

View File

@@ -99,14 +99,14 @@ import EditorJS from '@editorjs/editorjs'
import LessonHelp from '@/components/LessonHelp.vue'
import { ChevronRight } from 'lucide-vue-next'
import { getEditorTools, enablePlyr } from '@/utils'
import { capture, startRecording, stopRecording } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
const { brand } = sessionStore()
const editor = ref(null)
const instructorEditor = ref(null)
const user = inject('$user')
const openInstructorEditor = ref(false)
const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning')
let autoSaveInterval
let showSuccessMessage = false
@@ -131,7 +131,6 @@ onMounted(() => {
window.location.href = '/login'
}
capture('lesson_form_opened')
startRecording()
editor.value = renderEditor('content')
instructorEditor.value = renderEditor('instructor-notes')
window.addEventListener('keydown', keyboardShortcut)
@@ -226,7 +225,6 @@ const keyboardShortcut = (e) => {
onBeforeUnmount(() => {
clearInterval(autoSaveInterval)
window.removeEventListener('keydown', keyboardShortcut)
stopRecording()
})
const newLessonResource = createResource({

View File

@@ -233,6 +233,7 @@ import { ClipboardList, ListChecks, Plus, Trash2 } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { escapeHTML } from '@/utils'
import Question from '@/components/Modals/Question.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const { brand } = sessionStore()
const showQuestionModal = ref(false)
@@ -241,6 +242,7 @@ const currentQuestion = reactive({
marks: 0,
name: '',
})
const { capture } = useTelemetry()
const user = inject('$user')
const router = useRouter()
const readOnlyMode = window.read_only_mode
@@ -308,6 +310,9 @@ const submitQuiz = () => {
},
{
onSuccess(data) {
if (props.quizID === 'new') {
capture('quiz_created')
}
quizDetails.doc.total_marks = data.total_marks
toast.success(__('Quiz updated successfully'))
},

View File

@@ -1,90 +0,0 @@
import '../../../frappe/frappe/public/js/lib/posthog.js'
import { createResource } from 'frappe-ui'
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: 'lms.lms.telemetry.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: true,
capture_pageleave: true,
enable_heatmaps: false,
disable_session_recording: false,
loaded: (ph: typeof posthog) => {
window.posthog = ph
ph.identify(window.location.hostname)
},
})
}
// Posthog Functions
function capture(
event: string,
options: CaptureOptions = { data: { user: '' } },
) {
if (!isTelemetryEnabled()) return
window.posthog.capture(`lms_${event}`, options)
}
function startRecording() {
}
function stopRecording() {
}
// Posthog Plugin
function posthogPlugin(app: any) {
app.config.globalProperties.posthog = posthog
if (!window.posthog?.length) posthogSettings.fetch()
}
export {
posthog,
posthogSettings,
posthogPlugin,
capture,
startRecording,
stopRecording,
}

View File

@@ -2977,10 +2977,10 @@ fraction.js@^4.1.2:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.254:
version "0.1.254"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.254.tgz#a0edd00a8a87e1f15e01c4526089c82e32e8630d"
integrity sha512-qdf7h8S6mwwajQMC6p+M11qKB0gVtNKnPTphK2nzrG9M8vxND6t0/8D6LVMX/Tqv/gpQJg7FI7V2hi+p8bT0OQ==
frappe-ui@^0.1.256:
version "0.1.256"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.256.tgz#c14756eda75ca01ada034559e8bd2f91bcfe6dff"
integrity sha512-zj8n6KXpMv/0h1NcaCsjFLP8QBnofDEBJgQa+xECU0/jbq4gSqNhFOkcx788qNL+vmBo9frywTeXwDpl7hUCZA==
dependencies:
"@floating-ui/vue" "^1.1.6"
"@headlessui/vue" "^1.7.14"