fix: video watch time permission and other issues

This commit is contained in:
Jannat Patel
2026-02-20 13:08:27 +05:30
parent 133037698c
commit dfb7152aa3
5 changed files with 58 additions and 53 deletions

View File

@@ -3,7 +3,7 @@
v-model="show"
:options="{
size: '4xl',
title: __('Video Statistics for {0}').format(lessonTitle),
title: __('Video Statistics'),
}"
>
<template #body-content>
@@ -21,17 +21,22 @@
class="mt-2 mr-5 w-[25%]"
/> -->
</div>
<div v-if="currentTab" class="mt-4">
<div
v-if="currentTab"
:class="{
'mt-5': tabs.length > 1,
}"
>
<div class="grid grid-cols-[55%,40%] gap-5">
<div
class="space-y-5 border rounded-md p-2 pt-4 h-[70vh] overflow-y-auto"
class="space-y-5 border rounded-md p-2 pt-4 max-h-[70vh] overflow-y-auto"
>
<div class="grid grid-cols-[70%,30%] text-sm text-ink-gray-5">
<div class="px-4">
{{ __('Member') }}
</div>
<div class="text-center">
{{ __('Watch Time') }}
{{ __('Watch Time (mins)') }}
</div>
</div>
<div
@@ -68,15 +73,16 @@
</div>
</div>
<div class="space-y-5">
<NumberChart
class="border rounded-md"
:config="{
title: __('Average Watch Time'),
value: averageWatchTime,
}"
<NumberChartGraph
:title="__('Average Watch Time (mins)')"
:value="averageWatchTime"
/>
<div v-if="isPlyrSource">
<div class="video-player" :src="currentTab"></div>
<div
class="video-player"
:data-plyr-provider="provider"
:src="currentTab"
></div>
</div>
<VideoBlock v-else :file="currentTab" />
</div>
@@ -101,6 +107,7 @@ import {
import { computed, ref, watch } from 'vue'
import { enablePlyr, formatTimestamp } from '@/utils'
import VideoBlock from '@/components/VideoBlock.vue'
import NumberChartGraph from '@/components/NumberChartGraph.vue'
const show = defineModel<boolean | undefined>()
const currentTab = ref<string>('')
@@ -171,7 +178,7 @@ watch(show, () => {
const statisticsData = computed(() => {
const grouped = <Record<string, any[]>>{}
statistics.data.forEach((item: { source: string }) => {
statistics.data?.forEach((item: { source: string }) => {
if (!grouped[item.source]) {
grouped[item.source] = []
}

View File

@@ -12,7 +12,7 @@
</template>
</Button>
</Tooltip>
<Button v-if="canSeeStats()" @click="showVideoStats()">
<Button v-if="isAdmin" @click="showVideoStats()">
<template #icon>
<TrendingUp class="size-4 stroke-1.5" />
</template>
@@ -326,7 +326,7 @@
@updateNotes="updateNotes"
/>
<VideoStatistics
v-if="showStatsDialog"
v-if="isAdmin"
v-model="showStatsDialog"
:lessonName="lesson.data?.name"
:lessonTitle="lesson.data?.title"
@@ -524,7 +524,14 @@ const renderEditor = (holder, content) => {
const markProgress = () => {
if (user.data && lesson.data && !lesson.data.progress) {
progress.submit()
progress.submit(
{},
{
onError(err) {
console.error(err)
},
}
)
}
}
@@ -605,7 +612,6 @@ watch(
plyrSources.value = []
await nextTick()
resetLessonState(newChapterNumber, newLessonNumber)
startTimer()
updateNotes()
checkIfDiscussionsAllowed()
checkQuiz()
@@ -652,7 +658,7 @@ const getVideoDetails = () => {
const getPlyrSourceDetails = () => {
let details = []
plyrSources.value.forEach((source) => {
plyrSources.value.forEach(async (source) => {
if (source.currentTime == source.duration) markProgress()
let src = cleanYouTubeUrl(source.source)
details.push({
@@ -674,6 +680,7 @@ watch(
() => lesson.data,
async (data) => {
setupLesson(data)
startTimer()
getPlyrSource()
updateNotes()
if (data.icon == 'icon-youtube') clearInterval(timerInterval)
@@ -769,17 +776,19 @@ const checkIfDiscussionsAllowed = () => {
}
}
const isAdmin = computed(() => {
let isInstructor = lesson.data?.instructors?.includes(user.data?.name)
return user.data?.is_moderator || isInstructor
})
const allowEdit = () => {
if (window.read_only_mode) return false
if (user.data?.is_moderator) return true
if (lesson.data?.instructors?.includes(user.data?.name)) return true
if (isAdmin.value) return true
return false
}
const allowInstructorContent = () => {
if (user.data?.is_moderator) return true
if (lesson.data?.instructors?.includes(user.data?.name)) return true
return false
return isAdmin.value
}
const enrollment = createResource({
@@ -819,11 +828,6 @@ const toggleInlineMenu = async () => {
}
}
const canSeeStats = () => {
if (user.data?.is_moderator || user.data?.is_instructor) return true
return false
}
const showVideoStats = () => {
showStatsDialog.value = true
}

View File

@@ -8,7 +8,7 @@ from frappe.utils import ceil
class LMSEnrollment(Document):
def validate(self):
def before_insert(self):
self.validate_duplicate_enrollment()
self.validate_course_enrollment_eligibility()
self.validate_owner()
@@ -27,6 +27,7 @@ class LMSEnrollment(Document):
{
"course": self.course,
"member": self.member,
"name": ["!=", self.name],
},
)

View File

@@ -113,11 +113,12 @@
"read_only": 1
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-10-07 16:52:04.162521",
"modified_by": "Administrator",
"modified": "2026-02-19 16:31:23.401819",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Quiz Submission",
"owner": "Administrator",
@@ -133,21 +134,12 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "member_name",
"track_changes": 1
}
}

View File

@@ -102,7 +102,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-30 14:38:52.555010",
"modified": "2026-02-20 12:02:29.458645",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Video Watch Duration",
@@ -120,17 +120,6 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
@@ -154,6 +143,18 @@
"role": "Course Creator",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",