MDL-74610 quiz: multiple grades - setup for sections button
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -35,6 +35,7 @@ import {replaceNode} from 'core/templates';
|
||||
*/
|
||||
const SELECTORS = {
|
||||
'addGradeItemButton': '#mod_quiz-add_grade_item',
|
||||
'autoSetupButton': '#mod_quiz-grades_auto_setup',
|
||||
'editingPageContents': '#edit_grading_page-contents',
|
||||
'gradeItemList': 'table#mod_quiz-grade-item-list',
|
||||
'gradeItemSelect': 'select[data-slot-id]',
|
||||
@@ -123,6 +124,21 @@ const updateSlotGradeItem = (
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Call the Ajax service to setup one grade item for each quiz section.
|
||||
*
|
||||
* @param {Number} quizId id of the quiz to update.
|
||||
* @return {Promise} Promise that resolves to the context required to re-render the page.
|
||||
*/
|
||||
const autoSetupGradeItems = (
|
||||
quizId
|
||||
) => callServiceAndReturnRenderingData({
|
||||
methodname: 'mod_quiz_create_grade_item_per_section',
|
||||
args: {
|
||||
quizid: quizId
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.
|
||||
*
|
||||
@@ -377,6 +393,9 @@ const handleButtonClick = (e) => {
|
||||
if (e.target.closest(SELECTORS.addGradeItemButton)) {
|
||||
handleAddGradeItemClick(e);
|
||||
}
|
||||
if (e.target.closest(SELECTORS.autoSetupButton)) {
|
||||
handleAutoSetup(e);
|
||||
}
|
||||
if (e.target.closest(SELECTORS.resetAllButton)) {
|
||||
handleResetAllClick(e);
|
||||
}
|
||||
@@ -404,6 +423,26 @@ const handleAddGradeItemClick = (e) => {
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle clicks on the reset button - show a confirmation.
|
||||
*
|
||||
* @param {Event} e click event.
|
||||
*/
|
||||
const handleAutoSetup = (e) => {
|
||||
e.preventDefault();
|
||||
const pending = new Pending('setup-quiz-grade-items');
|
||||
|
||||
const quizId = e.target.dataset.quizId;
|
||||
|
||||
autoSetupGradeItems(quizId)
|
||||
.then(reRenderPage)
|
||||
.then(() => {
|
||||
pending.resolve();
|
||||
document.querySelector(SELECTORS.resetAllButton).focus();
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle clicks on the reset button - show a confirmation.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_quiz\external;
|
||||
|
||||
use coding_exception;
|
||||
use core_external\external_api;
|
||||
use core_external\external_description;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_multiple_structure;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_quiz\quiz_attempt;
|
||||
use mod_quiz\quiz_settings;
|
||||
use moodle_exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* For a quiz with no grade items yet, create a grade item for each section.
|
||||
*
|
||||
* And, assign the questions in each section to the corresponding grade item.
|
||||
*
|
||||
* The user must have the 'mod/quiz:manage' capability for the quiz.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class create_grade_item_per_section extends external_api {
|
||||
|
||||
/**
|
||||
* Declare the method parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'quizid' => new external_value(PARAM_INT, 'The quiz to update slots for.'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a quiz with no grade items yet, create a grade item for each section.
|
||||
*
|
||||
* And, assign the questions in each section to the corresponding grade item.
|
||||
*
|
||||
* The user must have the 'mod/quiz:manage' capability for the quiz.
|
||||
*
|
||||
* @param int $quizid the id of the quiz to setup grade items for.
|
||||
*/
|
||||
public static function execute(int $quizid): void {
|
||||
global $DB;
|
||||
|
||||
[
|
||||
'quizid' => $quizid,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'quizid' => $quizid,
|
||||
]);
|
||||
|
||||
// Check the request is valid.
|
||||
$quizobj = quiz_settings::create($quizid);
|
||||
require_capability('mod/quiz:manage', $quizobj->get_context());
|
||||
self::validate_context($quizobj->get_context());
|
||||
|
||||
$structure = $quizobj->get_structure();
|
||||
if ($structure->get_grade_items()) {
|
||||
throw new coding_exception('Cannot use create_grade_item_per_section for a quiz ' .
|
||||
'that already has grade items.');
|
||||
}
|
||||
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
$gradeitemsids = [];
|
||||
foreach ($structure->get_sections() as $section) {
|
||||
$gradeitem = new stdClass();
|
||||
$gradeitem->quizid = $quizid;
|
||||
$gradeitem->name = $section->heading;
|
||||
$structure->create_grade_item($gradeitem);
|
||||
$gradeitemsids[$section->id] = $gradeitem->id;
|
||||
}
|
||||
|
||||
foreach ($structure->get_slots() as $slot) {
|
||||
$structure->update_slot_grade_item($slot, $gradeitemsids[$slot->section->id]);
|
||||
}
|
||||
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the webservice response.
|
||||
*
|
||||
* @return external_description|null always null.
|
||||
*/
|
||||
public static function execute_returns(): ?external_description {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,7 @@ class edit_grading_page implements renderable, templatable {
|
||||
'gradeitems' => $gradeitems,
|
||||
'hasslots' => $this->structure->has_questions(),
|
||||
'sections' => array_values($sections),
|
||||
'hasmultiplesections' => count($sections) > 1,
|
||||
'nogradeitems' => ['message' => get_string('gradeitemsnoneyet', 'quiz')],
|
||||
'noslots' => ['message' => get_string('gradeitemnoslots', 'quiz')],
|
||||
];
|
||||
|
||||
@@ -55,8 +55,7 @@ class structure {
|
||||
protected $slotsinorder = [];
|
||||
|
||||
/**
|
||||
* @var stdClass[] currently a dummy. Holds data that will match the
|
||||
* quiz_sections, once it exists.
|
||||
* @var stdClass[] this quiz's data from the quiz_sections table. Each item has a ->lastslot field too.
|
||||
*/
|
||||
protected $sections = [];
|
||||
|
||||
@@ -1505,7 +1504,7 @@ class structure {
|
||||
*
|
||||
* The new grade item is added at the end of the order.
|
||||
*
|
||||
* @param stdClass $gradeitemdata must have property name.
|
||||
* @param stdClass $gradeitemdata must have property name - updated with the inserted data (sortorder and id).
|
||||
*/
|
||||
public function create_grade_item(stdClass $gradeitemdata): void {
|
||||
global $DB;
|
||||
|
||||
@@ -295,4 +295,12 @@ $functions = [
|
||||
'capabilities' => 'mod/quiz:manage',
|
||||
'ajax' => true,
|
||||
],
|
||||
|
||||
'mod_quiz_create_grade_item_per_section' => [
|
||||
'classname' => 'mod_quiz\external\create_grade_item_per_section',
|
||||
'description' => 'For a quiz with no grade items yet, create a grade item for each section, with the questions in that section assigned.',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'mod/quiz:manage',
|
||||
'ajax' => true,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -448,6 +448,7 @@ $string['gradeitemnewname'] = 'New name for grade item {$a}';
|
||||
$string['gradeitemnoneselected'] = '[none]';
|
||||
$string['gradeitemnoslots'] = 'No questions have been added to the quiz yet. Please add the questions to the quiz before setting up grading.';
|
||||
$string['gradeitems'] = 'Grade items';
|
||||
$string['gradeitemsautosetup'] = 'Setup a grade for each section';
|
||||
$string['gradeitemsetup'] = 'Quiz grading setup (advanced view)';
|
||||
$string['gradeitemsnoneyet'] = 'This quiz does not yet have any grade items defined, just a simple overall score will be used.';
|
||||
$string['gradeitemsremoveall'] = 'Reset advanced grading setup';
|
||||
|
||||
@@ -135,6 +135,12 @@
|
||||
<button type="button" class="btn btn-danger" id="mod_quiz-grades_reset_all"
|
||||
data-quiz-id="{{quizid}}">{{#str}} gradeitemsremoveall, quiz{{/str}}</button>
|
||||
{{/hasgradeitems}}
|
||||
{{^hasgradeitems}}
|
||||
{{#hasmultiplesections}}
|
||||
<button type="button" class="btn btn-secondary" id="mod_quiz-grades_auto_setup"
|
||||
data-quiz-id="{{quizid}}">{{#str}} gradeitemsautosetup, quiz{{/str}}</button>
|
||||
{{/hasmultiplesections}}
|
||||
{{/hasgradeitems}}
|
||||
</div>
|
||||
|
||||
<h3>{{#str}} gradeitemmarkscheme, quiz {{/str}}</h3>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
@mod @mod_quiz
|
||||
Feature: Attempt a quiz with multiple grades
|
||||
As a student
|
||||
In order to demonstrate multiple skills at once
|
||||
I need to be able to attempt quizzes with multiple grades setup
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username |
|
||||
| student |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student | C1 | student |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course |
|
||||
| quiz | Quiz 1 | C1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Navigation to, and display of, grading setup
|
||||
Given the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | Reading | Can you read this? |
|
||||
| Test questions | truefalse | Listening | Can you hear this? |
|
||||
And the following "mod_quiz > grade items" exist:
|
||||
| quiz | name |
|
||||
| Quiz 1 | Grade for reading |
|
||||
| Quiz 1 | Grade for listening |
|
||||
| Quiz 1 | Unused grade item |
|
||||
And quiz "Quiz 1" contains the following questions:
|
||||
| question | page | grade item |
|
||||
| Reading | 1 | Grade for reading |
|
||||
| Listening | 1 | Grade for listening |
|
||||
|
||||
When I am on the "Quiz 1" "quiz activity" page logged in as "student"
|
||||
And I click on "Attempt quiz" "button"
|
||||
And I set the field "True" in the "Can you read this?" "question" to "1"
|
||||
And I set the field "False" in the "Can you hear this?" "question" to "1"
|
||||
And I press "Finish attempt ..."
|
||||
And I press "Submit all and finish"
|
||||
And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue"
|
||||
|
||||
Then I should see "1.00 out of 1.00" in the "Grade for reading" "table_row"
|
||||
And I should see "0.00 out of 1.00" in the "Grade for listening" "table_row"
|
||||
And I should not see "Unused grade item"
|
||||
And I should see "1.00/2.00" in the "Marks" "table_row"
|
||||
# Funny order because 'Grade' also appears in other rows.
|
||||
And I should see "Grade" in the "50.00 out of 100.00" "table_row"
|
||||
@@ -148,3 +148,24 @@ Feature: Setup multiple grades for a quiz
|
||||
And I press "Reset advanced grading setup"
|
||||
And I click on "Cancel" "button" in the "Reset grading setup?" "dialogue"
|
||||
Then I should see "Intuition"
|
||||
|
||||
@javascript
|
||||
Scenario: Automatically set up one grade item per section
|
||||
Given quiz "Quiz 1" contains the following questions:
|
||||
| question | page |
|
||||
| Question A | 1 |
|
||||
| Question B | 1 |
|
||||
| Question C | 2 |
|
||||
And quiz "Quiz 1" contains the following sections:
|
||||
| heading | firstslot | shuffle |
|
||||
| Reading | 1 | 0 |
|
||||
| Listening | 3 | 0 |
|
||||
|
||||
When I am on the "Quiz 1" "mod_quiz > multiple grades setup" page logged in as teacher
|
||||
And I press "Setup a grade for each section"
|
||||
|
||||
Then "Reading" "table_row" should exist in the "mod_quiz-grade-item-list" "table"
|
||||
And "Listening" "table_row" should exist in the "mod_quiz-grade-item-list" "table"
|
||||
And the field "Question A" matches value "Reading"
|
||||
And the field "Question B" matches value "Reading"
|
||||
And the field "Question C" matches value "Listening"
|
||||
|
||||
+72
@@ -200,4 +200,76 @@ final class grade_items_test extends externallib_advanced_testcase {
|
||||
|
||||
return $quizobj;
|
||||
}
|
||||
|
||||
public function test_create_grade_item_per_section_works(): void {
|
||||
global $SITE;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create a quiz with no grade items yet, but two sections.
|
||||
/** @var \mod_quiz_generator $quizgenerator */
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quiz = $quizgenerator->create_instance(['course' => $SITE->id]);
|
||||
|
||||
// Create three questions.
|
||||
/** @var core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$saq1 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
$saq2 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
$saq3 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
|
||||
// Add them to the quiz.
|
||||
quiz_add_quiz_question($saq1->id, $quiz, 1, 1);
|
||||
quiz_add_quiz_question($saq2->id, $quiz, 1, 1);
|
||||
quiz_add_quiz_question($saq3->id, $quiz, 2, 1);
|
||||
|
||||
// Create two sections.
|
||||
$quizobj = quiz_settings::create($quiz->id);
|
||||
$structure = $quizobj->get_structure();
|
||||
$defaultsection = array_values($structure->get_sections())[0];
|
||||
$structure->set_section_heading($defaultsection->id, 'Listening');
|
||||
$structure->add_section_heading(2, 'Reading');
|
||||
|
||||
// Call the method we are testing.
|
||||
create_grade_item_per_section::execute($quizobj->get_quizid());
|
||||
|
||||
// Verify.
|
||||
$structure = $quizobj->get_structure();
|
||||
|
||||
$gradeitems = array_values($structure->get_grade_items());
|
||||
$this->assertCount(2, $gradeitems);
|
||||
$this->assertEquals('Listening', $gradeitems[0]->name);
|
||||
$this->assertEquals(1, $gradeitems[0]->sortorder);
|
||||
$this->assertEquals('Reading', $gradeitems[1]->name);
|
||||
$this->assertEquals(2, $gradeitems[1]->sortorder);
|
||||
|
||||
$this->assertEquals($gradeitems[0]->id, $structure->get_slot_by_number(1)->quizgradeitemid);
|
||||
$this->assertEquals($gradeitems[0]->id, $structure->get_slot_by_number(2)->quizgradeitemid);
|
||||
$this->assertEquals($gradeitems[1]->id, $structure->get_slot_by_number(3)->quizgradeitemid);
|
||||
}
|
||||
|
||||
public function test_create_grade_item_per_section_service_checks_permissions(): void {
|
||||
global $SITE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a quiz.
|
||||
/** @var \mod_quiz_generator $quizgenerator */
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quiz = $quizgenerator->create_instance(['course' => $SITE->id]);
|
||||
|
||||
$unprivilegeduser = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($unprivilegeduser);
|
||||
|
||||
$this->expectException(required_capability_exception::class);
|
||||
create_grade_item_per_section::execute($quiz->id);
|
||||
}
|
||||
|
||||
public function test_cant_create_grade_item_per_section_if_grade_items_already_exist(): void {
|
||||
$quizobj = $this->create_quiz_with_two_grade_items();
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
create_grade_item_per_section::execute($quizobj->get_quizid());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user