chore: analytics
This commit is contained in:
@@ -8,10 +8,12 @@
|
||||
<script setup>
|
||||
import { Toasts } from 'frappe-ui'
|
||||
import { Dialogs } from '@/utils/dialogs'
|
||||
import { computed, defineAsyncComponent } from 'vue'
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useScreenSize } from './utils/composables'
|
||||
import DesktopLayout from './components/DesktopLayout.vue'
|
||||
import MobileLayout from './components/MobileLayout.vue'
|
||||
import { stopSession } from '@/telemetry'
|
||||
import { init as initTelemetry } from '@/telemetry'
|
||||
|
||||
const screenSize = useScreenSize()
|
||||
|
||||
@@ -22,4 +24,12 @@ const Layout = computed(() => {
|
||||
return DesktopLayout
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await initTelemetry()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopSession()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<router-link
|
||||
v-if="isModerator"
|
||||
:to="{
|
||||
name: 'BatchCreation',
|
||||
name: 'BatchForm',
|
||||
params: {
|
||||
batchName: batch.data.name,
|
||||
},
|
||||
|
||||
@@ -77,7 +77,6 @@ const valuePropPassed = computed(() => 'value' in attrs)
|
||||
const value = computed({
|
||||
get: () => (valuePropPassed.value ? attrs.value : props.modelValue),
|
||||
set: (val) => {
|
||||
console.log(valuePropPassed.value)
|
||||
return (
|
||||
val?.value &&
|
||||
emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value)
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<router-link
|
||||
v-if="user?.data?.is_moderator || is_instructor()"
|
||||
:to="{
|
||||
name: 'CreateCourse',
|
||||
name: 'CourseForm',
|
||||
params: {
|
||||
courseName: course.data.name,
|
||||
},
|
||||
|
||||
@@ -124,8 +124,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const addQuiz = (value) => {
|
||||
console.log('here')
|
||||
console.log(value)
|
||||
getCurrentEditor().caret.setToLastBlock('end', 0)
|
||||
if (value) {
|
||||
getCurrentEditor().blocks.insert('quiz', {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { Dialog, FormControl, createResource } from 'frappe-ui'
|
||||
import { defineModel, reactive, watch } from 'vue'
|
||||
import { createToast } from '@/utils/'
|
||||
import { capture } from '@/telemetry'
|
||||
|
||||
const show = defineModel()
|
||||
const outline = defineModel('outline')
|
||||
@@ -91,6 +92,7 @@ const addChapter = (close) => {
|
||||
}
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
capture('chapter_created')
|
||||
chapterReference.submit(
|
||||
{ name: data.name },
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ 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'
|
||||
@@ -26,7 +25,6 @@ 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())
|
||||
|
||||
@@ -236,6 +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"
|
||||
|
||||
const router = useRouter()
|
||||
const user = inject('$user')
|
||||
@@ -274,6 +275,8 @@ onMounted(() => {
|
||||
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
|
||||
})
|
||||
@@ -19,7 +19,7 @@
|
||||
<router-link
|
||||
v-if="user.data?.is_moderator"
|
||||
:to="{
|
||||
name: 'BatchCreation',
|
||||
name: 'BatchForm',
|
||||
params: { batchName: 'new' },
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'CreateCourse',
|
||||
name: 'CourseForm',
|
||||
params: {
|
||||
courseName: 'new',
|
||||
},
|
||||
|
||||
@@ -82,6 +82,7 @@ import EditorJS from '@editorjs/editorjs'
|
||||
import LessonPlugins from '@/components/LessonPlugins.vue'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { updateDocumentTitle, createToast, getEditorTools } from '@/utils'
|
||||
import { capture } from '@/telemetry'
|
||||
|
||||
const editor = ref(null)
|
||||
const instructorEditor = ref(null)
|
||||
@@ -108,6 +109,7 @@ onMounted(() => {
|
||||
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
capture('lesson_form_opened')
|
||||
editor.value = renderEditor('content')
|
||||
instructorEditor.value = renderEditor('instructor-notes')
|
||||
})
|
||||
@@ -360,6 +362,7 @@ const createNewLesson = () => {
|
||||
{ lesson: data.name },
|
||||
{
|
||||
onSuccess() {
|
||||
capture('lesson_created')
|
||||
showToast('Success', 'Lesson created successfully', 'check')
|
||||
lessonDetails.reload()
|
||||
},
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -97,8 +97,8 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/courses/:courseName/edit',
|
||||
name: 'CreateCourse',
|
||||
component: () => 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,
|
||||
},
|
||||
{
|
||||
|
||||
98
frontend/src/telemetry.ts
Normal file
98
frontend/src/telemetry.ts
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
16
lms/lms/telemetry.py
Normal file
16
lms/lms/telemetry.py
Normal file
@@ -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"),
|
||||
}
|
||||
@@ -28,7 +28,6 @@
|
||||
"cypress-file-upload": "^5.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"posthog-js": "^1.154.4",
|
||||
"pre-commit": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user