chore: simplify grading for self paced course (#326)

* updated the grading flow for self paced course

* updated some logic and translations

* fix bug with newsfeed not opening for students and fix more bugs

* remove console

* add blog template route

* add doc page

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Em_Dev
2024-05-25 05:50:14 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 44087c458e
commit fc0b91a493
23 changed files with 144 additions and 176 deletions
@@ -4,6 +4,8 @@
import TextareaQuestion from '$lib/components/Question/TextareaQuestion/index.svelte';
import { QUESTION_TYPE } from '$lib/components/Question/constants';
import { getPropsForQuestion } from './functions';
import { course } from '$lib/components/Course/store';
import { COURSE_TYPE } from '$lib/utils/types';
export let questions = [];
export let questionnaireMetaData = {};
@@ -37,6 +39,7 @@
handleGrade={handleGrade(currentQuestion.id)}
{disableGrading}
disabled={true}
hideGrading={$course.type === COURSE_TYPE.SELF_PACED}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.question_type.id}
<CheckboxQuestion
@@ -57,6 +60,7 @@
handleGrade={handleGrade(currentQuestion.id)}
{disableGrading}
disabled={true}
hideGrading={$course.type === COURSE_TYPE.SELF_PACED}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.question_type.id}
<TextareaQuestion
@@ -77,6 +81,7 @@
handleGrade={handleGrade(currentQuestion.id)}
{disableGrading}
disabled={true}
hideGrading={$course.type === COURSE_TYPE.SELF_PACED}
/>
{/if}
{/each}
@@ -17,11 +17,7 @@
import { getPropsForQuestion, filterOutDeleted, wasCorrectAnswerSelected } from './functions';
import { formatAnswers, getGroupMemberId } from '$lib/components/Course/function';
import { submitExercise } from '$lib/utils/services/courses';
import {
fetchSubmission,
updateQuestionAnswer,
updateSubmission
} from '$lib/utils/services/submissions';
import { fetchSubmission } from '$lib/utils/services/submissions';
import { profile } from '$lib/utils/store/user';
import { currentOrg } from '$lib/utils/store/org';
import {
@@ -30,9 +26,8 @@
} from '$lib/utils/services/notification/notification';
import { lesson } from '../store/lessons';
import { browser } from '$app/environment';
import { useCompletion } from 'ai/svelte';
import { COURSE_TYPE } from '$lib/utils/types';
import GradingLoader from './GradingLoader.svelte';
import { t } from '$lib/utils/functions/translations';
export let preview: boolean = false;
export let exerciseId = '';
@@ -44,10 +39,7 @@
let hasSubmission = false;
let isLoadingAutoSavedData = false;
let alreadyCheckedAutoSavedData = false;
let gradedComment = true;
let isLoading = false;
let submissionResponse;
let submissionId;
function handleStart() {
$questionnaireMetaData.currentQuestionIndex += 1;
@@ -88,89 +80,7 @@
});
};
const { input, handleSubmit, completion } = useCompletion({
api: '/api/completion/gradingprompt',
onFinish: async () => {
try {
const responseData = $completion.replace('```json', '').replace('```', '');
let aiResponses = [];
try {
// Parse the modified response data as JSON
aiResponses = JSON.parse(responseData);
} catch (error) {
console.error('Error parsing AI response', error);
}
$questionnaire.questions.forEach((question) => {
const { id, points, question_type_id } = question;
let questionAnswer = submissionResponse?.res?.data.find(
(answer) => answer.question_id === id
);
console.log('this is the question answer', questionAnswer);
if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
const answer = $questionnaireMetaData.answers[question.name];
let score = points / answer.length;
$questionnaireMetaData.grades = {
...$questionnaireMetaData.grades,
[id]: score
};
updateQuestionAnswer({ point: score }, { id: questionAnswer?.id });
} else if (aiResponses.length) {
const graded = aiResponses.find((res) => res.id === id);
$questionnaireMetaData.grades = {
...$questionnaireMetaData.grades,
[id]: graded.score
};
updateQuestionAnswer({ point: graded.score }, { id: questionAnswer?.id });
}
});
$questionnaireMetaData.finalTotalGrade = Object.values(
$questionnaireMetaData.grades
).reduce((acc, answer): number => {
return acc + answer;
}, 0);
updateSubmission({
id: submissionId,
status_id: STATUS.GRADED,
total: $questionnaireMetaData.finalTotalGrade
});
$questionnaireMetaData.status = STATUS.GRADED;
console.log('grade', $questionnaireMetaData.finalTotalGrade);
} catch (error) {
console.error('Error', error);
} finally {
isLoading = false;
}
}
});
function automaticGrading(answers, questions) {
const paragraphAiInput = questions
.filter((q) => q.question_type_id === QUESTION_TYPE.TEXTAREA)
.map((q) => {
const answer = answers?.answers[q.name]; // { open_answer: '' }
return {
id: q.id,
question: q.title,
answer: answer,
point: q.points
};
});
$input = JSON.stringify(paragraphAiInput);
handleSubmit({ preventDefault: () => {} });
}
async function onSubmit(id, value) {
if ($course.type === COURSE_TYPE.SELF_PACED) {
isLoading = true;
}
const { answers } = $questionnaireMetaData;
const { questions } = $questionnaire;
const prevAnswer = answers[id] || [];
@@ -220,11 +130,6 @@
);
if (response) {
submissionResponse = response;
submissionId = submissionResponse.submission[0]?.id;
}
if ($course.type === COURSE_TYPE.SELF_PACED) {
automaticGrading($questionnaireMetaData, $questionnaire.questions);
}
notifyEducator();
@@ -316,10 +221,6 @@
alreadyCheckedAutoSavedData = true;
}
const toggleComments = () => {
gradedComment = !gradedComment;
};
$: browser && !alreadyCheckedAutoSavedData && getAutoSavedData();
// Reactive code
@@ -406,42 +307,46 @@
</div>
</RoleBasedSecurity>
{:else if $questionnaireMetaData.isFinished}
{#if isLoading}
<GradingLoader />
{:else if !isLoading && !isLoadingAutoSavedData}
{#if !isLoadingAutoSavedData}
<div class="flex items-center justify-between">
<div class="flex flex-col lg:flex-row items-start lg:items-center lg:space-x-4 w-full">
<h2 class="text-xl font-normal">{$questionnaire.title}</h2>
{#if STATUS.GRADED === $questionnaireMetaData.status}
<span
class="status-text bg-green-700 text-white py-1 px-2 text-center"
title="Status: Pending Review"
title={$t('course.navItem.lessons.exercises.all_exercises.view_mode.status_graded')}
>
Graded
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.graded')}
</span>
{:else if $course.type === COURSE_TYPE.SELF_PACED}
<span
class="status-text bg-green-700 text-white py-1 px-2 text-center"
title={$t('course.navItem.lessons.exercises.all_exercises.view_mode.status_submitted')}
>
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.submitted')}
</span>
{:else}
<span
class="status-text bg-yellow-600 text-white py-1 px-2 text-center"
title="Status: Pending Review"
title={$t('course.navItem.lessons.exercises.all_exercises.view_mode.status_pending')}
>
Pending
{$t('course.navItem.lessons.exercises.all_exercises.view_mode.pending')}
</span>
{/if}
</div>
{#if STATUS.GRADED === $questionnaireMetaData.status}
{#if STATUS.GRADED === $questionnaireMetaData.status && $course.type !== COURSE_TYPE.SELF_PACED}
<span
class="p-6 border-2 border-gray-300 bg-[#F5F8FE] rounded-full h-10 w-10 flex items-center justify-center text-[#2751DA] text-sm font-semibold"
title="Status: Pending Review"
title={$t('course.navItem.lessons.exercises.all_exercises.view_mode.status_graded')}
>
{$questionnaireMetaData.finalTotalGrade}/{$questionnaireMetaData.totalPossibleGrade}
</span>
{/if}
</div>
{#if $questionnaireMetaData.status === STATUS.GRADED && $questionnaireMetaData.comment}
<div
class="flex items-center justify-between bg-primary-700 p-4 text-white font-semibold rounded-sm mt-3"
>
{#if $questionnaireMetaData.status === STATUS.GRADED && $questionnaireMetaData.comment && $course.type !== COURSE_TYPE.SELF_PACED}
<div class="flex items-center justify-between bg-primary-700 p-4 text-white rounded-sm mt-3">
<span> {$questionnaireMetaData.comment}</span>
</div>
{/if}
@@ -18,7 +18,6 @@
export let courseId = '';
export let path = '';
export let isExercisePage = false;
export let isStudent = false;
export let isFetching = false;
export let containerClass = '';
@@ -51,20 +50,16 @@
$: onCourseIdChange(courseId);
$: if (typeof $globalStore.isStudent !== 'boolean') {
$: {
const user = $group.people.find((person) => person.profile_id === $profile.id);
if (user) {
isStudent = user.role_id === 3;
$globalStore.isStudent = isStudent;
$globalStore.isStudent = user.role_id === 3;
filterPollsByStatus(isStudent);
filterPollsByStatus($globalStore.isStudent);
} else if ($profile.id && $group.people.length) {
// Current User doesn't have permission to view
isPermitted = false;
}
} else {
isStudent = $globalStore.isStudent;
}
</script>
@@ -97,7 +92,7 @@
</Modal>
<div class="root">
<Navigation {path} {isStudent} />
<Navigation {path} isStudent={$globalStore.isStudent} />
<div class="rightBar {containerClass}" class:isMobile={$isMobile}>
{#if isExercisePage}
<Confetti />
@@ -7,6 +7,8 @@
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
import { course } from '$lib/components/Course/store';
import { COURSE_TYPE } from '$lib/utils/types';
export let title = '';
export let index = 1;
@@ -17,6 +19,7 @@
export let onPrevious = () => {};
export let defaultValue = [];
export let disablePreviousButton = false;
export let isLast = false;
export let disabled = false;
export let isPreview = false;
export let nextButtonProps = {};
@@ -134,7 +137,9 @@
<PrimaryButton
variant={nextButtonProps.isActive ? VARIANTS.CONTAINED : VARIANTS.OUTLINED}
type="submit"
label={nextButtonProps.label}
label={isLast
? $t('course.navItem.lessons.exercises.all_exercises.finish')
: $t('course.navItem.lessons.exercises.all_exercises.next')}
isDisabled={nextButtonProps.isDisabled}
{name}
/>
@@ -7,6 +7,8 @@
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
import { COURSE_TYPE } from '$lib/utils/types';
import { course } from '$lib/components/Course/store';
export let title = '';
export let index = 1;
@@ -20,6 +22,7 @@
export let disabled = false;
export let isPreview = false;
export let nextButtonProps = {};
export let isLast = false;
export let grade: number;
export let gradeMax = 0;
export let disableGrading = false;
@@ -135,7 +138,9 @@
<PrimaryButton
variant={nextButtonProps.isActive ? VARIANTS.CONTAINED : VARIANTS.OUTLINED}
type="submit"
label={nextButtonProps.label}
label={isLast
? $t('course.navItem.lessons.exercises.all_exercises.finish')
: $t('course.navItem.lessons.exercises.all_exercises.next')}
isDisabled={nextButtonProps.isDisabled}
{name}
/>
@@ -7,6 +7,8 @@
import Grade from '$lib/components/Question/Grade.svelte';
import { t } from '$lib/utils/functions/translations';
import ReasonBox from '../ReasonBox.svelte';
import { course } from '$lib/components/Course/store';
import { COURSE_TYPE } from '$lib/utils/types';
export let title = '';
export let index = 1;
@@ -76,7 +78,9 @@
<div class="ml-4">
{#if disabled}
<div class="bg-gray-200 dark:bg-gray-500 py-3 px-5 rounded-md mb-3">
{defaultValue}
{defaultValue === ''
? $t('course.navItem.lessons.exercises.all_exercises.no_answer')
: defaultValue}
</div>
{#if gradeWithAI}
<ReasonBox {reason} {isLoading} {acceptGrade} {rejectGrade} />
@@ -95,7 +99,7 @@
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={handlePrevious}
label="Previous"
label={$t('course.navItem.lessons.exercises.all_exercises.previous')}
isDisabled={disablePreviousButton}
/>
<PrimaryButton
@@ -76,8 +76,12 @@
$: {
if (editorRef && lastVal !== value) {
editorRef.setContent(value);
text = editorRef.getContent({ format: 'text' });
try {
editorRef.setContent(value);
text = editorRef.getContent({ format: 'text' });
} catch (error) {
console.error('Error setting default value', error);
}
}
if (editorRef && disabled !== disablindCache) {
disablindCache = disabled;