MDL-66893 mod_forum: update user content region in grader UI
[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  * @return {Function}
41  */
42 const getContentForUserIdFunction = (cmid) => (userid) => {
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);
53             return Templates.render(templateNames.contentRegion, context);
54         })
55         .catch(Notification.exception);
56 };
58 /**
59  * Curried function with CMID set, this is then used in unified grader as a fetch users call.
60  * The function curried fetches all users in a course for a given CMID.
61  *
62  * @param {Number} cmid
63  * @return {Array} Array of users for a given context.
64  */
65 const getUsersForCmidFunction = (cmid) => async() => {
66     const context = await CourseRepository.getUsersFromCourseModuleID(cmid);
68     return context.users;
69 };
72 const findGradableNode = node => node.closest(Selectors.gradableItem);
74 /**
75  * For a discussion we need to manipulate it's posts to hide certain UI elements.
76  *
77  * @param {Object} discussion
78  * @return {Array} name, id, posts
79  */
80 const discussionPostMapper = (discussion) => {
81     // Map postid => post.
82     const parentMap = new Map();
83     discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));
84     const userPosts = discussion.posts.userposts.map(post => {
85         post.readonly = true;
86         post.hasreplies = false;
87         post.replies = [];
89         const parent = post.parentid ? parentMap.get(post.parentid) : null;
90         if (parent) {
91             parent.hasreplies = false;
92             parent.replies = [];
93             parent.readonly = true;
94         }
96         return {
97             parent,
98             post
99         };
100     });
102     return {
103         ...discussion,
104         posts: userPosts,
105     };
106 };
108 /**
109  * Launch the Grader.
110  *
111  * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
112  */
113 const launchWholeForumGrading = async(rootNode) => {
114     const data = rootNode.dataset;
115     const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
116         'mod_forum',
117         data.contextid,
118         data.gradingComponent,
119         data.gradingComponentSubtype,
120         data.gradableItemtype
121     );
123     await Grader.launch(
124         getUsersForCmidFunction(data.cmid),
125         getContentForUserIdFunction(data.cmid),
126         gradingPanelFunctions.getter,
127         gradingPanelFunctions.setter,
128         {
129             groupid: data.groupid,
130             initialUserId: data.initialuserid,
131             moduleName: data.name,
132             courseName: data.courseName,
133             courseUrl: relativeUrl('/course/view.php', {id: data.courseId})
134         }
135     );
136 };
138 /**
139  * Register listeners to launch the grading panel.
140  */
141 export const registerLaunchListeners = () => {
142     document.addEventListener('click', async(e) => {
143         if (e.target.matches(Selectors.launch)) {
144             const rootNode = findGradableNode(e.target);
146             if (!rootNode) {
147                 throw Error('Unable to find a gradable item');
148             }
150             if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
151                 // Note: The preventDefault must be before any async function calls because the function becomes async
152                 // at that point and the default action is implemented.
153                 e.preventDefault();
154                 try {
155                     await launchWholeForumGrading(rootNode);
156                 } catch (error) {
157                     Notification.exception(error);
158                 }
159             } else {
160                 throw Error('Unable to find a valid gradable item');
161             }
162         }
163     });
164 };