From a9e93a679bdc11e9cd4cb8cd8f04a26982d9452c Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 7 Aug 2024 11:31:58 +0530 Subject: [PATCH 1/3] feat: posthog initialization --- .github/workflows/generate-pot-file.yml | 2 +- frontend/src/main.js | 2 + frontend/src/posthog.js | 24 ++++++++ frontend/yarn.lock | 29 ++++++---- lms/lms/api.py | 21 +++++++ package.json | 1 + yarn.lock | 75 ++++++++++++++++--------- 7 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 frontend/src/posthog.js diff --git a/.github/workflows/generate-pot-file.yml b/.github/workflows/generate-pot-file.yml index 390393d7..4c5d81e1 100644 --- a/.github/workflows/generate-pot-file.yml +++ b/.github/workflows/generate-pot-file.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: jobs: - regeneratee-pot-file: + regenerate-pot-file: name: Release runs-on: ubuntu-latest strategy: diff --git a/frontend/src/main.js b/frontend/src/main.js index 72dce29a..d26c11d3 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -6,6 +6,7 @@ import App from './App.vue' import { createPinia } from 'pinia' import dayjs from '@/utils/dayjs' import translationPlugin from './translation' +import posthog from './posthog' import { usersStore } from './stores/user' import { sessionStore } from './stores/session' import { initSocket } from './socket' @@ -25,6 +26,7 @@ app.use(FrappeUI) app.use(pinia) app.use(router) app.use(translationPlugin) +app.use(posthog) app.use(pageMetaPlugin) app.provide('$dayjs', dayjs) app.provide('$socket', initSocket()) diff --git a/frontend/src/posthog.js b/frontend/src/posthog.js new file mode 100644 index 00000000..f3c5bdb8 --- /dev/null +++ b/frontend/src/posthog.js @@ -0,0 +1,24 @@ +import posthog from "posthog-js"; +import { createResource } from 'frappe-ui' + +const apiInfo = createResource({ + url: 'lms.lms.api.get_posthog_api_key', + cache: 'apiInfo', + auto: true, + onSuccess(data) { + return data + }, +}) + + +export default { + install(app) { + app.config.globalProperties.$posthog = posthog.init(apiInfo.data.project_id, { + api_host: apiInfo.data.posthog_host, + autocapture: false, + capture_pageview: false, + capture_pageleave: false, + advanced_disable_decide: apiInfo.data.should_record_session, + }); + }, +}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 30adce7c..7833087e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -68,13 +68,6 @@ dependencies: "@codexteam/icons" "^0.0.5" -"@editorjs/image@^2.9.2": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@editorjs/image/-/image-2.9.2.tgz#c8bea65a578fab65a1a75df1223b4fd8f06b57d5" - integrity sha512-n09sMieGW8cksoeflpplzvbmFH2bdVzVTWbnidPWAHaeU467HRteoXU9yfGBB7+eeHZLnmCulQ2dr6ae+G2niw== - dependencies: - "@codexteam/icons" "^0.3.0" - "@editorjs/inline-code@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@editorjs/inline-code/-/inline-code-1.5.0.tgz#ad5849bac3396b9dad22dceeda76198dd991e426" @@ -1882,8 +1875,16 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1901,8 +1902,14 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== diff --git a/lms/lms/api.py b/lms/lms/api.py index 67300c13..a27557e7 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -6,6 +6,7 @@ from frappe.translate import get_all_translations from frappe import _ from frappe.query_builder import DocType from frappe.query_builder.functions import Count +from frappe.utils import time_diff, now_datetime, get_datetime @frappe.whitelist() @@ -559,3 +560,23 @@ 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/package.json b/package.json index f5231fac..3d55b554 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "cypress-file-upload": "^5.0.8" }, "dependencies": { + "posthog-js": "^1.154.4", "pre-commit": "^1.2.2" } } diff --git a/yarn.lock b/yarn.lock index 415d00a2..693509b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -107,7 +107,7 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -assert-plus@^1.0.0, assert-plus@1.0.0: +assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== @@ -294,7 +294,7 @@ concat-stream@^1.4.7: readable-stream "^2.2.2" typedarray "^0.0.6" -core-util-is@~1.0.0, core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== @@ -322,7 +322,7 @@ cypress-file-upload@^5.0.8: resolved "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz" integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== -cypress@^13.9.0, cypress@>3.0.0: +cypress@^13.9.0: version "13.9.0" resolved "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz" integrity sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ== @@ -430,7 +430,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enquirer@^2.3.6, "enquirer@>= 2.3.0 < 3": +enquirer@^2.3.6: version "2.4.1" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz" integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== @@ -498,16 +498,16 @@ extract-zip@2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" @@ -515,6 +515,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + figures@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" @@ -836,16 +841,16 @@ minimist@^1.2.8: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + npm-run-path@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" @@ -909,6 +914,15 @@ pify@^2.2.0: resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== +posthog-js@^1.154.4: + version "1.154.4" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.154.4.tgz#217524e45f7ceb68a268caf683da60dfa91eccbd" + integrity sha512-J6SvhjNGOqkL8uH/sL3h4rXN7bUz9MnCJ1bu/D9Vkf6Enft8LlkOnzackUwR8EpKdM/dhA0dUjDk5Fz/6dovbw== + dependencies: + fflate "^0.4.8" + preact "^10.19.3" + web-vitals "^4.0.1" + pre-commit@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz" @@ -918,6 +932,11 @@ pre-commit@^1.2.2: spawn-sync "^1.0.15" which "1.2.x" +preact@^10.19.3: + version "10.23.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.1.tgz#d400107289bc979881c5212cb5f5cd22cd1dc38c" + integrity sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A== + pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" @@ -1023,12 +1042,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.2: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1135,13 +1149,6 @@ sshpk@^1.14.1: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -1151,6 +1158,13 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -1276,7 +1290,12 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -which@^1.2.9, which@1.2.x: +web-vitals@^4.0.1: + version "4.2.2" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.2.tgz#e883245180b95e175eb75a5ca8903b1a11597d7a" + integrity sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw== + +which@1.2.x, which@^1.2.9: version "1.2.14" resolved "https://registry.npmjs.org/which/-/which-1.2.14.tgz" integrity sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw== From 703fafd6c3de09f8a8274a6ead2623c9b034610a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 12 Aug 2024 17:13:31 +0530 Subject: [PATCH 2/3] chore: analytics --- frontend/src/App.vue | 12 ++- frontend/src/components/BatchOverlay.vue | 2 +- frontend/src/components/Controls/Link.vue | 1 - frontend/src/components/CourseCardOverlay.vue | 2 +- frontend/src/components/LessonPlugins.vue | 2 - .../src/components/Modals/ChapterModal.vue | 2 + frontend/src/main.js | 2 - .../{BatchCreation.vue => BatchForm.vue} | 6 +- frontend/src/pages/Batches.vue | 2 +- .../{CreateCourse.vue => CourseForm.vue} | 8 +- frontend/src/pages/Courses.vue | 2 +- frontend/src/pages/LessonForm.vue | 3 + frontend/src/posthog.js | 24 ----- frontend/src/router.js | 8 +- frontend/src/telemetry.ts | 98 +++++++++++++++++++ frontend/src/utils/index.js | 4 +- lms/lms/api.py | 20 ---- lms/lms/telemetry.py | 16 +++ package.json | 1 - 19 files changed, 151 insertions(+), 64 deletions(-) rename frontend/src/pages/{BatchCreation.vue => BatchForm.vue} (98%) rename frontend/src/pages/{CreateCourse.vue => CourseForm.vue} (98%) delete mode 100644 frontend/src/posthog.js create mode 100644 frontend/src/telemetry.ts create mode 100644 lms/lms/telemetry.py diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6a9d4d5b..e7dfc8dc 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 2eb225c2..670548a4 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 2ace50f6..77e73c5d 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 dbd33dda..4e92db90 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 c367d151..299cf434 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 00000000..dfd5e3b6 --- /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 e732e09f..2123d981 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 a27557e7..c25e49da 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 00000000..c704351f --- /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 3d55b554..f5231fac 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" } } From 0335b3b4d0151ca9c255025aadcf76306d72fff1 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 12 Aug 2024 17:38:59 +0530 Subject: [PATCH 3/3] chore: fixed linters --- frontend/src/pages/BatchForm.vue | 6 +++--- frontend/src/pages/CourseForm.vue | 6 +++--- lms/lms/telemetry.py | 20 +++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/src/pages/BatchForm.vue b/frontend/src/pages/BatchForm.vue index 74a9101a..c5fd6d80 100644 --- a/frontend/src/pages/BatchForm.vue +++ b/frontend/src/pages/BatchForm.vue @@ -236,7 +236,7 @@ import MultiSelect from '@/components/Controls/MultiSelect.vue' import { useRouter } from 'vue-router' import { getFileSize, showToast } from '../utils' import { X, FileText } from 'lucide-vue-next' -import { capture } from "@/telemetry" +import { capture } from '@/telemetry' const router = useRouter() const user = inject('$user') @@ -276,7 +276,7 @@ onMounted(() => { if (props.batchName != 'new') { batchDetail.reload() } else { - capture("batch_form_opened") + capture('batch_form_opened') } window.addEventListener('keydown', keyboardShortcut) }) @@ -380,7 +380,7 @@ const createNewBatch = () => { {}, { onSuccess(data) { - capture("batch_created") + capture('batch_created') router.push({ name: 'BatchDetail', params: { diff --git a/frontend/src/pages/CourseForm.vue b/frontend/src/pages/CourseForm.vue index 4e92db90..9f7f2935 100644 --- a/frontend/src/pages/CourseForm.vue +++ b/frontend/src/pages/CourseForm.vue @@ -227,7 +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"; +import { capture } from '@/telemetry' const user = inject('$user') const newTag = ref('') @@ -270,7 +270,7 @@ onMounted(() => { if (props.courseName !== 'new') { courseResource.reload() } else { - capture("course_form_opened") + capture('course_form_opened') } window.addEventListener('keydown', keyboardShortcut) }) @@ -391,7 +391,7 @@ const submitCourse = () => { } else { courseCreationResource.submit(course, { onSuccess(data) { - capture("course_created") + capture('course_created') showToast('Success', 'Course created successfully', 'check') router.push({ name: 'CourseForm', diff --git a/lms/lms/telemetry.py b/lms/lms/telemetry.py index c704351f..7e4de8ec 100644 --- a/lms/lms/telemetry.py +++ b/lms/lms/telemetry.py @@ -1,16 +1,18 @@ 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") - ) + 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"), - } + return { + "project_id": frappe.conf.get("posthog_project_id"), + "telemetry_host": frappe.conf.get("posthog_host"), + }