Merge pull request #1507 from frappe/develop

chore: merge 'develop' into 'main'
This commit is contained in:
Jannat Patel
2025-05-16 12:01:07 +05:30
committed by GitHub
7 changed files with 133 additions and 89 deletions

View File

@@ -16,9 +16,9 @@ cd frappe-bench
# Use containers instead of localhost
bench set-mariadb-host mariadb
bench set-redis-cache-host redis:6379
bench set-redis-queue-host redis:6379
bench set-redis-socketio-host redis:6379
bench set-redis-cache-host redis://redis:6379
bench set-redis-queue-host redis://redis:6379
bench set-redis-socketio-host redis://redis:6379
# Remove redis, watch from Procfile
sed -i '/redis/d' ./Procfile

View File

@@ -148,7 +148,7 @@ function submitEvaluation(close) {
unavailabilityMessage = false
}
toast.warn(__('Evaluator is unavailable'))
toast.warning(__('Evaluator is unavailable'))
},
})
}

View File

@@ -494,7 +494,7 @@ const getAnswers = () => {
const checkAnswer = () => {
let answers = getAnswers()
if (!answers.length) {
toast.warn(__('Please select an option'))
toast.warning(__('Please select an option'))
return
}

View File

@@ -112,7 +112,6 @@ const { brand } = sessionStore()
const courseCount = ref(0)
onMounted(() => {
identifyUserPersona()
setFiltersFromQuery()
updateCourses()
getCourseCount()
@@ -177,6 +176,7 @@ const getCourseCount = () => {
doctype: 'LMS Course',
}).then((data) => {
courseCount.value = data
identifyUserPersona()
})
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="flex h-screen overflow-hidden sm:bg-gray-50">
<div class="relative h-full z-10 mx-auto pt-8 sm:w-max sm:pt-32">
<div class="relative h-full z-10 mx-auto sm:w-max pt-40">
<div class="mx-auto flex items-center justify-center space-x-2">
<LMSLogo class="size-7" />
<span
@@ -18,7 +18,7 @@
<div class="mb-5">
<div class="text-sm text-gray-700 mb-2">
{{ __('What is your main use case for Frappe Learning?') }}
{{ __('What is your use case for Frappe Learning?') }}
</div>
<FormControl
v-model="persona.useCase"
@@ -29,12 +29,12 @@
<div class="mb-5">
<div class="text-sm text-gray-700 mb-2">
{{ __('How many students are you planning to teach?') }}
{{ __('What best describes your role?') }}
</div>
<FormControl
v-model="persona.noOfStudents"
v-model="persona.role"
type="select"
:options="noOfStudentsOptions"
:options="roleOptions"
/>
</div>
@@ -65,7 +65,7 @@ const router = useRouter()
const { brand } = sessionStore()
const persona = reactive({
noOfStudents: null,
role: null,
useCase: null,
})
@@ -97,6 +97,24 @@ const skipPersonaForm = () => {
})
}
const roleOptions = computed(() => {
const options = [
'Trainer / Instructor',
'Freelancer / Consultant',
'HR / L&D Professional',
'School / University Admin',
'Software Developer',
'Community Manager',
'Business Owner / Team Lead',
'Other',
]
return options.map((option) => ({
label: option,
value: option,
}))
})
const noOfStudentsOptions = computed(() => {
const options = [
'Less than 50',

View File

@@ -2,10 +2,10 @@
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs v-if="submisisonDetails.doc" :items="breadcrumbs" />
<Breadcrumbs v-if="submissionDetails.doc" :items="breadcrumbs" />
<div class="space-x-2">
<Badge
v-if="submisisonDetails.isDirty"
v-if="submissionDetails.isDirty"
:label="__('Not Saved')"
variant="subtle"
theme="orange"
@@ -15,19 +15,19 @@
</Button>
</div>
</header>
<div v-if="submisisonDetails.doc" class="w-1/2 mx-auto py-5 space-y-5">
<div class="text-xl font-semibold text-ink-gray-9">
{{ submisisonDetails.doc.member_name }}
<div v-if="submissionDetails.doc" class="w-2/3 border-x mx-auto py-5">
<div class="text-xl px-10 font-semibold text-ink-gray-9 mb-5">
{{ submissionDetails.doc.member_name }}
</div>
<div class="space-y-4 border p-5 rounded-md">
<div class="space-y-4 border-b pb-5 px-10">
<div class="grid grid-cols-2 gap-5">
<FormControl
v-model="submisisonDetails.doc.quiz_title"
v-model="submissionDetails.doc.quiz_title"
:label="__('Quiz')"
:disabled="true"
/>
<FormControl
v-model="submisisonDetails.doc.member_name"
v-model="submissionDetails.doc.member_name"
:label="__('Member')"
:disabled="true"
/>
@@ -35,39 +35,39 @@
<div class="grid grid-cols-2 gap-5">
<FormControl
v-model="submisisonDetails.doc.score"
v-model="submissionDetails.doc.score"
:label="__('Score')"
:disabled="true"
/>
<FormControl
v-model="submisisonDetails.doc.percentage"
v-model="submissionDetails.doc.percentage"
:label="__('Percentage')"
:disabled="true"
/>
</div>
</div>
<div
v-for="(row, index) in submisisonDetails.doc.result"
class="border p-5 rounded-md space-y-4"
>
<div class="flex items-start space-x-1 font-semibold text-ink-gray-9">
<!-- <span>
{{ index + 1 }}.
</span> -->
<span class="leading-5" v-html="row.question"> </span>
</div>
<div class="leading-5 text-ink-gray-7 space-x-1">
<span> {{ __('Answer') }}: </span>
<span v-html="row.answer"></span>
</div>
<div class="grid grid-cols-2 gap-5">
<FormControl v-model="row.marks" :label="__('Marks')" />
<FormControl
v-model="row.marks_out_of"
:label="__('Marks out of')"
:disabled="true"
/>
<div class="divide-y">
<div
v-for="(row, index) in submissionDetails.doc.result"
class="py-5 px-10 space-y-4"
>
<div class="text-ink-gray-9">
<span class="font-semibold"> {{ __('Question') }}: </span>
<span class="leading-5" v-html="row.question"> </span>
</div>
<div class="">
<span class="font-semibold"> {{ __('Answer') }} </span>
<span class="leading-5" v-html="row.answer"></span>
</div>
<div class="grid grid-cols-2 gap-5">
<FormControl v-model="row.marks" :label="__('Marks')" />
<FormControl
v-model="row.marks_out_of"
:label="__('Marks out of')"
:disabled="true"
/>
</div>
</div>
</div>
</div>
@@ -119,7 +119,7 @@ const props = defineProps({
},
})
const submisisonDetails = createDocumentResource({
const submissionDetails = createDocumentResource({
doctype: 'LMS Quiz Submission',
name: props.submission,
auto: true,
@@ -132,18 +132,18 @@ const breadcrumbs = computed(() => {
route: {
name: 'QuizSubmissionList',
params: {
quizID: submisisonDetails.doc.quiz,
quizID: submissionDetails.doc.quiz,
},
},
},
{
label: submisisonDetails.doc.quiz_title,
label: submissionDetails.doc.quiz_title,
},
]
})
const saveSubmission = () => {
submisisonDetails.save.submit(
submissionDetails.save.submit(
{},
{
onError(err) {
@@ -155,7 +155,7 @@ const saveSubmission = () => {
usePageMeta(() => {
return {
title: `${submisisonDetails.doc.quiz_title}`,
title: `${submissionDetails.doc?.quiz_title}`,
icon: brand.favicon,
}
})

View File

@@ -96,9 +96,7 @@ def set_total_marks(questions):
@frappe.whitelist()
def quiz_summary(quiz, results):
score = 0
results = results and json.loads(results)
is_open_ended = False
percentage = 0
quiz_details = frappe.db.get_value(
@@ -108,7 +106,32 @@ def quiz_summary(quiz, results):
as_dict=1,
)
data = process_results(results, quiz)
results = data["results"]
score = data["score"]
is_open_ended = data["is_open_ended"]
score_out_of = quiz_details.total_marks
percentage = (score / score_out_of) * 100 if score_out_of else 0
submission = create_submission(
quiz, results, score_out_of, quiz_details.passing_percentage
)
save_progress_after_quiz(quiz_details, percentage)
return {
"score": score,
"score_out_of": score_out_of,
"submission": submission.name,
"pass": percentage == quiz_details.passing_percentage,
"percentage": percentage,
"is_open_ended": is_open_ended,
}
def process_results(results, quiz):
score = 0
is_open_ended = False
for result in results:
question_details = frappe.db.get_value(
@@ -123,55 +146,28 @@ def quiz_summary(quiz, results):
result["marks_out_of"] = question_details.marks
if question_details.type != "Open Ended":
correct = result["is_correct"][0]
for point in result["is_correct"]:
correct = correct and point
result["is_correct"] = correct
if len(result["is_correct"]) > 0:
correct = result["is_correct"][0]
for point in result["is_correct"]:
correct = correct and point
result["is_correct"] = correct
else:
result["is_correct"] = 0
marks = question_details.marks if correct else 0
result["marks"] = marks
score += marks
else:
result["is_correct"] = 0
is_open_ended = True
percentage = (score / score_out_of) * 100
result["answer"] = re.sub(
r'<img[^>]*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, result["answer"]
)
submission = frappe.new_doc("LMS Quiz Submission")
# Score and percentage are calculated by the controller function
submission.update(
{
"doctype": "LMS Quiz Submission",
"quiz": quiz,
"result": results,
"score": 0,
"score_out_of": score_out_of,
"member": frappe.session.user,
"percentage": 0,
"passing_percentage": quiz_details.passing_percentage,
}
)
submission.save(ignore_permissions=True)
if (
percentage >= quiz_details.passing_percentage
and quiz_details.lesson
and quiz_details.course
):
save_progress(quiz_details.lesson, quiz_details.course)
elif not quiz_details.passing_percentage:
save_progress(quiz_details.lesson, quiz_details.course)
result["is_correct"] = 0
result["answer"] = re.sub(
r'<img[^>]*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, result["answer"]
)
return {
"results": results,
"score": score,
"score_out_of": score_out_of,
"submission": submission.name,
"pass": percentage == quiz_details.passing_percentage,
"percentage": percentage,
"is_open_ended": is_open_ended,
}
@@ -219,6 +215,36 @@ def get_corrupted_image_msg():
return _("Image: Corrupted Data Stream")
def create_submission(quiz, results, score_out_of, passing_percentage):
submission = frappe.new_doc("LMS Quiz Submission")
# Score and percentage are calculated by the controller function
submission.update(
{
"doctype": "LMS Quiz Submission",
"quiz": quiz,
"result": results,
"score": 0,
"score_out_of": score_out_of,
"member": frappe.session.user,
"percentage": 0,
"passing_percentage": passing_percentage,
}
)
submission.save(ignore_permissions=True)
return submission
def save_progress_after_quiz(quiz_details, percentage):
if (
percentage >= quiz_details.passing_percentage
and quiz_details.lesson
and quiz_details.course
):
save_progress(quiz_details.lesson, quiz_details.course)
elif not quiz_details.passing_percentage:
save_progress(quiz_details.lesson, quiz_details.course)
@frappe.whitelist()
def get_question_details(question):
if frappe.db.exists("LMS Quiz Question", question):