diff --git a/frontend/package.json b/frontend/package.json index 77e7d608..bead43a6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 76464f03..3e352bbb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -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() - } -}) diff --git a/frontend/src/components/CourseCardOverlay.vue b/frontend/src/components/CourseCardOverlay.vue index d401836b..b01e5fc2 100644 --- a/frontend/src/components/CourseCardOverlay.vue +++ b/frontend/src/components/CourseCardOverlay.vue @@ -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, + }) }, }) diff --git a/frontend/src/components/Modals/ChapterModal.vue b/frontend/src/components/Modals/ChapterModal.vue index 6bc70b2a..409fb200 100644 --- a/frontend/src/components/Modals/ChapterModal.vue +++ b/frontend/src/components/Modals/ChapterModal.vue @@ -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({ diff --git a/frontend/src/components/Modals/ZoomAccountModal.vue b/frontend/src/components/Modals/ZoomAccountModal.vue index 5f0bb999..48f986a4 100644 --- a/frontend/src/components/Modals/ZoomAccountModal.vue +++ b/frontend/src/components/Modals/ZoomAccountModal.vue @@ -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') const zoomAccounts = defineModel('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')) diff --git a/frontend/src/components/Settings/PaymentGatewayDetails.vue b/frontend/src/components/Settings/PaymentGatewayDetails.vue index c6392140..2af7d6d4 100644 --- a/frontend/src/components/Settings/PaymentGatewayDetails.vue +++ b/frontend/src/components/Settings/PaymentGatewayDetails.vue @@ -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({ required: true, default: false }) const paymentGateways = defineModel('paymentGateways') const newGateway = ref(null) const newGatewayFields = ref([]) const newGatewayData = ref>({}) +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() }) diff --git a/frontend/src/components/Sidebar/AppSidebar.vue b/frontend/src/components/Sidebar/AppSidebar.vue index fbb95344..f28c0a0f 100644 --- a/frontend/src/components/Sidebar/AppSidebar.vue +++ b/frontend/src/components/Sidebar/AppSidebar.vue @@ -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) diff --git a/frontend/src/main.js b/frontend/src/main.js index c2968afa..00873dc2 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -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()) diff --git a/frontend/src/pages/BatchForm.vue b/frontend/src/pages/BatchForm.vue index e88b1aa6..8338cb45 100644 --- a/frontend/src/pages/BatchForm.vue +++ b/frontend/src/pages/BatchForm.vue @@ -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({ diff --git a/frontend/src/pages/Billing.vue b/frontend/src/pages/Billing.vue index 81ed9a5e..00a98a94 100644 --- a/frontend/src/pages/Billing.vue +++ b/frontend/src/pages/Billing.vue @@ -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) { diff --git a/frontend/src/pages/CourseForm.vue b/frontend/src/pages/CourseForm.vue index 5a412e1c..9a62a2de 100644 --- a/frontend/src/pages/CourseForm.vue +++ b/frontend/src/pages/CourseForm.vue @@ -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')) }, diff --git a/frontend/src/pages/LessonForm.vue b/frontend/src/pages/LessonForm.vue index b3a3f8a8..1a79db57 100644 --- a/frontend/src/pages/LessonForm.vue +++ b/frontend/src/pages/LessonForm.vue @@ -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({ diff --git a/frontend/src/pages/QuizForm.vue b/frontend/src/pages/QuizForm.vue index d3e8cc7a..e43e06f8 100644 --- a/frontend/src/pages/QuizForm.vue +++ b/frontend/src/pages/QuizForm.vue @@ -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')) }, diff --git a/frontend/src/telemetry.ts b/frontend/src/telemetry.ts deleted file mode 100644 index eb17613c..00000000 --- a/frontend/src/telemetry.ts +++ /dev/null @@ -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, -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 95621ac6..c3ca947a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"