weekly release 4.0dev
[moodle.git] / mod / forum / amd / src / grades / grader.js
CommitLineData
bae67469
MM
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * This module will tie together all of the different calls the gradable module will make.
18 *
19 * @module mod_forum/grades/grader
bae67469
MM
20 * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23import * as Selectors from './grader/selectors';
24import Repository from 'mod_forum/repository';
25import Templates from 'core/templates';
26import * as Grader from '../local/grades/grader';
27import Notification from 'core/notification';
28import CourseRepository from 'core_course/repository';
4c98e56c 29import {relativeUrl} from 'core/url';
bae67469
MM
30
31const templateNames = {
32 contentRegion: 'mod_forum/grades/grader/discussion/posts',
33};
34
046ebd3a
MM
35/**
36 * Curried function with CMID set, this is then used in unified grader as a fetch a users content.
37 *
38 * @param {Number} cmid
69848e71 39 * @param {Bool} experimentalDisplayMode
046ebd3a
MM
40 * @return {Function}
41 */
69848e71 42const getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {
046ebd3a
MM
43 /**
44 * Given the parent function is called with the second param set execute the partially executed function.
45 *
46 * @param {Number} userid
47 */
48 return Repository.getDiscussionByUserID(userid, cmid)
49 .then(context => {
50 // Rebuild the returned data for the template.
51 context.discussions = context.discussions.map(discussionPostMapper);
69848e71 52 context.experimentaldisplaymode = experimentalDisplayMode ? true : false;
046ebd3a
MM
53
54 return Templates.render(templateNames.contentRegion, context);
55 })
56 .catch(Notification.exception);
57};
bae67469 58
046ebd3a
MM
59/**
60 * Curried function with CMID set, this is then used in unified grader as a fetch users call.
61 * The function curried fetches all users in a course for a given CMID.
62 *
63 * @param {Number} cmid
546bf3c6 64 * @param {Number} groupID
046ebd3a
MM
65 * @return {Array} Array of users for a given context.
66 */
546bf3c6
MM
67const getUsersForCmidFunction = (cmid, groupID) => async() => {
68 const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID);
bae67469 69
046ebd3a 70 return context.users;
bae67469
MM
71};
72
bae67469 73
046ebd3a
MM
74const findGradableNode = node => node.closest(Selectors.gradableItem);
75
76/**
77 * For a discussion we need to manipulate it's posts to hide certain UI elements.
78 *
79 * @param {Object} discussion
80 * @return {Array} name, id, posts
81 */
82const discussionPostMapper = (discussion) => {
bae67469
MM
83 // Map postid => post.
84 const parentMap = new Map();
85 discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));
bae67469 86 const userPosts = discussion.posts.userposts.map(post => {
bae67469 87 post.readonly = true;
d9fedf3d
RW
88 post.hasreplies = false;
89 post.replies = [];
bae67469 90
d9fedf3d
RW
91 const parent = post.parentid ? parentMap.get(post.parentid) : null;
92 if (parent) {
93 parent.hasreplies = false;
94 parent.replies = [];
95 parent.readonly = true;
69848e71 96 post.parentauthorname = parent.author.fullname;
d9fedf3d 97 }
bae67469 98
d9fedf3d
RW
99 return {
100 parent,
101 post
102 };
bae67469
MM
103 });
104
105 return {
d9fedf3d 106 ...discussion,
bae67469
MM
107 posts: userPosts,
108 };
109};
110
eaee6477
AN
111/**
112 * Launch the Grader.
113 *
114 * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
115 */
4395ef46
AN
116const launchWholeForumGrading = async(rootNode, {
117 focusOnClose = null,
118} = {}) => {
eaee6477 119 const data = rootNode.dataset;
eaee6477
AN
120 const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
121 'mod_forum',
122 data.contextid,
123 data.gradingComponent,
124 data.gradingComponentSubtype,
125 data.gradableItemtype
126 );
127
546bf3c6
MM
128 const groupID = data.group ? data.group : 0;
129
eaee6477 130 await Grader.launch(
546bf3c6 131 getUsersForCmidFunction(data.cmid, groupID),
69848e71 132 getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == "1"),
eaee6477
AN
133 gradingPanelFunctions.getter,
134 gradingPanelFunctions.setter,
135 {
136 groupid: data.groupid,
137 initialUserId: data.initialuserid,
4c98e56c
RW
138 moduleName: data.name,
139 courseName: data.courseName,
b6bf1e8e
SR
140 courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),
141 sendStudentNotifications: data.sendStudentNotifications,
4395ef46 142 focusOnClose,
eaee6477
AN
143 }
144 );
145};
146
07d8669d
MM
147/**
148 * Launch the Grader.
149 *
150 * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
151 */
799418ad
AN
152const launchViewGrading = async(rootNode, {
153 focusOnClose = null,
154} = {}) => {
07d8669d
MM
155 const data = rootNode.dataset;
156 const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
157 'mod_forum',
158 data.contextid,
159 data.gradingComponent,
160 data.gradingComponentSubtype,
161 data.gradableItemtype
162 );
163
164 await Grader.view(
165 gradingPanelFunctions.getter,
166 data.userid,
799418ad
AN
167 data.name,
168 {
169 focusOnClose,
170 }
07d8669d
MM
171 );
172};
173
f281c616
AN
174/**
175 * Register listeners to launch the grading panel.
176 */
bae67469 177export const registerLaunchListeners = () => {
046ebd3a 178 document.addEventListener('click', async(e) => {
bae67469
MM
179 if (e.target.matches(Selectors.launch)) {
180 const rootNode = findGradableNode(e.target);
181
182 if (!rootNode) {
183 throw Error('Unable to find a gradable item');
184 }
185
186 if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
f281c616
AN
187 // Note: The preventDefault must be before any async function calls because the function becomes async
188 // at that point and the default action is implemented.
bae67469 189 e.preventDefault();
eaee6477 190 try {
4395ef46
AN
191 await launchWholeForumGrading(rootNode, {
192 focusOnClose: e.target,
193 });
eaee6477
AN
194 } catch (error) {
195 Notification.exception(error);
196 }
bae67469
MM
197 } else {
198 throw Error('Unable to find a valid gradable item');
199 }
200 }
07d8669d
MM
201 if (e.target.matches(Selectors.viewGrade)) {
202 e.preventDefault();
203 const rootNode = findGradableNode(e.target);
204
205 if (!rootNode) {
206 throw Error('Unable to find a gradable item');
207 }
208
209 if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
210 // Note: The preventDefault must be before any async function calls because the function becomes async
211 // at that point and the default action is implemented.
212 e.preventDefault();
213 try {
799418ad
AN
214 await launchViewGrading(rootNode, {
215 focusOnClose: e.target,
216 });
07d8669d
MM
217 } catch (error) {
218 Notification.exception(error);
219 }
220 } else {
221 throw Error('Unable to find a valid gradable item');
222 }
223 }
bae67469
MM
224 });
225};