diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6a9d4d5b8..e7dfc8dc5 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -8,10 +8,12 @@ diff --git a/frontend/src/components/BatchOverlay.vue b/frontend/src/components/BatchOverlay.vue index 2eb225c22..670548a41 100644 --- a/frontend/src/components/BatchOverlay.vue +++ b/frontend/src/components/BatchOverlay.vue @@ -81,7 +81,7 @@ { if (!user.data) window.location.href = '/login' if (props.batchName != 'new') { batchDetail.reload() + } else { + capture("batch_form_opened") } window.addEventListener('keydown', keyboardShortcut) }) @@ -377,6 +380,7 @@ const createNewBatch = () => { {}, { onSuccess(data) { + capture("batch_created") router.push({ name: 'BatchDetail', params: { @@ -447,7 +451,7 @@ const breadcrumbs = computed(() => { } crumbs.push({ label: props.batchName == 'new' ? 'New Batch' : 'Edit Batch', - route: { name: 'BatchCreation', params: { batchName: props.batchName } }, + route: { name: 'BatchForm', params: { batchName: props.batchName } }, }) return crumbs }) diff --git a/frontend/src/pages/Batches.vue b/frontend/src/pages/Batches.vue index 2ace50f69..77e73c5d4 100644 --- a/frontend/src/pages/Batches.vue +++ b/frontend/src/pages/Batches.vue @@ -19,7 +19,7 @@ diff --git a/frontend/src/pages/CreateCourse.vue b/frontend/src/pages/CourseForm.vue similarity index 98% rename from frontend/src/pages/CreateCourse.vue rename to frontend/src/pages/CourseForm.vue index dbd33dda1..4e92db901 100644 --- a/frontend/src/pages/CreateCourse.vue +++ b/frontend/src/pages/CourseForm.vue @@ -227,6 +227,7 @@ import { FileText, X } from 'lucide-vue-next' import { useRouter } from 'vue-router' import CourseOutline from '@/components/CourseOutline.vue' import MultiSelect from '@/components/Controls/MultiSelect.vue' +import { capture } from "@/telemetry"; const user = inject('$user') const newTag = ref('') @@ -268,6 +269,8 @@ onMounted(() => { if (props.courseName !== 'new') { courseResource.reload() + } else { + capture("course_form_opened") } window.addEventListener('keydown', keyboardShortcut) }) @@ -388,9 +391,10 @@ const submitCourse = () => { } else { courseCreationResource.submit(course, { onSuccess(data) { + capture("course_created") showToast('Success', 'Course created successfully', 'check') router.push({ - name: 'CreateCourse', + name: 'CourseForm', params: { courseName: data.name }, }) }, @@ -489,7 +493,7 @@ const breadcrumbs = computed(() => { } crumbs.push({ label: props.courseName == 'new' ? 'New Course' : 'Edit Course', - route: { name: 'CreateCourse', params: { courseName: props.courseName } }, + route: { name: 'CourseForm', params: { courseName: props.courseName } }, }) return crumbs }) diff --git a/frontend/src/pages/Courses.vue b/frontend/src/pages/Courses.vue index c367d1512..299cf4345 100644 --- a/frontend/src/pages/Courses.vue +++ b/frontend/src/pages/Courses.vue @@ -22,7 +22,7 @@ import('@/pages/CreateCourse.vue'), + name: 'CourseForm', + component: () => import('@/pages/CourseForm.vue'), props: true, }, { @@ -109,8 +109,8 @@ const routes = [ }, { path: '/batches/:batchName/edit', - name: 'BatchCreation', - component: () => import('@/pages/BatchCreation.vue'), + name: 'BatchForm', + component: () => import('@/pages/BatchForm.vue'), props: true, }, { diff --git a/frontend/src/telemetry.ts b/frontend/src/telemetry.ts new file mode 100644 index 000000000..dfd5e3b6a --- /dev/null +++ b/frontend/src/telemetry.ts @@ -0,0 +1,98 @@ +import { useStorage } from "@vueuse/core"; +import { call } from "frappe-ui"; +import "../../../frappe/frappe/public/js/lib/posthog.js"; + +const APP = "lms"; +const SITENAME = window.location.hostname; + +declare global { + interface Window { + posthog: any; + } +} + +const telemetry = useStorage("telemetry", { + enabled: false, + project_id: "", + host: "", +}); + +export async function init() { + await set_enabled(); + if (!telemetry.value.enabled) return; + try { + await set_credentials(); + window.posthog.init(telemetry.value.project_id, { + api_host: telemetry.value.host, + autocapture: false, + person_profiles: "always", + capture_pageview: true, + capture_pageleave: true, + disable_session_recording: false, + session_recording: { + maskAllInputs: false, + maskInputOptions: { + password: true, + }, + }, + loaded: (posthog) => { + window.posthog = posthog; + window.posthog.identify(SITENAME); + }, + }); + } catch (e) { + console.trace("Failed to initialize telemetry", e); + telemetry.value.enabled = false; + } +} + +async function set_enabled() { + if (telemetry.value.enabled) return; + + await call("lms.lms.telemetry.is_enabled").then((res) => { + telemetry.value.enabled = res; + }); +} + +async function set_credentials() { + if (!telemetry.value.enabled) return; + if (telemetry.value.project_id && telemetry.value.host) return; + + await call("lms.lms.telemetry.get_credentials").then((res) => { + telemetry.value.project_id = res.project_id; + telemetry.value.host = res.telemetry_host; + }); +} + +interface CaptureOptions { + data: { + user: string; + [key: string]: string | number | boolean | object; + }; +} + +export function capture( + event: string, + options: CaptureOptions = { data: { user: "" } } +) { + if (!telemetry.value.enabled) return; + window.posthog.capture(`${APP}_${event}`, options); +} + +export function recordSession() { + if (!telemetry.value.enabled) return; + if (window.posthog && window.posthog.__loaded) { + window.posthog.startSessionRecording(); + } +} + +export function stopSession() { + if (!telemetry.value.enabled) return; + if ( + window.posthog && + window.posthog.__loaded && + window.posthog.sessionRecordingStarted() + ) { + window.posthog.stopSessionRecording(); + } +} diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index e732e09f2..2123d981d 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -424,7 +424,7 @@ export function getSidebarLinks() { 'Courses', 'CourseDetail', 'Lesson', - 'CreateCourse', + 'CourseForm', 'LessonForm', ], }, @@ -432,7 +432,7 @@ export function getSidebarLinks() { label: 'Batches', icon: 'Users', to: 'Batches', - activeFor: ['Batches', 'BatchDetail', 'Batch', 'BatchCreation'], + activeFor: ['Batches', 'BatchDetail', 'Batch', 'BatchForm'], }, { label: 'Certified Participants', diff --git a/lms/lms/api.py b/lms/lms/api.py index a27557e75..c25e49da2 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -560,23 +560,3 @@ def get_categories(doctype, filters): categoryOptions.append({"label": category, "value": category}) return categoryOptions - -@frappe.whitelist(allow_guest=True) -def get_posthog_api_key(): - should_record_session - return { - "project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD), - "posthog_host": frappe.conf.get(POSTHOG_HOST_FIELD), - "enable_telemetry": frappe.get_system_settings("enable_telemetry"), - "should_record_session": should_record_session(), - } - -def should_record_session(): - start_datetime = frappe.boot.sysdefaults.session_recording_start - start_datetime = get_datetime(start_datetime) - if not start_datetime: - return False - - now = now_datetime() - # if user allowed recording only record for first 2 hours, never again. - return time_diff(now, start_datetime) < 120; \ No newline at end of file diff --git a/lms/lms/telemetry.py b/lms/lms/telemetry.py new file mode 100644 index 000000000..c704351f3 --- /dev/null +++ b/lms/lms/telemetry.py @@ -0,0 +1,16 @@ +import frappe + +@frappe.whitelist() +def is_enabled(): + return bool( + frappe.get_system_settings("enable_telemetry") + and frappe.conf.get("posthog_host") + and frappe.conf.get("posthog_project_id") + ) + +@frappe.whitelist() +def get_credentials(): + return { + "project_id": frappe.conf.get("posthog_project_id"), + "telemetry_host": frappe.conf.get("posthog_host"), + } diff --git a/package.json b/package.json index 3d55b554e..f5231fac1 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "cypress-file-upload": "^5.0.8" }, "dependencies": { - "posthog-js": "^1.154.4", "pre-commit": "^1.2.2" } }