weekly release 4.0dev
[moodle.git] / mod / forum / amd / src / grades / grader.js
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/>.
16 /**
17  * This module will tie together all of the different calls the gradable module will make.
18  *
19  * @module     mod_forum/grades/grader
20  * @package    mod_forum
21  * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 import * as Selectors from './grader/selectors';
25 import Repository from 'mod_forum/repository';
26 import Templates from 'core/templates';
27 import * as Grader from '../local/grades/grader';
28 import Notification from 'core/notification';
29 import CourseRepository from 'core_course/repository';
30 import {relativeUrl} from 'core/url';
32 const templateNames = {
33     contentRegion: 'mod_forum/grades/grader/discussion/posts',
34 };
36 /**
37  * Curried function with CMID set, this is then used in unified grader as a fetch a users content.
38  *
39  * @param {Number} cmid
40  * @param {Bool} experimentalDisplayMode
41  * @return {Function}
42  */
43 const getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {
44     /**
45      * Given the parent function is called with the second param set execute the partially executed function.
46      *
47      * @param {Number} userid
48      */
49     return Repository.getDiscussionByUserID(userid, cmid)
50         .then(context => {
51             // Rebuild the returned data for the template.
52             context.discussions = context.discussions.map(discussionPostMapper);
53             context.experimentaldisplaymode = experimentalDisplayMode ? true : false;
55             return Templates.render(templateNames.contentRegion, context);
56         })
57         .catch(Notification.exception);
58 };
60 /**
61  * Curried function with CMID set, this is then used in unified grader as a fetch users call.
62  * The function curried fetches all users in a course for a given CMID.
63  *
64  * @param {Number} cmid
65  * @param {Number} groupID
66  * @return {Array} Array of users for a given context.
67  */
68 const getUsersForCmidFunction = (cmid, groupID) => async() => {
69     const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID);
71     return context.users;
72 };
75 const findGradableNode = node => node.closest(Selectors.gradableItem);
77 /**
78  * For a discussion we need to manipulate it's posts to hide certain UI elements.
79  *
80  * @param {Object} discussion
81  * @return {Array} name, id, posts
82  */
83 const discussionPostMapper = (discussion) => {
84     // Map postid => post.
85     const parentMap = new Map();
86     discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));
87     const userPosts = discussion.posts.userposts.map(post => {
88         post.readonly = true;
89         post.hasreplies = false;
90         post.replies = [];
92         const parent = post.parentid ? parentMap.get(post.parentid) : null;
93         if (parent) {
94             parent.hasreplies = false;
95             parent.replies = [];
96             parent.readonly = true;
97             post.parentauthorname = parent.author.fullname;
98         }
100         return {
101             parent,
102             post
103         };
104     });
106     return {
107         ...discussion,
108         posts: userPosts,
109     };
110 };
112 /**
113  * Launch the Grader.
114  *
115  * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
116  */
117 const launchWholeForumGrading = async(rootNode, {
118     focusOnClose = null,
119 } = {}) => {
120     const data = rootNode.dataset;
121     const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
122         'mod_forum',
123         data.contextid,
124         data.gradingComponent,
125         data.gradingComponentSubtype,
126         data.gradableItemtype
127     );
129     const groupID = data.group ? data.group : 0;
131     await Grader.launch(
132         getUsersForCmidFunction(data.cmid, groupID),
133         getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == "1"),
134         gradingPanelFunctions.getter,
135         gradingPanelFunctions.setter,
136         {
137             groupid: data.groupid,
138             initialUserId: data.initialuserid,
139             moduleName: data.name,
140             courseName: data.courseName,
141             courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),
142             sendStudentNotifications: data.sendStudentNotifications,
143             focusOnClose,
144         }
145     );
146 };
148 /**
149  * Launch the Grader.
150  *
151  * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
152  */
153 const launchViewGrading = async(rootNode, {
154     focusOnClose = null,
155 } = {}) => {
156     const data = rootNode.dataset;
157     const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
158         'mod_forum',
159         data.contextid,
160         data.gradingComponent,
161         data.gradingComponentSubtype,
162         data.gradableItemtype
163     );
165     await Grader.view(
166         gradingPanelFunctions.getter,
167         data.userid,
168         data.name,
169         {
170             focusOnClose,
171         }
172     );
173 };
175 /**
176  * Register listeners to launch the grading panel.
177  */
178 export const registerLaunchListeners = () => {
179     document.addEventListener('click', async(e) => {
180         if (e.target.matches(Selectors.launch)) {
181             const rootNode = findGradableNode(e.target);
183             if (!rootNode) {
184                 throw Error('Unable to find a gradable item');
185             }
187             if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
188                 // Note: The preventDefault must be before any async function calls because the function becomes async
189                 // at that point and the default action is implemented.
190                 e.preventDefault();
191                 try {
192                     await launchWholeForumGrading(rootNode, {
193                         focusOnClose: e.target,
194                     });
195                 } catch (error) {
196                     Notification.exception(error);
197                 }
198             } else {
199                 throw Error('Unable to find a valid gradable item');
200             }
201         }
202         if (e.target.matches(Selectors.viewGrade)) {
203             e.preventDefault();
204             const rootNode = findGradableNode(e.target);
206             if (!rootNode) {
207                 throw Error('Unable to find a gradable item');
208             }
210             if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
211                 // Note: The preventDefault must be before any async function calls because the function becomes async
212                 // at that point and the default action is implemented.
213                 e.preventDefault();
214                 try {
215                     await launchViewGrading(rootNode, {
216                         focusOnClose: e.target,
217                     });
218                 } catch (error) {
219                     Notification.exception(error);
220                 }
221             } else {
222                 throw Error('Unable to find a valid gradable item');
223             }
224         }
225     });
226 };