diff --git a/course/format/tests/local/overview/overviewfactory_test.php b/course/format/tests/local/overview/overviewfactory_test.php index bf94583a241..6083cf4df07 100644 --- a/course/format/tests/local/overview/overviewfactory_test.php +++ b/course/format/tests/local/overview/overviewfactory_test.php @@ -145,7 +145,7 @@ final class overviewfactory_test extends \advanced_testcase { ], 'workshop' => [ 'resourcetype' => 'workshop', - 'expected' => resourceoverview::class, + 'expected' => \mod_workshop\courseformat\overview::class, ], ]; } diff --git a/course/format/tests/output/local/overview/missingoverviewnotice_test.php b/course/format/tests/output/local/overview/missingoverviewnotice_test.php index 241e33c3cdc..ff7ca3bc028 100644 --- a/course/format/tests/output/local/overview/missingoverviewnotice_test.php +++ b/course/format/tests/output/local/overview/missingoverviewnotice_test.php @@ -84,7 +84,7 @@ final class missingoverviewnotice_test extends \advanced_testcase { 'scorm' => ['modname' => 'scorm', 'expectempty' => false], 'url' => ['modname' => 'url', 'expectempty' => false], 'wiki' => ['modname' => 'wiki', 'expectempty' => false], - 'workshop' => ['modname' => 'workshop', 'expectempty' => false], + 'workshop' => ['modname' => 'workshop', 'expectempty' => true], ]; } } diff --git a/mod/workshop/classes/courseformat/overview.php b/mod/workshop/classes/courseformat/overview.php new file mode 100644 index 00000000000..29378bd074e --- /dev/null +++ b/mod/workshop/classes/courseformat/overview.php @@ -0,0 +1,236 @@ +. + +namespace mod_workshop\courseformat; + +use cm_info; +use core_courseformat\local\overview\overviewitem; +use core\output\action_link; +use core\output\local\properties\text_align; +use core\output\local\properties\button; +use core\url; +use stdClass; +use workshop; + +/** + * Workshop overview integration. + * + * @package mod_workshop + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview extends \core_courseformat\activityoverviewbase { + /** @var workshop $workshop the workshop instance. */ + private workshop $workshop; + + /** @var stdClass $activephase the active phase. */ + private stdClass $activephase; + + /** + * Constructor. + * + * @param cm_info $cm the course module instance. + */ + public function __construct( + cm_info $cm, + ) { + global $CFG, $USER; + + require_once($CFG->dirroot . '/mod/workshop/locallib.php'); + + parent::__construct($cm); + $this->workshop = new workshop( + $cm->get_instance_record(), + $cm, + $this->course, + $this->context, + ); + + $userplan = new \workshop_user_plan($this->workshop, $USER->id); + foreach ($userplan->phases as $phase) { + if ($phase->active) { + $this->activephase = $phase; + } + } + } + + #[\Override] + protected function get_grade_item_names(array $items): array { + if (count($items) != 2) { + return parent::get_grade_item_names($items); + } + $names = []; + foreach ($items as $item) { + $stridentifier = ($item->itemnumber == 0) ? 'overview_submission_grade' : 'overview_assessment_grade'; + $names[$item->id] = get_string($stridentifier, 'mod_workshop'); + } + return $names; + } + + #[\Override] + public function get_extra_overview_items(): array { + return [ + 'phase' => $this->get_extra_phase_overview(), + 'deadline' => $this->get_extra_deadline_overview(), + 'submissions' => $this->get_extra_submissions_overview(), + 'assessments' => $this->get_extra_assessments_overview(), + ]; + } + + /** + * Get the current phase overview item. + * + * @return overviewitem|null An overview item, or null if the user lacks the required capability. + */ + private function get_extra_phase_overview(): ?overviewitem { + $currentphasetitle = '-'; + if ($this->activephase) { + $currentphasetitle = $this->activephase->title; + } + return new overviewitem( + name: get_string('phase', 'workshop'), + value: $this->workshop->phase, + content: $currentphasetitle, + ); + } + + /** + * Retrieves an overview of the deadline for the workshop. + * + * @return overviewitem|null An overview item, or null if the current phase does not have a deadline. + */ + private function get_extra_deadline_overview(): ?overviewitem { + $deadline = match ((int)$this->workshop->phase) { + workshop::PHASE_SUBMISSION => $this->workshop->submissionend ?? 0, + workshop::PHASE_ASSESSMENT => $this->workshop->assessmentend ?? 0, + default => 0, + }; + + if (empty($deadline)) { + return new overviewitem( + name: get_string('deadline', 'workshop'), + value: null, + content: '-', + ); + } + + return new overviewitem( + name: get_string('deadline', 'workshop'), + value: (int) $deadline, + content: userdate($deadline), + ); + } + + /** + * Retrieves an overview of submissions for the workshop. + * + * @return overviewitem|null An overview item, or null if the user lacks the required capability. + */ + private function get_extra_submissions_overview(): ?overviewitem { + if (!has_capability('mod/workshop:viewallsubmissions', $this->cm->context)) { + return null; + } + + $submissions = $this->workshop->count_submissions(); + $total = $this->workshop->count_participants(); + + if (!$total) { + return new overviewitem( + name: get_string('submissions', 'workshop'), + value: 0, + content: '-', + textalign: text_align::CENTER, + ); + } + + $content = get_string( + 'count_of_total', + 'core', + ['count' => $submissions, 'total' => $total] + ); + + // If the current phase is submission, we can add a direct link. + if ($this->workshop->phase == workshop::PHASE_SUBMISSION) { + $content = new action_link( + url: new url( + '/mod/workshop/view.php', + ['id' => $this->cm->id], + 'workshop-viewlet-allsubmissions', + ), + text: $content, + attributes: ['class' => button::SECONDARY_OUTLINE->classes()], + ); + } + + return new overviewitem( + name: get_string('submissions', 'workshop'), + value: $submissions, + content: $content, + textalign: text_align::CENTER, + ); + } + + /** + * Retrieves an overview of assessments for the workshop. + * + * @return overviewitem|null An overview item, or null if the user lacks the required capability. + */ + private function get_extra_assessments_overview(): ?overviewitem { + global $USER; + + if (!has_capability('mod/workshop:viewallassessments', $this->cm->context)) { + return null; + } + + $assessments = $this->workshop->count_assessments(true); + $total = $this->workshop->count_assessments(false); + + if (!$total) { + return new overviewitem( + name: get_string('assessments', 'workshop'), + value: 0, + content: '-', + textalign: text_align::CENTER, + ); + } + + $content = get_string( + 'count_of_total', + 'core', + ['count' => $assessments, 'total' => $total] + ); + + // If the current phase is assessment, we can add a direct link. + if ($this->workshop->phase == workshop::PHASE_ASSESSMENT) { + $content = new action_link( + url: new url( + '/mod/workshop/view.php', + ['id' => $this->cm->id], + 'workshop-viewlet-gradereport', + ), + text: $content, + attributes: ['class' => button::SECONDARY_OUTLINE->classes()], + ); + } + + return new overviewitem( + name: get_string('assessments', 'workshop'), + value: $assessments, + content: $content, + textalign: text_align::CENTER, + ); + } +} diff --git a/mod/workshop/index.php b/mod/workshop/index.php index 281a526ebf2..e39f3f64ed3 100644 --- a/mod/workshop/index.php +++ b/mod/workshop/index.php @@ -24,68 +24,8 @@ */ require(__DIR__.'/../../config.php'); -require_once(__DIR__.'/lib.php'); -$id = required_param('id', PARAM_INT); // course +// For this type of page this is the course id. +$courseid = required_param('id', PARAM_INT); -$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); - -require_course_login($course); - -$PAGE->set_pagelayout('incourse'); -$PAGE->set_url('/mod/workshop/index.php', array('id' => $course->id)); -$PAGE->set_title($course->fullname); -$PAGE->set_heading($course->shortname); -$PAGE->navbar->add(get_string('modulenameplural', 'workshop')); - -/// Output starts here - -echo $OUTPUT->header(); - -$params = array('context' => context_course::instance($course->id)); -$event = \mod_workshop\event\course_module_instance_list_viewed::create($params); -$event->add_record_snapshot('course', $course); -$event->trigger(); - -/// Get all the appropriate data - -if (! $workshops = get_all_instances_in_course('workshop', $course)) { - echo $OUTPUT->heading(get_string('modulenameplural', 'workshop')); - notice(get_string('noworkshops', 'workshop'), new moodle_url('/course/view.php', array('id' => $course->id))); - echo $OUTPUT->footer(); - die(); -} - -$usesections = course_format_uses_sections($course->format); - -$timenow = time(); -$strname = get_string('name'); -$table = new html_table(); - -if ($usesections) { - $strsectionname = course_get_format($course)->get_generic_section_name(); - $table->head = array ($strsectionname, $strname); - $table->align = array ('center', 'left'); -} else { - $table->head = array ($strname); - $table->align = array ('left'); -} - -foreach ($workshops as $workshop) { - if (empty($workshop->visible)) { - $link = html_writer::link(new moodle_url('/mod/workshop/view.php', array('id' => $workshop->coursemodule)), - $workshop->name, array('class' => 'dimmed')); - } else { - $link = html_writer::link(new moodle_url('/mod/workshop/view.php', array('id' => $workshop->coursemodule)), - $workshop->name); - } - - if ($usesections) { - $table->data[] = array(get_section_name($course, $workshop->section), $link); - } else { - $table->data[] = array($link); - } -} -echo $OUTPUT->heading(get_string('modulenameplural', 'workshop'), 3); -echo html_writer::table($table); -echo $OUTPUT->footer(); +\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, 'workshop'); diff --git a/mod/workshop/lang/en/deprecated.txt b/mod/workshop/lang/en/deprecated.txt new file mode 100644 index 00000000000..25811a0f7c9 --- /dev/null +++ b/mod/workshop/lang/en/deprecated.txt @@ -0,0 +1 @@ +noworkshops,mod_workshop diff --git a/mod/workshop/lang/en/workshop.php b/mod/workshop/lang/en/workshop.php index 40d3012c860..b8ffbe90f78 100644 --- a/mod/workshop/lang/en/workshop.php +++ b/mod/workshop/lang/en/workshop.php @@ -108,6 +108,7 @@ $string['daysleft'] = '{$a} days left'; $string['daystoday'] = 'today'; $string['daystomorrow'] = 'tomorrow'; $string['daysyesterday'] = 'yesterday'; +$string['deadline'] = 'Deadline'; $string['deadlinesignored'] = 'Time restrictions do not apply to you'; $string['deletesubmission'] = 'Delete submission'; $string['editassessmentform'] = 'Edit assessment form'; @@ -229,7 +230,6 @@ $string['nosubmissions'] = 'No submissions yet in this workshop'; $string['nothingtoreview'] = 'Nothing to review'; $string['notassessed'] = 'Not assessed yet'; $string['notoverridden'] = 'Not overridden'; -$string['noworkshops'] = 'There are no workshops in this course'; $string['noyoursubmission'] = 'You have not submitted your work yet'; $string['nothingfound'] = 'Nothing to display'; $string['nullgrade'] = '-'; @@ -241,10 +241,13 @@ $string['overallfeedbackmode_0'] = 'Disabled'; $string['overallfeedbackmode_1'] = 'Enabled and optional'; $string['overallfeedbackmode_2'] = 'Enabled and required'; $string['overallfeedbackmode_help'] = 'If enabled, a text field is displayed at the bottom of the assessment form. Reviewers can put the overall assessment of the submission there, or provide additional explanation of their assessment.'; +$string['overview_assessment_grade'] = 'Assessment grade'; +$string['overview_submission_grade'] = 'Submission grade'; $string['page-mod-workshop-x'] = 'Any workshop module page'; $string['participant'] = 'Participant'; $string['participantrevierof'] = 'Participant is reviewer of'; $string['participantreviewedby'] = 'Participant is reviewed by'; +$string['phase'] = 'Phase'; $string['phaseassessment'] = 'Assessment phase'; $string['phaseclosed'] = 'Closed'; $string['phaseevaluation'] = 'Grading evaluation phase'; @@ -345,6 +348,7 @@ $string['strategy_help'] = 'The grading strategy determines the assessment form * Rubric - A level assessment is given regarding specified criteria'; $string['strategyhaschanged'] = 'The workshop grading strategy has changed since the form was opened for editing.'; $string['submission'] = 'Submission'; +$string['submissions'] = 'Submissions'; $string['submissionattachment'] = 'Attachment'; $string['submissionby'] = 'Submission by {$a}'; $string['submissioncontent'] = 'Submission content'; @@ -455,3 +459,6 @@ $string['yourassessmentfor'] = 'Your assessment for {$a}'; $string['yourgrades'] = 'Your grades'; $string['yoursubmission'] = 'Your submission'; $string['yoursubmissionwithassessments'] = 'Your submission with assessments'; + +// Deprecated since Moodle 5.0. +$string['noworkshops'] = 'There are no workshops in this course'; diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index cbaf9cc0d00..3c6c83d8591 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -774,6 +774,37 @@ class workshop { return $DB->count_records_sql($sql, $params); } + /** + * Returns the total number of assessments in the workshop. + * + * @param bool $onlygraded If true, count only graded assessments + * @param int|null $groupid If not null, return only assessments by reviewers in the specified group + * @return int Number of assessments + */ + public function count_assessments(bool $onlygraded = false, ?int $groupid = null): int { + global $DB; + + $params = ['workshopid' => $this->id]; + $sql = "SELECT COUNT(s.id) + FROM {workshop_assessments} s + JOIN {workshop_submissions} ws ON (s.submissionid = ws.id) + JOIN {user} u ON (ws.authorid = u.id) + JOIN {workshop} w ON (ws.workshopid = w.id)"; + + if ($groupid) { + $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)"; + $params['groupid'] = $groupid; + } + + if ($onlygraded) { + $sql .= " WHERE s.grade IS NOT NULL AND w.id = :workshopid"; + } else { + $sql .= " WHERE w.id = :workshopid"; + } + + return $DB->count_records_sql($sql, $params); + } + /** * Returns submissions from this workshop diff --git a/mod/workshop/tests/behat/overview_report.feature b/mod/workshop/tests/behat/overview_report.feature new file mode 100644 index 00000000000..204bbcd46b1 --- /dev/null +++ b/mod/workshop/tests/behat/overview_report.feature @@ -0,0 +1,166 @@ +@mod @mod_workshop +Feature: Testing overview integration in mod_workshop +In order to summarize the workshops + As a user + I need to be able to see the workshop overview + + Background: + Given the following "users" exist: + | username | firstname | lastname | + | student1 | Student | 1 | + | student2 | Student | 2 | + | student3 | Student | 3 | + | student4 | Student | 4 | + | student5 | Student | 5 | + | student6 | Student | 6 | + | student7 | Student | 7 | + | student8 | Student | 8 | + | teacher1 | Teacher | T | + And the following "courses" exist: + | fullname | shortname | groupmode | + | Course 1 | C1 | 1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | student4 | C1 | student | + | student5 | C1 | student | + | student6 | C1 | student | + | student7 | C1 | student | + | student8 | C1 | student | + | teacher1 | C1 | editingteacher | + And the following "activities" exist: + | activity | name | course | idnumber | submissiontypetext | submissiontypefile | grade | gradinggrade | gradedecimals | overallfeedbackmethod |latesubmissions | submisstionstart | submissionend | + | workshop | Activity 1 | C1 | workshop1 | 2 | 1 | 100 | 5 | 1 | 2 |1 | ##1 Jan 2018 08:00## | ##1 Jan 2040 08:00## | + | workshop | Activity 2 | C1 | workshop1 | 2 | 1 | 100 | 5 | 1 | 2 |1 | ##1 Jan 2018 08:00## | ##1 Jan 2040 08:00## | + + Scenario: The workshop overview report should generate log events + Given I am on the "Course 1" "course > activities > workshop" page logged in as "teacher1" + When I am on the "Course 1" "course" page logged in as "teacher1" + And I navigate to "Reports" in current page administration + And I click on "Logs" "link" + And I click on "Get these logs" "button" + Then I should see "Course activities overview page viewed" + And I should see "viewed the instance list for the module 'workshop'" + + @javascript + Scenario: Students can see relevant columns in the workshop overview + # Workshop is not compatible with grade generators (hopefully someday). + Given I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1" + And I turn editing mode on + And I change window size to "large" + And I click on "Activity 1 (submission)" "core_grades > grade_actions" in the "Student 1" "table_row" + And I choose "Edit grade" in the open action menu + And I set the following fields to these values: + | Overridden | 1 | + | Final grade | 10 | + And I press "Save changes" + And I click on "Activity 2 (assessment)" "core_grades > grade_actions" in the "Student 1" "table_row" + And I choose "Edit grade" in the open action menu + And I set the following fields to these values: + | Overridden | 1 | + | Final grade | 20 | + And I press "Save changes" + And I change window size to "medium" + And I am on the "Activity 1" "workshop activity" page + And I change phase in workshop "Activity 1" to "Submission phase" + When I am on the "Course 1" "course > activities > workshop" page logged in as "student1" + # Check columns. + Then I should see "Name" in the "workshop_overview_collapsible" "region" + And I should see "Phase" in the "workshop_overview_collapsible" "region" + And I should see "Deadline" in the "workshop_overview_collapsible" "region" + And I should see "Assessment grade" in the "workshop_overview_collapsible" "region" + And I should see "Submission grade" in the "workshop_overview_collapsible" "region" + # Check phase. + And I should see "Submission phase" in the "Activity 1" "table_row" + And I should see "Setup phase" in the "Activity 2" "table_row" + # Cheack Deadline. + And I should see "1 January 2040" in the "Activity 1" "table_row" + And I should see "-" in the "Activity 2" "table_row" + # Check Grades. + And I should see "10.00" in the "Activity 1" "table_row" + And I should see "-" in the "Activity 1" "table_row" + And I should see "-" in the "Activity 2" "table_row" + # The worksup assessment grade is normalized to 5.00. + And I should see "5.00" in the "Activity 2" "table_row" + + Scenario: Teachers can see relevant columns in the workshop overview + Given I log in as "teacher1" + And I am on the "C1" course page logged in as teacher1 + And I edit assessment form in workshop "Activity 1" as: + | id_description__idx_0_editor | Aspect1 | + | id_description__idx_1_editor | | + | id_description__idx_2_editor | | + And I change phase in workshop "Activity 1" to "Submission phase" + # Change activity 2 to submission phase to see due date. + And I am on the "Activity 2" "workshop activity" page + And I change phase in workshop "Activity 2" to "Submission phase" + # student1 submits + And I am on the "Activity 1" "workshop activity" page logged in as student1 + And I add a submission in workshop "Activity 1" as: + | Title | Submission1 | + | Submission content | Some content | + # teacher1 allocates reviewers and changes the phase to assessment + When I am on the "Activity 1" "workshop activity" page logged in as teacher1 + And I allocate submissions in workshop "Activity 1" as: + | Participant | Reviewer | + | Student 1 | Student 2 | + | Student 1 | Student 3 | + | Student 1 | Student 4 | + And I am on the "Activity 1" "workshop activity" page + And I change phase in workshop "Activity 1" to "Assessment phase" + # student2 assesses work of student1 + And I am on the "Activity 1" "workshop activity" page logged in as student2 + And I assess submission "Student 1" in workshop "Activity 1" as: + | grade__idx_0 | 10 / 10 | + | peercomment__idx_0 | Amazing | + | Feedback for the author | Good work | + # student3 assesses work of student1 + And I am on the "Activity 1" "workshop activity" page logged in as student3 + And I assess submission "Student 1" in workshop "Activity 1" as: + | grade__idx_0 | 10 / 10 | + | peercomment__idx_0 | Amazing | + | Feedback for the author | Good work | + # student4 assesses work of student1 + And I am on the "Activity 1" "workshop activity" page logged in as student4 + And I assess submission "Student 1" in workshop "Activity 1" as: + | grade__idx_0 | 6 / 10 | + | peercomment__idx_0 | You can do better | + | Feedback for the author | Good work | + # teacher1 makes sure he can see all peer grades and changes to grading evaluation phase + And I am on the "Activity 1" "workshop activity" page logged in as teacher1 + And I change phase in workshop "Activity 1" to "Grading evaluation phase" + And I press "Re-calculate grades" + # Now, the test itself. + When I am on the "Course 1" "course > activities > workshop" page logged in as "teacher1" + # Check columns. + Then I should see "Name" in the "workshop_overview_collapsible" "region" + And I should see "Phase" in the "workshop_overview_collapsible" "region" + And I should see "Deadline" in the "workshop_overview_collapsible" "region" + And I should see "Submissions" in the "workshop_overview_collapsible" "region" + And I should see "Assessments" in the "workshop_overview_collapsible" "region" + # Check phase. + And I should see "Grading evaluation phase" in the "Activity 1" "table_row" + And I should see "Submission phase" in the "Activity 2" "table_row" + # Cheack Deadline. + And I should see "-" in the "Activity 1" "table_row" + And I should see "1 January 2040" in the "Activity 2" "table_row" + # Check Submissions. + And I should see "1 of 8" in the "Activity 1" "table_row" + And I should see "0 of 8" in the "Activity 2" "table_row" + # Check Assessments. + And I should see "3 of 3" in the "Activity 1" "table_row" + And I should see "-" in the "Activity 2" "table_row" + + @javascript + Scenario: The workshop index redirect to the activities overview + When I log in as "admin" + And I am on "Course 1" course homepage with editing mode on + And I add the "Activities" block + And I click on "Workshops" "link" in the "Activities" "block" + Then I should see "View all the activities in this course" + And I should see "Name" in the "workshop_overview_collapsible" "region" + And I should see "Phase" in the "workshop_overview_collapsible" "region" + And I should see "Submissions" in the "workshop_overview_collapsible" "region" + And I should see "Assessments" in the "workshop_overview_collapsible" "region" diff --git a/mod/workshop/tests/courseformat/overview_test.php b/mod/workshop/tests/courseformat/overview_test.php new file mode 100644 index 00000000000..a6f74a4ea28 --- /dev/null +++ b/mod/workshop/tests/courseformat/overview_test.php @@ -0,0 +1,636 @@ +. + +namespace mod_workshop\courseformat; + +use core_courseformat\local\overview\overviewfactory; + +/** + * Tests for Workshop overview integration. + * + * @covers \mod_workshop\course\overview + * @package mod_workshop + * @category test + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class overview_test extends \advanced_testcase { + #[\Override] + public static function setUpBeforeClass(): void { + global $CFG; + require_once($CFG->dirroot . '/mod/workshop/locallib.php'); + parent::setUpBeforeClass(); + } + + /** + * Test get_grade_item_names method. + * + * @dataProvider data_provider_get_grade_item_names + * @covers ::get_grade_item_names + * @param string $user + * @param bool $expectempty + * @param bool $hassubmission + * @param bool $hasassesment + */ + public function test_get_grade_item_names( + string $user, + bool $expectempty, + bool $hassubmission, + bool $hasassesment, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $activity = $this->getDataGenerator()->create_module( + 'workshop', + ['course' => $course->id], + ); + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + + // Set up a generator to create content. + /** @var \mod_workshop_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + + // Add grading. + $workshopitems = array_values( + \grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => 'workshop', + 'iteminstance' => (int) $activity->id, + 'courseid' => $course->id, + ['grade' => 100.0], + ]) + ); + + // Workshop stores in number 1 the assessment grade and in number 0 the submission grade. + $gradeitems = []; + foreach ($workshopitems as $workshopitem) { + if ($workshopitem->itemnumber == 0) { + $gradeitems['submission'] = $workshopitem; + } else { + $gradeitems['assessment'] = $workshopitem; + } + } + + $expectedsubmissions = '-'; + if ($hassubmission) { + $submissionid = $generator->create_submission( + $activity->id, + $student->id, + ['title' => 'My custom title', 'grade' => 85.00000], + ); + $gradegrade = new \grade_grade(); + $gradegrade->itemid = $gradeitems['submission']->id; + $gradegrade->userid = (int) $student->id; + $gradegrade->rawgrade = 77; + $gradegrade->finalgrade = 77; + $gradegrade->insert(); + $expectedsubmissions = '77.00000'; + } + + $expectedassessments = '-'; + if ($hasassesment) { + $generator->create_assessment( + $submissionid, + $student->id, + ['weight' => 3, 'grade' => 95.00000], + ); + $gradegrade = new \grade_grade(); + $gradegrade->itemid = $gradeitems['assessment']->id; + $gradegrade->userid = (int) $student->id; + $gradegrade->rawgrade = 88; + $gradegrade->finalgrade = 88; + $gradegrade->insert(); + $expectedassessments = '88.00000'; + } + + $currentuser = ($user == 'teacher') ? $teacher : $student; + $this->setUser($currentuser); + + $items = overviewfactory::create($cm)->get_grades_overviews(); + + // Students should not see item. + if ($expectempty) { + $this->assertEmpty($items); + return; + } + + $this->assertEquals(get_string('overview_submission_grade', 'mod_workshop'), $items[0]->get_name()); + $this->assertEquals($expectedsubmissions, $items[0]->get_value()); + + $this->assertEquals(get_string('overview_assessment_grade', 'mod_workshop'), $items[1]->get_name()); + $this->assertEquals($expectedassessments, $items[1]->get_value()); + } + + /** + * Data provider for test_get_grade_item_names. + * + * @return array + */ + public static function data_provider_get_grade_item_names(): array { + return [ + 'student with submissions' => [ + 'user' => 'student', + 'expectempty' => false, + 'hassubmission' => true, + 'hasassesment' => false, + ], + 'student with assessments' => [ + 'user' => 'student', + 'expectempty' => false, + 'hassubmission' => true, + 'hasassesment' => true, + ], + 'teacher' => [ + 'user' => 'teacher', + 'expectempty' => true, + 'hassubmission' => false, + 'hasassesment' => false, + ], + ]; + } + + /** + * Test get_extra_phase_overview method. + * + * @covers ::get_extra_phase_overview + * @dataProvider data_provider_get_extra_phase_overview + * @param string $user + * @param int $currentphase + */ + public function test_get_extra_phase_overview(string $user, int $currentphase): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $activity = $this->getDataGenerator()->create_module( + 'workshop', + ['course' => $course->id], + ); + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + + $manager = new \workshop($activity, $cm, $course, $cm->context); + $manager->switch_phase($currentphase); + + $currentuser = ($user == 'teacher') ? $teacher : $student; + $this->setUser($currentuser); + + $overview = overviewfactory::create($cm); + $reflection = new \ReflectionClass($overview); + $method = $reflection->getMethod('get_extra_phase_overview'); + $method->setAccessible(true); + $item = $method->invoke($overview); + + $this->assertEquals(get_string('phase', 'mod_workshop'), $item->get_name()); + $this->assertEquals($currentphase, $item->get_value()); + } + + /** + * Data provider for test_get_extra_phase_overview. + * + * @return array + */ + public static function data_provider_get_extra_phase_overview(): array { + return [ + 'teacher setup phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SETUP, + ], + 'student setup phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SETUP, + ], + 'teacher submission phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SUBMISSION, + ], + 'student submission phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SUBMISSION, + ], + 'teacher assessment phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + ], + 'student assessment phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + ], + 'teacher evaluation phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_EVALUATION, + ], + 'student evaluation phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_EVALUATION, + ], + 'teacher closed phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_CLOSED, + ], + 'student closed phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_CLOSED, + ], + ]; + } + + /** + * Test get_extra_deadline_overview method. + * + * @covers ::get_extra_deadline_overview + * @dataProvider data_provider_get_extra_deadline_overview + * @param string $user + * @param int $currentphase + * @param int $submissionend + * @param int $assessmentend + * @param int|null $expectedincrement null if the item should be null. + */ + public function test_get_extra_deadline_overview( + string $user, + int $currentphase, + int $submissionend, + int $assessmentend, + ?int $expectedincrement, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $current = $this->mock_clock_with_frozen()->time(); + + $activity = $this->getDataGenerator()->create_module( + 'workshop', + [ + 'course' => $course->id, + 'submissionend' => $current + $submissionend, + 'assessmentend' => $current + $assessmentend, + ], + ); + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + + $manager = new \workshop($activity, $cm, $course, $cm->context); + $manager->switch_phase($currentphase); + + $currentuser = ($user == 'teacher') ? $teacher : $student; + $this->setUser($currentuser); + + $overview = overviewfactory::create($cm); + $reflection = new \ReflectionClass($overview); + $method = $reflection->getMethod('get_extra_deadline_overview'); + $method->setAccessible(true); + $item = $method->invoke($overview); + + $this->assertEquals(get_string('deadline', 'mod_workshop'), $item->get_name()); + + if ($expectedincrement === null) { + $this->assertNull($item->get_value()); + return; + } + + $this->assertEquals($current + $expectedincrement, $item->get_value()); + } + + /** + * Data provider for test_get_extra_phase_overview. + * + * @return array + */ + public static function data_provider_get_extra_deadline_overview(): array { + $submissionend = 3600; + $assessmentend = 7200; + return [ + 'teacher setup phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SETUP, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + 'student setup phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SETUP, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + 'teacher submission phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => $submissionend, + ], + 'student submission phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => $submissionend, + ], + 'teacher assessment phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => $assessmentend, + ], + 'student assessment phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => $assessmentend, + ], + 'teacher evaluation phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + 'student evaluation phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + 'teacher closed phase' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_CLOSED, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + 'student closed phase' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_CLOSED, + 'submissionend' => $submissionend, + 'assessmentend' => $assessmentend, + 'expectedincrement' => null, + ], + ]; + } + + /** + * Test get_extra_submissions_overview and get_extra_assessments_overview methods. + * + * @covers ::get_extra_submissions_overview + * @covers ::get_extra_assessments_overview + * @dataProvider data_provider_get_extra_submissions_overview + * @param string $user + * @param int $currentphase + * @param bool $hasstudentactivity + * @param bool $expectnull + */ + public function test_get_extra_submissions_overview( + string $user, + int $currentphase, + bool $hasstudentactivity, + bool $expectnull, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $activity = $this->getDataGenerator()->create_module( + 'workshop', + ['course' => $course->id], + ); + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + + // Set up a generator to create content. + /** @var \mod_workshop_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + + // Add grading. + $workshopitems = array_values( + \grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => 'workshop', + 'iteminstance' => (int) $activity->id, + 'courseid' => $course->id, + ['grade' => 100.0], + ]) + ); + + // Worksop stores un number 1 the assessment grade and in number 0 the submission grade. + foreach ($workshopitems as $workshopitem) { + if ($workshopitem->itemnumber == 0) { + $gradeitems['submission'] = $workshopitem; + } else { + $gradeitems['assessment'] = $workshopitem; + } + } + + if ($hasstudentactivity) { + // Create some submissions. + $submissionid = $generator->create_submission( + $activity->id, + $student->id, + ['title' => 'My custom title', 'grade' => 85.00000], + ); + $generator->create_submission( + $activity->id, + $student2->id, + ['title' => 'My custom title', 'grade' => 65.00000], + ); + // Assess one submission. + $generator->create_assessment( + $submissionid, + $student->id, + ['weight' => 3, 'grade' => 95.00000], + ); + } + + $manager = new \workshop($activity, $cm, $course, $cm->context); + $manager->switch_phase($currentphase); + + $currentuser = ($user == 'teacher') ? $teacher : $student; + $this->setUser($currentuser); + + $overview = overviewfactory::create($cm); + $reflection = new \ReflectionClass($overview); + + $method = $reflection->getMethod('get_extra_submissions_overview'); + $method->setAccessible(true); + $itemsubmissions = $method->invoke($overview); + + $method = $reflection->getMethod('get_extra_assessments_overview'); + $method->setAccessible(true); + $itemassessment = $method->invoke($overview); + + if ($expectnull) { + $this->assertNull($itemsubmissions); + $this->assertNull($itemassessment); + return; + } + + $this->assertEquals(get_string('submissions', 'mod_workshop'), $itemsubmissions->get_name()); + $expected = ($hasstudentactivity) ? 2 : 0; + $this->assertEquals($expected, $itemsubmissions->get_value()); + + $this->assertEquals(get_string('assessments', 'mod_workshop'), $itemassessment->get_name()); + $expected = ($hasstudentactivity) ? 1 : 0; + $this->assertEquals($expected, $itemassessment->get_value()); + } + + /** + * Data provider for test_get_extra_submissions_overview. + * + * @return array + */ + public static function data_provider_get_extra_submissions_overview(): array { + return [ + 'teacher setup phase without activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SETUP, + 'hasstudentactivity' => false, + 'expectnull' => false, + ], + 'student setup phase without activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SETUP, + 'hasstudentactivity' => false, + 'expectnull' => true, + ], + 'teacher submission phase without activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'hasstudentactivity' => false, + 'expectnull' => false, + ], + 'student submission phase without activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'hasstudentactivity' => false, + 'expectnull' => true, + ], + 'teacher assessment phase without activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'hasstudentactivity' => false, + 'expectnull' => false, + ], + 'student assessment phase without activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'hasstudentactivity' => false, + 'expectnull' => true, + ], + 'teacher evaluation phase without activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'hasstudentactivity' => false, + 'expectnull' => false, + ], + 'student evaluation phase without activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'hasstudentactivity' => false, + 'expectnull' => true, + ], + 'teacher closed phase without activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_CLOSED, + 'hasstudentactivity' => false, + 'expectnull' => false, + ], + 'student closed phase without activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_CLOSED, + 'hasstudentactivity' => false, + 'expectnull' => true, + ], + // Tests with assessments. + 'teacher setup phase with activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SETUP, + 'hasstudentactivity' => true, + 'expectnull' => false, + ], + 'student setup phase with activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SETUP, + 'hasstudentactivity' => true, + 'expectnull' => true, + ], + 'teacher submission phase with activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'hasstudentactivity' => true, + 'expectnull' => false, + ], + 'student submission phase with activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_SUBMISSION, + 'hasstudentactivity' => true, + 'expectnull' => true, + ], + 'teacher assessment phase with activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'hasstudentactivity' => true, + 'expectnull' => false, + ], + 'student assessment phase with activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_ASSESSMENT, + 'hasstudentactivity' => true, + 'expectnull' => true, + ], + 'teacher evaluation phase with activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'hasstudentactivity' => true, + 'expectnull' => false, + ], + 'student evaluation phase with activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_EVALUATION, + 'hasstudentactivity' => true, + 'expectnull' => true, + ], + 'teacher closed phase with activity' => [ + 'user' => 'teacher', + 'currentphase' => \workshop::PHASE_CLOSED, + 'hasstudentactivity' => true, + 'expectnull' => false, + ], + 'student closed phase with activity' => [ + 'user' => 'student', + 'currentphase' => \workshop::PHASE_CLOSED, + 'hasstudentactivity' => true, + 'expectnull' => true, + ], + ]; + } +} diff --git a/mod/workshop/tests/locallib_test.php b/mod/workshop/tests/locallib_test.php index 4f340409369..ec7f11bc93f 100644 --- a/mod/workshop/tests/locallib_test.php +++ b/mod/workshop/tests/locallib_test.php @@ -867,4 +867,101 @@ final class locallib_test extends \advanced_testcase { return $initialbarprefs; } + + /** + * Test count_submissions and count_assessments methods. + * + * @covers \workshop::count_submissions + * @covers \workshop::count_assessments + */ + public function test_count_submissions_count_assessments(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); + $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); + $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + groups_add_member($group1, $student1); + groups_add_member($group1, $student3); + groups_add_member($group2, $student2); + + $activity = $this->getDataGenerator()->create_module( + 'workshop', + ['course' => $course->id , 'groupmode' => SEPARATEGROUPS], + ); + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + + // Set up a generator to create content. + /** @var \mod_workshop_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + + // Create some submissions. + $submission1id = $generator->create_submission( + $activity->id, + $student1->id, + ['title' => 'My custom title', 'grade' => 85.00000], + ); + $submission2id = $generator->create_submission( + $activity->id, + $student2->id, + ['title' => 'My custom title', 'grade' => null], + ); + $submission3id = $generator->create_submission( + $activity->id, + $student3->id, + ['title' => 'My custom title', 'grade' => null], + ); + $submission4id = $generator->create_submission( + $activity->id, + $student4->id, + ['title' => 'My custom title', 'grade' => null], + ); + // Assess one submission. + $generator->create_assessment( + $submission1id, + $student1->id, + ['weight' => 3, 'grade' => 95.00000], + ); + $generator->create_assessment( + $submission2id, + $student2->id, + ['weight' => 3, 'grade' => null], + ); + $generator->create_assessment( + $submission3id, + $student3->id, + ['weight' => 3, 'grade' => null], + ); + $generator->create_assessment( + $submission4id, + $student4->id, + ['weight' => 3, 'grade' => 35.00000], + ); + + $manager = new workshop($activity, $cm, $course, $cm->context); + + $this->assertEquals(4, $manager->count_submissions()); + $this->assertEquals(4, $manager->count_submissions('all')); + $this->assertEquals(1, $manager->count_submissions($student1->id)); + $this->assertEquals(1, $manager->count_submissions($student2->id)); + + $this->assertEquals(2, $manager->count_submissions('all', $group1->id)); + $this->assertEquals(1, $manager->count_submissions('all', $group2->id)); + $this->assertEquals(1, $manager->count_submissions($student1->id, $group1->id)); + $this->assertEquals(0, $manager->count_submissions($student2->id, $group1->id)); + + $this->assertEquals(4, $manager->count_assessments()); + $this->assertEquals(2, $manager->count_assessments(true)); + + $this->assertEquals(2, $manager->count_assessments(false, $group1->id)); + $this->assertEquals(1, $manager->count_assessments(true, $group1->id)); + + $this->assertEquals(1, $manager->count_assessments(false, $group2->id)); + $this->assertEquals(0, $manager->count_assessments(true, $group2->id)); + } }