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/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 @@ 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..9f7f2935 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 @@ { + 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 67300c13..c25e49da 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() diff --git a/lms/lms/telemetry.py b/lms/lms/telemetry.py new file mode 100644 index 00000000..7e4de8ec --- /dev/null +++ b/lms/lms/telemetry.py @@ -0,0 +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") + ) + + +@frappe.whitelist() +def get_credentials(): + return { + "project_id": frappe.conf.get("posthog_project_id"), + "telemetry_host": frappe.conf.get("posthog_host"), + } 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==