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 <best@classroomio.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
+19
-114
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user