From 09bcf24bc3bb33c1e64bbecf7356fd1357dd003b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?manu=20=E2=80=A2=E2=80=A2?= Date: Tue, 21 Jan 2025 18:05:55 +0100 Subject: [PATCH] SAK-50907 Assignments: New option to replicate the self-report rubric to the grading rubric --- .../api/src/resources/assignment.properties | 1 + .../src/resources/assignment_ca.properties | 1 + .../src/resources/assignment_es.properties | 1 + assignment/tool/src/webapp/js/assignments.js | 86 +++++++++++++++++++ ...signments_instructor_grading_submission.vm | 11 +++ .../bundle/src/main/bundle/grader.properties | 1 + .../src/main/bundle/grader_ca.properties | 1 + .../src/main/bundle/grader_es.properties | 1 + .../packages/sakai-grader/src/SakaiGrader.js | 82 ++++++++++++++++++ .../src/sakai-grader-rendering-mixin.js | 10 +++ .../sakai-rubrics/src/SakaiRubricStudent.js | 4 +- 11 files changed, 198 insertions(+), 1 deletion(-) diff --git a/assignment/api/src/resources/assignment.properties b/assignment/api/src/resources/assignment.properties index 2fe7ee8dbcd1..cb38020035f1 100644 --- a/assignment/api/src/resources/assignment.properties +++ b/assignment/api/src/resources/assignment.properties @@ -1298,6 +1298,7 @@ youhavetorubric=This is a self-report assignment. You must use the following rub youhavetorubricone=This is a self-report assignment. You must use the following rubric to grade your work before submitting. You must complete at least one criterion of the rubric in order to submit the task. studentrubric=This is a self-report assignment. You can check the self-report of submitter before grading. autoevaluation=Autoevaluation: +copyAutoevaluation=Copy student's autoevaluation to the grading rubric instructor_grading=Instructor Grading: reviewrubric=This is a self-report assignment. You can check your self-report before grading. reviewrubricreport=This is your self-report. diff --git a/assignment/api/src/resources/assignment_ca.properties b/assignment/api/src/resources/assignment_ca.properties index 9783a7e45671..1c3c98ba93c3 100644 --- a/assignment/api/src/resources/assignment_ca.properties +++ b/assignment/api/src/resources/assignment_ca.properties @@ -1268,6 +1268,7 @@ youhavetorubric=Aquesta tasca \u00fas autoavaluable. Has d'autoavaluar la teva t youhavetorubricone=Aquesta tasca \u00fas autoavaluable. Has d'autoavaluar la teva tasca amb la r\u00FAbrica abans d'enviar-la. Has de completar almenys un criteri de la r\u00FAbrica per fer l'enviament. studentrubric=Aquesta tasca \u00E9s autoavaluable. Pots revisar l'autoavaluaci\u00F3 de l'estudiant abans de puntuar la tasca. autoevaluation=Autoavaluaci\u00f3: +copyAutoevaluation=Copiar l'autoavaluaci\u00f3 de l'estudiant a la r\u00fabrica de correcci\u00f3 instructor_grading=Correcci\u00f3 del profesor: reviewrubric=Aquesta tasca \u00E9s autoavaluable. Pot revisar la autoavaluaci\u00F3 abans d'enviar-la. reviewrubricreport=Aquesta \u00E9s la teva autoavaluaci\u00F3. diff --git a/assignment/api/src/resources/assignment_es.properties b/assignment/api/src/resources/assignment_es.properties index 017b87bf69ce..c08c5bb798e0 100644 --- a/assignment/api/src/resources/assignment_es.properties +++ b/assignment/api/src/resources/assignment_es.properties @@ -1298,6 +1298,7 @@ youhavetorubricone=Esta tarea es autoevaluable. Debes autoevaluar tu tarea con l studentrubric=Esta tarea es autoevaluable. Puedes revisar la autoevaluaci\u00f3n del estudiante antes de puntuar la tarea. autoevaluation=Autoevaluaci\u00f3n\: instructor_grading=Correcci\u00f3n del docente\: +copyAutoevaluation=Copiar la autoevaluaci\u00f3n del estudiante a la r\u00fabrica de evaluaci\u00f3n reviewrubric=Esta tarea es autoevaluable. Puedes revisar tu autoevaluaci\u00f3n antes de enviarla. reviewrubricreport=Esta es tu autoevaluaci\u00f3n. diff --git a/assignment/tool/src/webapp/js/assignments.js b/assignment/tool/src/webapp/js/assignments.js index e3bee6fc92f3..3bbb9333622d 100755 --- a/assignment/tool/src/webapp/js/assignments.js +++ b/assignment/tool/src/webapp/js/assignments.js @@ -1006,6 +1006,92 @@ ASN.disableTimesheetSetupSection = function() ASN.toggleAutoAnnounceEstimate(false); } +ASN.autocompleteRubricWithSelfReport = function(event) { + event.preventDefault(); + + const studentRubric = document.querySelector("sakai-rubric-student"); + if (!studentRubric) { + console.error("sakai-rubric-student element not found"); + return; + } + + const gradingRubric = document.querySelector("sakai-rubric-grading"); + if (!gradingRubric) { + console.error("sakai-rubric-grading element not found"); + return; + } + + const evaluationDetails = []; + const criterionRows = studentRubric.querySelectorAll(".criterion-row"); + criterionRows.forEach(row => { + const criterionId = row.id.split("_").pop(); + const selectedRating = row.querySelector(".rating-item.selected"); + if (selectedRating) { + const selectedRatingId = selectedRating.id.split("-").pop(); + evaluationDetails.push({ criterionId, selectedRatingId }); + } + }); + + if (evaluationDetails.length === 0) { + console.error("No evaluation details found in sakai-rubric-student"); + return; + } + + const selectedItems = gradingRubric.querySelectorAll(".rating-item.selected"); + selectedItems.forEach(item => { + item.click(); + }); + + let totalPoints = 0; + const decimalSeparator = 1.1.toLocaleString(portal.locale).substring(1, 2); + evaluationDetails.forEach(detail => { + const criterionRow = gradingRubric.querySelector(`#criterion_row_${detail.criterionId}`); + if (criterionRow) { + const ratingItem = criterionRow.querySelector(`.rating-item[data-rating-id="${detail.selectedRatingId}"]`); + if (ratingItem) { + ratingItem.click(); + const pointsElement = ratingItem.querySelector(".points"); + const pointsText = pointsElement.textContent.trim(); + let points = parseFloat(pointsText.replace(",", ".")); + const pointsInParentheses = pointsElement.querySelector("b"); + if (pointsInParentheses) { + const pointsInParenthesesText = pointsInParentheses.textContent.trim().replace(/[()]/g, ''); + points = parseFloat(pointsInParenthesesText.replace(",", ".")); + } + totalPoints += points; + } + } + }); + + const formattedTotalPoints = totalPoints.toLocaleString(portal.locale, { maximumFractionDigits: 2 }).replace(".", decimalSeparator); + const scoreGradeInput = document.querySelector("#grade"); + if (scoreGradeInput) { + scoreGradeInput.value = formattedTotalPoints; + scoreGradeInput.dispatchEvent(new Event("keypress", { bubbles: true })); + } + + const studentRubricAutocompleteConfirm = document.querySelector("#student-rubric-autocomplete-confirm"); + if (studentRubricAutocompleteConfirm) { + studentRubricAutocompleteConfirm.classList.remove("d-none"); + setTimeout(() => { + studentRubricAutocompleteConfirm.classList.add("d-none"); + }, 2000); + } +}; + +document.addEventListener('rubric-student-rendered', function() { + const studentRubricButton = document.getElementById("student-rubric-autocomplete-button"); + if (studentRubricButton) { + const rubricPreview = document.querySelector("sakai-rubric-criterion-preview"); + if (rubricPreview) { + console.debug("sakai-rubric-criterion-preview element found, hiding button"); + studentRubricButton.classList.add("d-none"); + } else { + studentRubricButton.classList.remove("d-none"); + } + } +}); + $(document).ready(() => { //peer-review and self-report $('body').on('rubric-association-loaded', e => { diff --git a/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_grading_submission.vm b/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_grading_submission.vm index b1b9c3842288..ca95b76e03e5 100644 --- a/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_grading_submission.vm +++ b/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_grading_submission.vm @@ -413,6 +413,17 @@ evaluated-item-owner-id="$submitterId" #end > +

+ $tlang.getString("studentrubric") +

+
#end diff --git a/webcomponents/bundle/src/main/bundle/grader.properties b/webcomponents/bundle/src/main/bundle/grader.properties index 27f1a322fb62..4360366af628 100644 --- a/webcomponents/bundle/src/main/bundle/grader.properties +++ b/webcomponents/bundle/src/main/bundle/grader.properties @@ -13,6 +13,7 @@ grading_rubric_tooltip=Grade this submission using a rubric rubric=Rubric autoevaluation=Autoevaluation: openAutoevaluation=Check Autoevaluation +copyAutoevaluation=Copy student's autoevaluation to the grading rubric studentrubric=This is a self-report assignment. You can check the self-report of submitter before grading. add_feedback_tooltip=Write, or record, some feedback for this student written_feedback_label=Write some feedback diff --git a/webcomponents/bundle/src/main/bundle/grader_ca.properties b/webcomponents/bundle/src/main/bundle/grader_ca.properties index 4298b529d87f..24283d4e2b43 100644 --- a/webcomponents/bundle/src/main/bundle/grader_ca.properties +++ b/webcomponents/bundle/src/main/bundle/grader_ca.properties @@ -5,6 +5,7 @@ grading_rubric_tooltip=Qualifica el lliurament usant una r\u00fabrica rubric=R\u00fabrica autoevaluation=Autoavaluaci\u00f3: openAutoevaluation=Revisa l'autoavaluaci\u00f3 +copyAutoevaluation=Copiar l'autoavaluaci\u00f3 de l'estudiant a la r\u00fabrica de correcci\u00f3 studentrubric=Aquesta tasca \u00E9s autoavaluable. Pots revisar l'autoavaluaci\u00F3 de l'estudiant abans de puntuar la tasca. add_feedback_tooltip=Escriu, o enregistra, comentaris per a aquest estudiant written_feedback_label=Escriu comentaris diff --git a/webcomponents/bundle/src/main/bundle/grader_es.properties b/webcomponents/bundle/src/main/bundle/grader_es.properties index 6987bb6f2fb1..bfa08cc83fcd 100644 --- a/webcomponents/bundle/src/main/bundle/grader_es.properties +++ b/webcomponents/bundle/src/main/bundle/grader_es.properties @@ -13,6 +13,7 @@ grading_rubric_tooltip=Evaluar este env\u00edo utilizando una r\u00fabrica rubric=R\u00fabrica autoevaluation=Autoevaluaci\u00f3n\: openAutoevaluation=Revisa la autoevaluaci\u00f3n +copyAutoevaluation=Copiar la autoevaluaci\u00f3n del estudiante a la r\u00fabrica de evaluaci\u00f3n studentrubric=Esta tarea es autoevaluable. Puedes revisar la autoevaluaci\u00f3n del estudiante antes de puntuar la tarea. add_feedback_tooltip=Escribir o grabar alg\u00fan tipo de comentario o feedback para este/esta estudiante written_feedback_label=Escribir comentarios/feedback diff --git a/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/SakaiGrader.js b/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/SakaiGrader.js index 8f01e03573c5..0474243403f1 100644 --- a/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/SakaiGrader.js +++ b/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/SakaiGrader.js @@ -335,6 +335,17 @@ export class SakaiGrader extends graderRenderingMixin(gradableDataMixin(SakaiEle _toggleStudentRubric() { this._rubricStudentShowing = !this._rubricStudentShowing; + + const studentRubricButton = document.getElementById("student-rubric-autocomplete-button"); + if (studentRubricButton) { + const rubricPreview = document.querySelector("sakai-rubric-criterion-preview"); + if (rubricPreview) { + console.debug("sakai-rubric-criterion-preview element found, hiding button"); + studentRubricButton.classList.add("d-none"); + } else { + studentRubricButton.classList.remove("d-none"); + } + } } _togglePrivateNotesEditor() { @@ -427,6 +438,77 @@ export class SakaiGrader extends graderRenderingMixin(gradableDataMixin(SakaiEle this._addRubricParam(e, "criterion-comment"); } + _autocompleteRubricWithSelfReport() { + const studentRubric = document.querySelector("sakai-rubric-student"); + if (!studentRubric) { + console.error("sakai-rubric-student element not found"); + return; + } + + const gradingRubric = document.querySelector("sakai-rubric-grading"); + if (!gradingRubric) { + console.error("sakai-rubric-grading element not found"); + return; + } + + const evaluationDetails = []; + const criterionRows = studentRubric.querySelectorAll(".criterion-row"); + criterionRows.forEach(row => { + const criterionId = row.id.split("_").pop(); + const selectedRating = row.querySelector(".rating-item.selected"); + if (selectedRating) { + const selectedRatingId = selectedRating.id.split("-").pop(); + evaluationDetails.push({ criterionId, selectedRatingId }); + } + }); + + if (evaluationDetails.length === 0) { + console.error("No evaluation details found in sakai-rubric-student"); + return; + } + + const selectedItems = gradingRubric.querySelectorAll(".rating-item.selected"); + selectedItems.forEach(item => { + item.click(); + }); + + let totalPoints = 0; + const decimalSeparator = 1.1.toLocaleString(portal.locale).substring(1, 2); + evaluationDetails.forEach(detail => { + const criterionRow = gradingRubric.querySelector(`#criterion_row_${detail.criterionId}`); + if (criterionRow) { + const ratingItem = criterionRow.querySelector(`.rating-item[data-rating-id="${detail.selectedRatingId}"]`); + if (ratingItem) { + ratingItem.click(); + const pointsElement = ratingItem.querySelector(".points"); + const pointsText = pointsElement.textContent.trim(); + let points = parseFloat(pointsText.replace(",", ".")); + const pointsInParentheses = pointsElement.querySelector("b"); + if (pointsInParentheses) { + const pointsInParenthesesText = pointsInParentheses.textContent.trim().replace(/[()]/g, ""); + points = parseFloat(pointsInParenthesesText.replace(",", ".")); + } + totalPoints += points; + } + } + }); + + const formattedTotalPoints = totalPoints.toLocaleString(portal.locale, { maximumFractionDigits: 2 }).replace(".", decimalSeparator); + const scoreGradeInput = document.querySelector("#grade"); + if (scoreGradeInput) { + scoreGradeInput.value = formattedTotalPoints; + scoreGradeInput.dispatchEvent(new Event("keypress", { bubbles: true })); + } + + const studentRubricAutocompleteConfirm = document.querySelector("#student-rubric-autocomplete-confirm"); + if (studentRubricAutocompleteConfirm) { + studentRubricAutocompleteConfirm.classList.remove("d-none"); + setTimeout(() => { + studentRubricAutocompleteConfirm.classList.add("d-none"); + }, 2000); + } + } + /** * Bundle up and all the needed stuff, like the grade, rubric, instructor comments and attachments. */ diff --git a/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/sakai-grader-rendering-mixin.js b/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/sakai-grader-rendering-mixin.js index 377d98fb3cea..1c18d5c5ab5f 100644 --- a/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/sakai-grader-rendering-mixin.js +++ b/webcomponents/tool/src/main/frontend/packages/sakai-grader/src/sakai-grader-rendering-mixin.js @@ -489,6 +489,16 @@ export const graderRenderingMixin = Base => class extends Base {

${this._i18n.autoevaluation}

${this._i18n.studentrubric}

+
+ +
${!this.dynamic ? html`

@@ -138,6 +138,8 @@ export class SakaiRubricStudent extends rubricsApiMixin(RubricsElement) {

`; + this.dispatchEvent(new CustomEvent("rubric-student-rendered", { bubbles: true, composed: true })); + return renderedContent; } _setRubric() {