diff --git a/README.md b/README.md index f6d5b867..faf1d83e 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,36 @@ 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 this program. If not, see . + +## Version 2024040300 refactor ## + +Post version `2024040300` this plugin was completely refactored to support more reports and modules. + +Each report is now a subplugin within the `report` directory +The subplugins report class should extend from the \local_assessfreq\report_base class + +Capability checks were reworked to be relative to the location that they are being loading from. The initial version +has the following capabilities: + +- local/assessfreq:view +- assessfreqreport/activity_dashboard:view +- assessfreqreport/activities_in_progress:view +- assessfreqreport/heatmap:view +- assessfreqreport/summary_graphs:view + +however each future subplugin can define their own access checks by using the abstract `has_access` method. +Accessing the reports from a course (link now added to the course report screen) will do the capability check at the +course context level, otherwise system level will be used. + +The reports themselves should also be restricted based on the $PAGE->course if it is not the SITEID as this is set +during the intial load of the index.php file. + +Each module is now a subplugin within the `source` directory +The subplugins source class should extend from the \local_assessfreq\source_base class + + +Along with general performance improvements additional settings have been added to reduce the load on the reports: + +* Assign and quiz sources have a setting called "windowexclusion" which will allow the admin to specify a length of time to exclude long running assessments +* The activity dashboard has "trendlimit" and "trendcount" settings to reduce the number of trend records it attempts to load +* User tables have been update to only return users that have attempt data diff --git a/amd/build/calendar.min.js b/amd/build/calendar.min.js deleted file mode 100644 index 59f5f57f..00000000 --- a/amd/build/calendar.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Javascript for heatmap calendar generation and display. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/calendar",["core/str","core/notification","core/ajax"],(function(Str,Notification,Ajax){var Calendar={},eventArray=[];const stringArr=[{key:"sun",component:"calendar"},{key:"mon",component:"calendar"},{key:"tue",component:"calendar"},{key:"wed",component:"calendar"},{key:"thu",component:"calendar"},{key:"fri",component:"calendar"},{key:"sat",component:"calendar"},{key:"jan",component:"local_assessfreq"},{key:"feb",component:"local_assessfreq"},{key:"mar",component:"local_assessfreq"},{key:"apr",component:"local_assessfreq"},{key:"may",component:"local_assessfreq"},{key:"jun",component:"local_assessfreq"},{key:"jul",component:"local_assessfreq"},{key:"aug",component:"local_assessfreq"},{key:"sep",component:"local_assessfreq"},{key:"oct",component:"local_assessfreq"},{key:"nov",component:"local_assessfreq"},{key:"dec",component:"local_assessfreq"}];var stringResult,heatRangeMax,heatRangeMin,colorArray,processModules,heatRangeScale={1:0,2:0,3:0,4:0,5:0,6:0};const getContrast=function(hexcolor){return void 0===hexcolor?"#000000":("#"===hexcolor.slice(0,1)&&(hexcolor=hexcolor.slice(1)),(299*parseInt(hexcolor.substr(0,2),16)+587*parseInt(hexcolor.substr(2,2),16)+114*parseInt(hexcolor.substr(4,2),16))/1e3>=128?"#000000":"#FFFFFF")},daysInMonth=function(month,year){return 32-new Date(year,month,32).getDate()},getHeatColors=function(){return new Promise(((resolve,reject)=>{Ajax.call([{methodname:"local_assessfreq_get_heat_colors",args:{}}],!0,!1)[0].done((function(response){colorArray=JSON.parse(response),resolve(colorArray)})).fail((function(){reject(new Error("Failed to get heat colors"))}))}))},getProcessModules=function(){return new Promise(((resolve,reject)=>{Ajax.call([{methodname:"local_assessfreq_get_process_modules",args:{}}],!0,!1)[0].done((function(response){processModules=JSON.parse(response),resolve(processModules)})).fail((function(){reject(new Error("Failed to get process events"))}))}))},getHeat=function(eventCount){if(eventCount==heatRangeMin)return 1;const localPercent=(eventCount-heatRangeMin)/(heatRangeMax-heatRangeMin);let heat=Math.round(5*localPercent+1);return heat<1&&(heat=1),heat>6&&(heat=6),heat},getEvents=function(_ref){let{year:year,metric:metric,modules:modules}=_ref;return new Promise(((resolve,reject)=>{let args={year:year,metric:metric,modules:modules},jsonArgs=JSON.stringify(args);Ajax.call([{methodname:"local_assessfreq_get_frequency",args:{jsondata:jsonArgs}}])[0].done((response=>{eventArray=JSON.parse(response),resolve(eventArray)})).fail((()=>{reject(new Error("Failed to get events"))}))}))},createTables=function(_ref2){let{year:year,startMonth:startMonth,endMonth:endMonth}=_ref2;return new Promise(((resolve,reject)=>{let calendarContainer=document.createElement("div"),month=startMonth;for(let i=startMonth;i<=endMonth;i++){let container=document.createElement("div");container.classList.add("local-assessfreq-month");let table=document.createElement("table");table.classList.add("table-striped");let thead=document.createElement("thead"),tbody=document.createElement("tbody");tbody.id="calendar-body-"+i;let monthRow=document.createElement("tr"),dayrow=document.createElement("tr"),monthHeader=document.createElement("th");monthHeader.colSpan=7,monthHeader.innerHTML=stringResult[7+month];for(let j=0;j<7;j++){let dayHeader=document.createElement("th");dayHeader.innerHTML=stringResult[j],dayrow.appendChild(dayHeader)}monthRow.appendChild(monthHeader),thead.appendChild(monthRow),thead.appendChild(dayrow),table.appendChild(thead),table.appendChild(tbody),container.appendChild(table),calendarContainer.appendChild(container),month++}if(void 0===year||void 0===startMonth||void 0===endMonth)reject(Error("Failed to create calendar tables."));else{resolve({calendarContainer:calendarContainer,year:year,startMonth:startMonth})}}))},getTooltip=function(dayArray){let tipHTML="";for(let[key,value]of Object.entries(dayArray))tipHTML+=""+processModules[key]+": "+value+"
";return tipHTML},populateCalendarDays=function(table,year,month){let firstDay=new Date(year,month).getDay(),monthEvents=function(year,month){let monthevents;return void 0!==eventArray[year]&&void 0!==eventArray[year][month]&&(monthevents=eventArray[year][month]),monthevents}(year,month+1),date=1;for(let i=0;i<6;i++){let row=document.createElement("tr");for(let j=0;j<7;j++){if(0===i&&jdaysInMonth(month,year))break;if(cell=document.createElement("td"),cellText=document.createTextNode(date),void 0!==monthEvents&&monthEvents.hasOwnProperty(date)){let heat=getHeat(monthEvents[date].number);(0==heatRangeScale[heat]||heatRangeScale[heat]>monthEvents[date].number)&&(heatRangeScale[heat]=monthEvents[date].number),cell.style.backgroundColor=colorArray[heat],cell.style.color=getContrast(colorArray[heat]),cell.dataset.toggle="tooltip",cell.dataset.html="true",cell.dataset.event="true",cell.dataset.date=year+"-"+(month+1)+"-"+date,cell.title=getTooltip(monthEvents[date]),cell.style.cursor="pointer"}date++}cell.appendChild(cellText),row.appendChild(cell)}table.appendChild(row)}},populateCalendar=function(_ref3){let{calendarContainer:calendarContainer,year:year,startMonth:startMonth}=_ref3;return new Promise(((resolve,reject)=>{let tables=calendarContainer.getElementsByTagName("tbody"),month=startMonth;for(var i=0;i{let table=document.createElement("table"),tbody=document.createElement("tbody"),trow=document.createElement("tr");for(var i=1;i<7;i++)if(0!==heatRangeScale[i]){let cell=document.createElement("td"),cellText=document.createTextNode(heatRangeScale[i]+"+");cell.appendChild(cellText),cell.style.backgroundColor=colorArray[i],cell.style.color=getContrast(colorArray[i]),trow.appendChild(cell)}tbody.appendChild(trow),table.appendChild(tbody),heatRangeScale={1:0,2:0,3:0,4:0,5:0,6:0},resolve(table)}))},Calendar.generate=function(year,startMonth,endMonth,metric,modules){return new Promise(((resolve,reject)=>{const dateObj={year:year,startMonth:startMonth,endMonth:endMonth},eventObj={year:year,metric:metric,modules:modules};Str.get_strings(stringArr).catch((()=>{Notification.exception(new Error("Failed to load strings"))})).then((stringReturn=>(stringResult=stringReturn,eventObj))).then(getEvents).then((eventArray=>{!function(eventArray,dateObj){new Promise((resolve=>{if(void 0===eventArray&&(heatRangeMax=0,heatRangeMin=0,resolve(eventArray)),Object.keys(eventArray).length>0&&"undefined"!==eventArray[dateObj.year]){let eventcount=new Array,year=eventArray[dateObj.year];for(let i=0;i<12;i++)if(void 0!==year[i]){let month=year[i];for(let j=0;j<32;j++)void 0!==month[j]&&eventcount.push(month[j].number)}heatRangeMax=Math.max(...eventcount),heatRangeMin=Math.min(...eventcount)}else heatRangeMax=0,heatRangeMin=0;resolve(eventArray)}))}(eventArray,dateObj)})).then(getHeatColors).then(getProcessModules).then((()=>dateObj)).then(createTables).then(populateCalendar).then((calendarHTML=>{void 0!==calendarHTML?resolve(calendarHTML):reject(Error("Could not generate calendar"))}))}))},Calendar})); - -//# sourceMappingURL=calendar.min.js.map \ No newline at end of file diff --git a/amd/build/calendar.min.js.map b/amd/build/calendar.min.js.map deleted file mode 100644 index 067b1a77..00000000 --- a/amd/build/calendar.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calendar.min.js","sources":["../src/calendar.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for heatmap calendar generation and display.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/str', 'core/notification', 'core/ajax'], function (Str, Notification, Ajax) {\n\n /**\n * Module level variables.\n */\n var Calendar = {};\n var eventArray = [];\n const stringArr = [\n {key: 'sun', component: 'calendar'},\n {key: 'mon', component: 'calendar'},\n {key: 'tue', component: 'calendar'},\n {key: 'wed', component: 'calendar'},\n {key: 'thu', component: 'calendar'},\n {key: 'fri', component: 'calendar'},\n {key: 'sat', component: 'calendar'},\n {key: 'jan', component: 'local_assessfreq'},\n {key: 'feb', component: 'local_assessfreq'},\n {key: 'mar', component: 'local_assessfreq'},\n {key: 'apr', component: 'local_assessfreq'},\n {key: 'may', component: 'local_assessfreq'},\n {key: 'jun', component: 'local_assessfreq'},\n {key: 'jul', component: 'local_assessfreq'},\n {key: 'aug', component: 'local_assessfreq'},\n {key: 'sep', component: 'local_assessfreq'},\n {key: 'oct', component: 'local_assessfreq'},\n {key: 'nov', component: 'local_assessfreq'},\n {key: 'dec', component: 'local_assessfreq'},\n ];\n var stringResult;\n var heatRangeMax;\n var heatRangeMin;\n var colorArray;\n var processModules;\n var heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0};\n\n /**\n * Pick a contrasting text color based on the background color.\n *\n * @param {String} hexcolor A hexcolor value.\n * @return {String} The contrasting color (black or white).\n */\n const getContrast = function (hexcolor) {\n\n if (typeof (hexcolor) === \"undefined\") {\n return '#000000';\n }\n\n // If a leading # is provided, remove it.\n if (hexcolor.slice(0, 1) === '#') {\n hexcolor = hexcolor.slice(1);\n }\n\n // Convert to RGB value.\n var r = parseInt(hexcolor.substr(0,2),16);\n var g = parseInt(hexcolor.substr(2,2),16);\n var b = parseInt(hexcolor.substr(4,2),16);\n\n // Get YIQ ratio.\n var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;\n\n // Check contrast.\n return (yiq >= 128) ? '#000000' : '#FFFFFF';\n };\n\n /**\n * Check how many days in a month code.\n * from https://dzone.com/articles/determining-number-days-month.\n *\n * @method daysInMonth\n * @param {Number} month The month to get the number of days for.\n * @param {Number} year The year to get the number of days for.\n */\n const daysInMonth = function (month, year) {\n return 32 - new Date(year, month, 32).getDate();\n };\n\n /**\n * Get the heat colors to use in the heat map via Ajax.\n *\n * @method getHeatColors\n */\n const getHeatColors = function () {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_assessfreq_get_heat_colors',\n args: {},\n }], true, false)[0].done(function (response) {\n colorArray = JSON.parse(response);\n resolve(colorArray);\n }).fail(function () {\n reject(new Error('Failed to get heat colors'));\n });\n });\n };\n\n /**\n * Get the event names that we are processing.\n *\n * @method getProcessEvents\n */\n const getProcessModules = function () {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_assessfreq_get_process_modules',\n args: {},\n }], true, false)[0].done(function (response) {\n processModules = JSON.parse(response);\n resolve(processModules);\n }).fail(function () {\n reject(new Error('Failed to get process events'));\n });\n });\n };\n\n /**\n * Calculate the min and max values to use in the heatmap.\n *\n * @method daysInMonth\n * @param {Object} eventArray All the event count for the heatmap.\n * @param {Object} dateObj Date details.\n */\n const calcHeatRange = function (eventArray, dateObj) {\n return new Promise((resolve) => {\n\n // Resolve early if there are no events.\n if (typeof (eventArray) === \"undefined\") {\n heatRangeMax = 0;\n heatRangeMin = 0;\n\n resolve(eventArray);\n }\n // If scheduled tasks have not run yet we may not have any data.\n let eventArrayLength = Object.keys(eventArray).length;\n if ((eventArrayLength > 0) && (eventArray[dateObj.year] !== \"undefined\")) {\n let eventcount = new Array;\n let year = eventArray[dateObj.year];\n\n // Iterate through all the event counts.\n // This code looks nasty but there is only 366 days in a year.\n for (let i = 0; i < 12; i++) {\n if (typeof year[i] !== \"undefined\") {\n let month = year[i];\n for (let j = 0; j < 32; j++) {\n if (typeof month[j] !== \"undefined\") {\n eventcount.push(month[j].number);\n }\n }\n }\n }\n\n // Get min and max values to calculate heat spread.\n heatRangeMax = Math.max(...eventcount);\n heatRangeMin = Math.min(...eventcount);\n } else {\n heatRangeMax = 0;\n heatRangeMin = 0;\n }\n\n resolve(eventArray);\n });\n };\n\n /**\n * Translate assessment frequency to a heat value.\n *\n * @method getHeat\n * @param {Number} eventCount The count to get the heat value.\n * @return {Number} heat The heat value.\n */\n const getHeat = function (eventCount) {\n let scaleMin = 1;\n\n if (eventCount == heatRangeMin) {\n return scaleMin;\n }\n\n const scaleRange = 5; // 0 - 5 steps.\n const localRange = heatRangeMax - heatRangeMin;\n const localPercent = (eventCount - heatRangeMin) / localRange;\n let heat = Math.round((localPercent * scaleRange) + 1);\n\n // Clamp values.\n if (heat < 1) {\n heat = 1;\n }\n\n if (heat > 6) {\n heat = 6;\n }\n\n return heat;\n };\n\n /**\n * Get the events to display in the calendar via ajax call.\n *\n * @method getEvents\n *\n * @param {Object} args The arguments to pass to the ajax call.\n * @param {Number} args.year The year to get the events for.\n * @param {String} args.metric The metric to get the events for.\n * @param {Array} args.modules The modules to get the events for.\n *\n * @return {Promise}\n */\n const getEvents = function ({year, metric, modules}) {\n return new Promise((resolve, reject) => {\n let args = {\n year: year,\n metric: metric,\n modules: modules\n };\n let jsonArgs = JSON.stringify(args);\n\n // Get the events to use in the mapping.\n Ajax.call([{\n methodname: 'local_assessfreq_get_frequency',\n args: {\n jsondata: jsonArgs\n },\n }])[0].done((response) => {\n eventArray = JSON.parse(response);\n resolve(eventArray);\n }).fail(() => {\n reject(new Error('Failed to get events'));\n });\n });\n };\n\n /**\n * Get the events for a particular month and year.\n *\n * @param {Number} year The year to get the number of days for.\n * @param {Number} month The month to get the number of days for.\n * @return {Array} monthevents The events for the supplied month.\n */\n const getMonthEvents = function (year, month) {\n let monthevents;\n\n if ((typeof eventArray[year] !== \"undefined\") && (typeof eventArray[year][month] !== \"undefined\")) {\n monthevents = eventArray[year][month];\n }\n\n return monthevents;\n };\n\n /**\n * Create the table structure for the calendar months.\n *\n * @param {Object} args The arguments to pass to the ajax call.\n * @param {Number} args.year The year to get the events for.\n * @param {Number} args.startMonth The month to start the calendar\n * @param {Number} args.endMonth The month to end the calendar\n *\n * @return {Promise}\n */\n const createTables = function ({year, startMonth, endMonth}) {\n return new Promise((resolve, reject) => {\n let calendarContainer = document.createElement('div');\n let month = startMonth;\n\n // Itterate through and build are tables.\n for (let i = startMonth; i <= endMonth; i++) {\n // Setup some elements.\n let container = document.createElement('div');\n container.classList.add('local-assessfreq-month');\n let table = document.createElement('table');\n table.classList.add('table-striped');\n let thead = document.createElement('thead');\n let tbody = document.createElement('tbody');\n tbody.id = 'calendar-body-' + i;\n let monthRow = document.createElement('tr');\n let dayrow = document.createElement('tr');\n let monthHeader = document.createElement('th');\n monthHeader.colSpan = 7;\n monthHeader.innerHTML = stringResult[(7 + month)];\n\n for (let j = 0; j < 7; j++) {\n let dayHeader = document.createElement('th');\n dayHeader.innerHTML = stringResult[j];\n dayrow.appendChild(dayHeader);\n }\n\n // Construct the table.\n monthRow.appendChild(monthHeader);\n\n thead.appendChild(monthRow);\n thead.appendChild(dayrow);\n\n table.appendChild(thead);\n table.appendChild(tbody);\n\n container.appendChild(table);\n\n // Add to parent.\n calendarContainer.appendChild(container);\n\n // Increment variables.\n month++;\n }\n\n if ((typeof year === 'undefined') || (typeof startMonth === 'undefined') || (typeof endMonth === 'undefined')) {\n reject(Error('Failed to create calendar tables.'));\n } else {\n const resultObj = {\n calendarContainer : calendarContainer,\n year : year,\n startMonth : startMonth\n };\n resolve(resultObj);\n }\n });\n };\n\n /**\n * Generate the tooltip HTML.\n *\n * @param {Object} dayArray The details of the events for that day/\n * @return {String} tipHTML The HTML for the tooltip.\n */\n const getTooltip = function (dayArray) {\n let tipHTML = '';\n\n for (let [key, value] of Object.entries(dayArray)) {\n tipHTML += '' + processModules[key] + ': ' + value + '
';\n }\n\n return tipHTML;\n };\n\n /**\n * Generate calendar markup for the month.\n *\n * @param {Object} table The base table to populate.\n * @param {Number} year The year to generate calendar for.\n * @param {Number} month The monthe to generate calendar for.\n */\n const populateCalendarDays = function (table, year, month) {\n let firstDay = (new Date(year, month)).getDay(); // Get the starting day of the month.\n let monthEvents = getMonthEvents(year, (month + 1)); // We add one due to month diferences between PHP and JS.\n let date = 1; // Creating all cells.\n\n for (let i = 0; i < 6; i++) {\n let row = document.createElement(\"tr\"); // Creates a table row.\n\n // Creating individual cells, filing them up with data.\n for (let j = 0; j < 7; j++) {\n if (i === 0 && j < firstDay) {\n var cell = document.createElement(\"td\");\n var cellText = document.createTextNode(\"\");\n cell.dataset.event = 'false';\n } else if (date > daysInMonth(month, year)) { // Break if we have generated all the days for this month.\n break;\n } else {\n cell = document.createElement(\"td\");\n cellText = document.createTextNode(date);\n if ((typeof monthEvents !== \"undefined\") && (monthEvents.hasOwnProperty(date))) {\n let heat = getHeat(monthEvents[date]['number']);\n\n if (heatRangeScale[heat] == 0 || heatRangeScale[heat] > monthEvents[date]['number']) {\n heatRangeScale[heat] = monthEvents[date]['number'];\n }\n\n cell.style.backgroundColor = colorArray[heat];\n cell.style.color = getContrast(colorArray[heat]);\n\n // Add tooltip to cell.\n cell.dataset.toggle = 'tooltip';\n cell.dataset.html = 'true';\n cell.dataset.event = 'true';\n cell.dataset.date = year + '-' + (month + 1) + '-' + date;\n cell.title = getTooltip(monthEvents[date]);\n cell.style.cursor = \"pointer\";\n }\n date++;\n }\n\n cell.appendChild(cellText);\n row.appendChild(cell);\n }\n table.appendChild(row); // Appending each row into calendar body.\n }\n };\n\n /**\n * Controls the population of the calendar in to the base tables.\n *\n * @param {Object} args The arguments to pass to the ajax call.\n * @param {Object} args.calendarContainer The container to populate the calendar into.\n * @param {Number} args.year The year to get the events for.\n * @param {Number} args.startMonth The month to start the calendar\n *\n * @return {Promise}\n */\n const populateCalendar = function ({calendarContainer, year, startMonth}) {\n return new Promise((resolve, reject) => {\n // Get the table boodies.\n let tables = calendarContainer.getElementsByTagName(\"tbody\");\n let month = startMonth;\n\n // For each table body populate with calendar.\n for (var i = 0; i < tables.length; i++) {\n let table = tables[i];\n populateCalendarDays(table, year, month);\n month++;\n }\n\n if (typeof calendarContainer === 'undefined') {\n reject(Error('Failed to populate calendar tables.'));\n } else {\n resolve(calendarContainer);\n }\n });\n };\n\n /**\n * Create the heatmap scale for the calendar.\n *\n * @method createHeatScale\n */\n Calendar.createHeatScale = function () {\n return new Promise((resolve) => {\n let table = document.createElement('table');\n let tbody = document.createElement('tbody');\n let trow = document.createElement('tr');\n\n for (var i = 1; i < 7; i++) {\n if (heatRangeScale[i] !== 0) {\n let cell = document.createElement('td');\n let cellText = document.createTextNode(heatRangeScale[i] + '+');\n\n cell.appendChild(cellText);\n cell.style.backgroundColor = colorArray[i];\n cell.style.color = getContrast(colorArray[i]);\n\n trow.appendChild(cell);\n }\n }\n\n tbody.appendChild(trow);\n table.appendChild(tbody);\n\n // Reset heat range scale.\n heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0};\n\n resolve(table);\n });\n };\n\n /**\n * Initialise method for report calendar heatmap creation.\n *\n * @param {Number} year The year to generate the heatmap for.\n * @param {Number} startMonth The month to start with for the heatmap calendar.\n * @param {Number} endMonth The month to end with for the heatmap calendar.\n * @param {String} metric The type of metric to display, 'students' or 'aseess'.\n * @param {Array} modules The modules to display in the heatamp.\n * @return {Promise}\n */\n Calendar.generate = function (year, startMonth, endMonth, metric, modules) {\n return new Promise((resolve, reject) => {\n const dateObj = {\n year : year,\n startMonth : startMonth,\n endMonth : endMonth\n };\n\n const eventObj = {\n year : year,\n metric : metric,\n modules : modules\n };\n\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n Notification.exception(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n stringResult = stringReturn;\n return eventObj;\n })\n .then(getEvents)\n .then((eventArray) => {\n calcHeatRange(eventArray, dateObj);\n })\n .then(getHeatColors)\n .then(getProcessModules)\n .then(() => {\n return dateObj;\n })\n .then(createTables) // Create tables for calendar.\n .then(populateCalendar)\n .then((calendarHTML) => { // Return the result of the generate function.\n if (typeof calendarHTML !== 'undefined') {\n resolve(calendarHTML);\n } else {\n reject(Error('Could not generate calendar'));\n }\n });\n });\n\n };\n\n return Calendar;\n});\n"],"names":["define","Str","Notification","Ajax","Calendar","eventArray","stringArr","key","component","stringResult","heatRangeMax","heatRangeMin","colorArray","processModules","heatRangeScale","getContrast","hexcolor","slice","parseInt","substr","daysInMonth","month","year","Date","getDate","getHeatColors","Promise","resolve","reject","call","methodname","args","done","response","JSON","parse","fail","Error","getProcessModules","getHeat","eventCount","localPercent","heat","Math","round","getEvents","metric","modules","jsonArgs","stringify","jsondata","createTables","startMonth","endMonth","calendarContainer","document","createElement","i","container","classList","add","table","thead","tbody","id","monthRow","dayrow","monthHeader","colSpan","innerHTML","j","dayHeader","appendChild","getTooltip","dayArray","tipHTML","value","Object","entries","populateCalendarDays","firstDay","getDay","monthEvents","monthevents","getMonthEvents","date","row","cell","cellText","createTextNode","dataset","event","hasOwnProperty","style","backgroundColor","color","toggle","html","title","cursor","populateCalendar","tables","getElementsByTagName","length","createHeatScale","trow","generate","dateObj","eventObj","get_strings","catch","exception","then","stringReturn","keys","eventcount","Array","push","number","max","min","calcHeatRange","calendarHTML"],"mappings":";;;;;;AAsBAA,mCAAO,CAAC,WAAY,oBAAqB,cAAc,SAAUC,IAAKC,aAAcC,UAK5EC,SAAW,GACXC,WAAa,SACXC,UAAY,CACd,CAACC,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,YACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,oBACxB,CAACD,IAAK,MAAOC,UAAW,yBAExBC,aACAC,aACAC,aACAC,WACAC,eACAC,eAAiB,GAAM,IAAQ,IAAQ,IAAQ,IAAQ,IAAQ,SAQ7DC,YAAc,SAAUC,sBAEA,IAAdA,SACD,WAIkB,MAAzBA,SAASC,MAAM,EAAG,KAClBD,SAAWA,SAASC,MAAM,KASd,IALRC,SAASF,SAASG,OAAO,EAAE,GAAG,IAKV,IAJpBD,SAASF,SAASG,OAAO,EAAE,GAAG,IAIE,IAHhCD,SAASF,SAASG,OAAO,EAAE,GAAG,KAGU,KAGjC,IAAO,UAAY,YAWhCC,YAAc,SAAUC,MAAOC,aAC1B,GAAK,IAAIC,KAAKD,KAAMD,MAAO,IAAIG,WAQpCC,cAAgB,kBACX,IAAIC,SAAQ,CAACC,QAASC,UACzBzB,KAAK0B,KAAK,CAAC,CACPC,WAAY,mCACZC,KAAM,MACN,GAAM,GAAO,GAAGC,MAAK,SAAUC,UAC/BrB,WAAasB,KAAKC,MAAMF,UACxBN,QAAQf,eACTwB,MAAK,WACJR,OAAO,IAAIS,MAAM,qCAUvBC,kBAAoB,kBACf,IAAIZ,SAAQ,CAACC,QAASC,UACzBzB,KAAK0B,KAAK,CAAC,CACPC,WAAY,uCACZC,KAAM,MACN,GAAM,GAAO,GAAGC,MAAK,SAAUC,UAC/BpB,eAAiBqB,KAAKC,MAAMF,UAC5BN,QAAQd,mBACTuB,MAAK,WACJR,OAAO,IAAIS,MAAM,wCA4DvBE,QAAU,SAAUC,eAGlBA,YAAc7B,oBAFH,QAQT8B,cAAgBD,WAAa7B,eADhBD,aAAeC,kBAE9B+B,KAAOC,KAAKC,MAHG,EAGIH,aAA6B,UAGhDC,KAAO,IACPA,KAAO,GAGPA,KAAO,IACPA,KAAO,GAGJA,MAeLG,UAAY,mBAAUvB,KAACA,KAADwB,OAAOA,OAAPC,QAAeA,qBAChC,IAAIrB,SAAQ,CAACC,QAASC,cACrBG,KAAO,CACPT,KAAMA,KACNwB,OAAQA,OACRC,QAASA,SAETC,SAAWd,KAAKe,UAAUlB,MAG9B5B,KAAK0B,KAAK,CAAC,CACPC,WAAY,iCACZC,KAAM,CACFmB,SAAUF,aAEd,GAAGhB,MAAMC,WACT5B,WAAa6B,KAAKC,MAAMF,UACxBN,QAAQtB,eACT+B,MAAK,KACJR,OAAO,IAAIS,MAAM,gCAgCvBc,aAAe,oBAAU7B,KAACA,KAAD8B,WAAOA,WAAPC,SAAmBA,uBACvC,IAAI3B,SAAQ,CAACC,QAASC,cACrB0B,kBAAoBC,SAASC,cAAc,OAC3CnC,MAAQ+B,eAGP,IAAIK,EAAIL,WAAYK,GAAKJ,SAAUI,IAAK,KAErCC,UAAYH,SAASC,cAAc,OACvCE,UAAUC,UAAUC,IAAI,8BACpBC,MAAQN,SAASC,cAAc,SACnCK,MAAMF,UAAUC,IAAI,qBAChBE,MAAQP,SAASC,cAAc,SAC/BO,MAAQR,SAASC,cAAc,SACnCO,MAAMC,GAAK,iBAAmBP,MAC1BQ,SAAWV,SAASC,cAAc,MAClCU,OAASX,SAASC,cAAc,MAChCW,YAAcZ,SAASC,cAAc,MACzCW,YAAYC,QAAU,EACtBD,YAAYE,UAAY5D,aAAc,EAAIY,WAErC,IAAIiD,EAAI,EAAGA,EAAI,EAAGA,IAAK,KACpBC,UAAYhB,SAASC,cAAc,MACvCe,UAAUF,UAAY5D,aAAa6D,GACnCJ,OAAOM,YAAYD,WAIvBN,SAASO,YAAYL,aAErBL,MAAMU,YAAYP,UAClBH,MAAMU,YAAYN,QAElBL,MAAMW,YAAYV,OAClBD,MAAMW,YAAYT,OAElBL,UAAUc,YAAYX,OAGtBP,kBAAkBkB,YAAYd,WAG9BrC,gBAGiB,IAATC,WAAgD,IAAf8B,iBAAoD,IAAbC,SAChFzB,OAAOS,MAAM,0CACV,CAMHV,QALkB,CACd2B,kBAAoBA,kBACpBhC,KAAOA,KACP8B,WAAaA,kBAavBqB,WAAa,SAAUC,cACrBC,QAAU,OAET,IAAKpE,IAAKqE,SAAUC,OAAOC,QAAQJ,UACpCC,SAAW,WAAa9D,eAAeN,KAAO,cAAgBqE,MAAQ,eAGnED,SAULI,qBAAuB,SAAUlB,MAAOvC,KAAMD,WAC5C2D,SAAY,IAAIzD,KAAKD,KAAMD,OAAQ4D,SACnCC,YAvGe,SAAU5D,KAAMD,WAC/B8D,wBAE6B,IAArB9E,WAAWiB,YAA8D,IAA5BjB,WAAWiB,MAAMD,SACtE8D,YAAc9E,WAAWiB,MAAMD,QAG5B8D,YAgGWC,CAAe9D,KAAOD,MAAQ,GAC5CgE,KAAO,MAEN,IAAI5B,EAAI,EAAGA,EAAI,EAAGA,IAAK,KACpB6B,IAAM/B,SAASC,cAAc,UAG5B,IAAIc,EAAI,EAAGA,EAAI,EAAGA,IAAK,IACd,IAANb,GAAWa,EAAIU,SAAU,KACrBO,KAAOhC,SAASC,cAAc,MAC9BgC,SAAWjC,SAASkC,eAAe,IACvCF,KAAKG,QAAQC,MAAQ,YAClB,CAAA,GAAIN,KAAOjE,YAAYC,MAAOC,eAGjCiE,KAAOhC,SAASC,cAAc,MAC9BgC,SAAWjC,SAASkC,eAAeJ,WACP,IAAhBH,aAAiCA,YAAYU,eAAeP,MAAQ,KACxE3C,KAAOH,QAAQ2C,YAAYG,MAAZ,SAES,GAAxBvE,eAAe4B,OAAc5B,eAAe4B,MAAQwC,YAAYG,MAAZ,UACpDvE,eAAe4B,MAAQwC,YAAYG,MAAZ,QAG3BE,KAAKM,MAAMC,gBAAkBlF,WAAW8B,MACxC6C,KAAKM,MAAME,MAAQhF,YAAYH,WAAW8B,OAG1C6C,KAAKG,QAAQM,OAAS,UACtBT,KAAKG,QAAQO,KAAO,OACpBV,KAAKG,QAAQC,MAAQ,OACrBJ,KAAKG,QAAQL,KAAO/D,KAAO,KAAOD,MAAQ,GAAK,IAAMgE,KACrDE,KAAKW,MAAQzB,WAAWS,YAAYG,OACpCE,KAAKM,MAAMM,OAAS,UAExBd,OAGJE,KAAKf,YAAYgB,UACjBF,IAAId,YAAYe,MAEpB1B,MAAMW,YAAYc,OAcpBc,iBAAmB,oBAAU9C,kBAACA,kBAADhC,KAAoBA,KAApB8B,WAA0BA,yBAClD,IAAI1B,SAAQ,CAACC,QAASC,cAErByE,OAAS/C,kBAAkBgD,qBAAqB,SAChDjF,MAAQ+B,eAGP,IAAIK,EAAI,EAAGA,EAAI4C,OAAOE,OAAQ9C,IAAK,KAChCI,MAAQwC,OAAO5C,GACnBsB,qBAAqBlB,MAAOvC,KAAMD,OAClCA,aAG6B,IAAtBiC,kBACP1B,OAAOS,MAAM,wCAEbV,QAAQ2B,8BAUpBlD,SAASoG,gBAAkB,kBAChB,IAAI9E,SAASC,cACZkC,MAAQN,SAASC,cAAc,SAC/BO,MAAQR,SAASC,cAAc,SAC/BiD,KAAOlD,SAASC,cAAc,UAE7B,IAAIC,EAAI,EAAGA,EAAI,EAAGA,OACO,IAAtB3C,eAAe2C,GAAU,KACrB8B,KAAOhC,SAASC,cAAc,MAC9BgC,SAAWjC,SAASkC,eAAe3E,eAAe2C,GAAK,KAE3D8B,KAAKf,YAAYgB,UACjBD,KAAKM,MAAMC,gBAAkBlF,WAAW6C,GACxC8B,KAAKM,MAAME,MAAQhF,YAAYH,WAAW6C,IAE1CgD,KAAKjC,YAAYe,MAIzBxB,MAAMS,YAAYiC,MAClB5C,MAAMW,YAAYT,OAGlBjD,eAAiB,GAAM,IAAQ,IAAQ,IAAQ,IAAQ,IAAQ,GAE/Da,QAAQkC,WAchBzD,SAASsG,SAAW,SAAUpF,KAAM8B,WAAYC,SAAUP,OAAQC,gBACvD,IAAIrB,SAAQ,CAACC,QAASC,gBACnB+E,QAAU,CACZrF,KAAOA,KACP8B,WAAaA,WACbC,SAAWA,UAGTuD,SAAW,CACbtF,KAAOA,KACPwB,OAASA,OACTC,QAAUA,SAGd9C,IAAI4G,YAAYvG,WAAWwG,OAAM,KAC7B5G,aAAa6G,UAAU,IAAI1E,MAAM,8BAElC2E,MAAKC,eACJxG,aAAewG,aACRL,YAEVI,KAAKnE,WACLmE,MAAM3G,cAxWO,SAAUA,WAAYsG,SACjC,IAAIjF,SAASC,kBAGY,IAAhBtB,aACRK,aAAe,EACfC,aAAe,EAEfgB,QAAQtB,aAGWwE,OAAOqC,KAAK7G,YAAYkG,OACvB,GAAoC,cAA7BlG,WAAWsG,QAAQrF,MAAwB,KAClE6F,WAAa,IAAIC,MACjB9F,KAAOjB,WAAWsG,QAAQrF,UAIzB,IAAImC,EAAI,EAAGA,EAAI,GAAIA,YACG,IAAZnC,KAAKmC,GAAoB,KAC5BpC,MAAQC,KAAKmC,OACZ,IAAIa,EAAI,EAAGA,EAAI,GAAIA,SACI,IAAbjD,MAAMiD,IACb6C,WAAWE,KAAKhG,MAAMiD,GAAGgD,QAOzC5G,aAAeiC,KAAK4E,OAAOJ,YAC3BxG,aAAegC,KAAK6E,OAAOL,iBAE3BzG,aAAe,EACfC,aAAe,EAGnBgB,QAAQtB,eAoUJoH,CAAcpH,WAAYsG,YAE7BK,KAAKvF,eACLuF,KAAK1E,mBACL0E,MAAK,IACKL,UAEVK,KAAK7D,cACL6D,KAAKZ,kBACLY,MAAMU,oBACyB,IAAjBA,aACP/F,QAAQ+F,cAER9F,OAAOS,MAAM,uCAOtBjC"} \ No newline at end of file diff --git a/amd/build/chart_data.min.js b/amd/build/chart_data.min.js deleted file mode 100644 index 19594e64..00000000 --- a/amd/build/chart_data.min.js +++ /dev/null @@ -1,10 +0,0 @@ -define("local_assessfreq/chart_data",["exports","core/fragment","core/notification","core/str","core/templates"],(function(_exports,_fragment,_notification,Str,_templates){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * Chart data JS module. - * - * @module local_assessfreq/char_data - * @copyright 2020 Guillermo Gomez - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */let cards,contextId,fragment,template;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.getCardCharts=void 0,_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str),_templates=_interopRequireDefault(_templates);_exports.getCardCharts=(quizId,hoursFilter,yearSelect)=>{cards.forEach((cardData=>{let cardElement=document.getElementById(cardData.cardId),spinner=cardElement.getElementsByClassName("overlay-icon-container")[0],chartBody=cardElement.getElementsByClassName("chart-body")[0],values={call:cardData.call};hoursFilter&&(values.hoursahead=hoursFilter[0],values.hoursbehind=hoursFilter[1]),quizId&&(values.quiz=quizId),yearSelect&&(values.year=yearSelect);let params={data:JSON.stringify(values)};spinner.classList.remove("hide"),_fragment.default.loadFragment("local_assessfreq",fragment,contextId,params).done((response=>{let resObj=JSON.parse(response);if(!0===resObj.hasdata){let context={withtable:!0,chartdata:JSON.stringify(resObj.chart)};return void 0!==cardData.aspect&&(context.aspect=cardData.aspect),void _templates.default.render(template,context).done(((html,js)=>{spinner.classList.add("hide"),_templates.default.replaceNodeContents(chartBody,html,js)})).fail((()=>{_notification.default.exception(new Error("Failed to load chart template."))}))}Str.get_string("nodata","local_assessfreq").then((str=>{const noDatastr=document.createElement("h3");noDatastr.innerHTML=str,chartBody.innerHTML=noDatastr.outerHTML,spinner.classList.add("hide")})).catch((()=>{_notification.default.exception(new Error("Failed to load string: nodata"))}))})).fail((()=>{_notification.default.exception(new Error("Failed to load card."))}))}))};_exports.init=(cardsArray,contextIdChart,fragmentChart,templateChart)=>{cards=cardsArray,contextId=contextIdChart,fragment=fragmentChart,template=templateChart}})); - -//# sourceMappingURL=chart_data.min.js.map \ No newline at end of file diff --git a/amd/build/chart_data.min.js.map b/amd/build/chart_data.min.js.map deleted file mode 100644 index 2dbb84b3..00000000 --- a/amd/build/chart_data.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"chart_data.min.js","sources":["../src/chart_data.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module local_assessfreq/char_data\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport * as Str from 'core/str';\nimport Templates from 'core/templates';\n\n/**\n * Module level variables.\n */\nlet cards;\nlet contextId;\nlet fragment;\nlet template;\n\n/**\n * For each of the cards on the dashboard get their corresponding chart data.\n * Data is based on the year variable from the corresponding dropdown.\n * Chart data is loaded via ajax.\n *\n * @param {int|null} quizId The quiz Id.\n * @param {array|null} hoursFilter Array with hour ahead or behind preference.\n * @param {int|null} yearSelect Year selected.\n */\nexport const getCardCharts = (quizId, hoursFilter, yearSelect) => {\n cards.forEach((cardData) => {\n let cardElement = document.getElementById(cardData.cardId);\n let spinner = cardElement.getElementsByClassName('overlay-icon-container')[0];\n let chartBody = cardElement.getElementsByClassName('chart-body')[0];\n let values = {'call': cardData.call};\n // Add values to Object depending on dashboard type.\n if (hoursFilter) {\n values.hoursahead = hoursFilter[0];\n values.hoursbehind = hoursFilter[1];\n }\n if (quizId) {\n values.quiz = quizId;\n }\n if (yearSelect) {\n values.year = yearSelect;\n }\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show sinner if not already shown.\n Fragment.loadFragment('local_assessfreq', fragment, contextId, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata === true) {\n let context = {\n 'withtable': true, 'chartdata': JSON.stringify(resObj.chart)\n };\n if (typeof cardData.aspect !== 'undefined') {\n context.aspect = cardData.aspect;\n }\n Templates.render(template, context).done((html, js) => {\n spinner.classList.add('hide'); // Hide spinner if not already hidden.\n // Load card body.\n Templates.replaceNodeContents(chartBody, html, js);\n }).fail(() => {\n Notification.exception(new Error('Failed to load chart template.'));\n return;\n });\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n chartBody.innerHTML = noDatastr.outerHTML;\n spinner.classList.add('hide'); // Hide spinner if not already hidden.\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load card.'));\n return;\n });\n });\n};\n\n/**\n * Initialise method for table handler.\n *\n * @param {array} cardsArray Cards array.\n * @param {int} contextIdChart The context id.\n * @param {string} fragmentChart Fragment name.\n * @param {string} templateChart Template name.\n */\nexport const init = (cardsArray, contextIdChart, fragmentChart, templateChart) => {\n cards = cardsArray;\n contextId = contextIdChart;\n fragment = fragmentChart;\n template = templateChart;\n};\n"],"names":["cards","contextId","fragment","template","quizId","hoursFilter","yearSelect","forEach","cardData","cardElement","document","getElementById","cardId","spinner","getElementsByClassName","chartBody","values","call","hoursahead","hoursbehind","quiz","year","params","JSON","stringify","classList","remove","loadFragment","done","response","resObj","parse","hasdata","context","chart","aspect","render","html","js","add","replaceNodeContents","fail","exception","Error","Str","get_string","then","str","noDatastr","createElement","innerHTML","outerHTML","catch","cardsArray","contextIdChart","fragmentChart","templateChart"],"mappings":";;;;;;;SA+BIA,MACAC,UACAC,SACAC,w6BAWyB,CAACC,OAAQC,YAAaC,cAC/CN,MAAMO,SAASC,eACPC,YAAcC,SAASC,eAAeH,SAASI,QAC/CC,QAAUJ,YAAYK,uBAAuB,0BAA0B,GACvEC,UAAYN,YAAYK,uBAAuB,cAAc,GAC7DE,OAAS,MAASR,SAASS,MAE3BZ,cACAW,OAAOE,WAAab,YAAY,GAChCW,OAAOG,YAAcd,YAAY,IAEjCD,SACAY,OAAOI,KAAOhB,QAEdE,aACAU,OAAOK,KAAOf,gBAEdgB,OAAS,MAASC,KAAKC,UAAUR,SAErCH,QAAQY,UAAUC,OAAO,0BAChBC,aAAa,mBAAoBzB,SAAUD,UAAWqB,QAC1DM,MAAMC,eACCC,OAASP,KAAKQ,MAAMF,cACD,IAAnBC,OAAOE,QAAkB,KACrBC,QAAU,YACG,YAAmBV,KAAKC,UAAUM,OAAOI,oBAE3B,IAApB1B,SAAS2B,SAChBF,QAAQE,OAAS3B,SAAS2B,gCAEpBC,OAAOjC,SAAU8B,SAASL,MAAK,CAACS,KAAMC,MAC5CzB,QAAQY,UAAUc,IAAI,2BAEZC,oBAAoBzB,UAAWsB,KAAMC,OAChDG,MAAK,2BACSC,UAAU,IAAIC,MAAM,sCAKrCC,IAAIC,WAAW,SAAU,oBAAoBC,MAAMC,YACzCC,UAAYtC,SAASuC,cAAc,MACzCD,UAAUE,UAAYH,IACtBhC,UAAUmC,UAAYF,UAAUG,UAChCtC,QAAQY,UAAUc,IAAI,WAEvBa,OAAM,2BACQV,UAAU,IAAIC,MAAM,wCAG1CF,MAAK,2BACSC,UAAU,IAAIC,MAAM,8CAc7B,CAACU,WAAYC,eAAgBC,cAAeC,iBAC5DxD,MAAQqD,WACRpD,UAAYqD,eACZpD,SAAWqD,cACXpD,SAAWqD"} \ No newline at end of file diff --git a/amd/build/chart_output_chartjs.min.js b/amd/build/chart_output_chartjs.min.js deleted file mode 100644 index f2ae8904..00000000 --- a/amd/build/chart_output_chartjs.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Chart output for chart.js with custom override for aspect config. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/chart_output_chartjs",["core/chart_output_chartjs"],(function(Output){var ChartOutput={},aspectRatio=!1,rtLegendoptions=!1;return Output.prototype._makeConfig=function(){var config={type:this._getChartType(),data:{labels:this._cleanData(this._chart.getLabels()),datasets:this._makeDatasetsConfig()},options:{title:{display:null!==this._chart.getTitle(),text:this._cleanData(this._chart.getTitle())}}},legendOptions=this._chart.getLegendOptions();return legendOptions&&(config.options.legend=legendOptions),rtLegendoptions&&(config.options.legend=rtLegendoptions),this._chart.getXAxes().forEach(function(axis,i){var axisLabels=axis.getLabels();config.options.scales=config.options.scales||{},config.options.scales.xAxes=config.options.scales.xAxes||[],config.options.scales.xAxes[i]=this._makeAxisConfig(axis,"x",i),null!==axisLabels&&(config.options.scales.xAxes[i].ticks.callback=function(value,index){return axisLabels[index]||""}),config.options.scales.xAxes[i].stacked=this._isStacked()}.bind(this)),this._chart.getYAxes().forEach(function(axis,i){var axisLabels=axis.getLabels();config.options.scales=config.options.scales||{},config.options.scales.yAxes=config.options.scales.yAxes||[],config.options.scales.yAxes[i]=this._makeAxisConfig(axis,"y",i),null!==axisLabels&&(config.options.scales.yAxes[i].ticks.callback=function(value){return axisLabels[parseInt(value,10)]||""}),config.options.scales.yAxes[i].stacked=this._isStacked()}.bind(this)),config.options.tooltips={callbacks:{label:this._makeTooltip.bind(this)}},config.options.maintainAspectRatio=aspectRatio,config},ChartOutput.init=function(chartImage,ChartInst,aspect,legend){aspectRatio=aspect,rtLegendoptions=legend,new Output(chartImage,ChartInst)},ChartOutput})); - -//# sourceMappingURL=chart_output_chartjs.min.js.map \ No newline at end of file diff --git a/amd/build/chart_output_chartjs.min.js.map b/amd/build/chart_output_chartjs.min.js.map deleted file mode 100644 index 65756f59..00000000 --- a/amd/build/chart_output_chartjs.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"chart_output_chartjs.min.js","sources":["../src/chart_output_chartjs.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart output for chart.js with custom override for aspect config.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/chart_output_chartjs'], function (Output) {\n\n /**\n * Module level variables.\n */\n var ChartOutput = {};\n var aspectRatio = false;\n var rtLegendoptions = false;\n\n /**\n * Overrride the config.\n *\n * @protected\n * @return {Object} The axis config.\n */\n Output.prototype._makeConfig = function () {\n var config = {\n type: this._getChartType(),\n data: {\n labels: this._cleanData(this._chart.getLabels()),\n datasets: this._makeDatasetsConfig()\n },\n options: {\n title: {\n display: this._chart.getTitle() !== null,\n text: this._cleanData(this._chart.getTitle())\n }\n }\n };\n var legendOptions = this._chart.getLegendOptions();\n if (legendOptions) {\n config.options.legend = legendOptions;\n }\n\n // Override legend options with those provided at run time.\n if (rtLegendoptions) {\n config.options.legend = rtLegendoptions;\n }\n\n this._chart.getXAxes().forEach(function (axis, i) {\n var axisLabels = axis.getLabels();\n\n config.options.scales = config.options.scales || {};\n config.options.scales.xAxes = config.options.scales.xAxes || [];\n config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i);\n\n if (axisLabels !== null) {\n config.options.scales.xAxes[i].ticks.callback = function (value, index) {\n return axisLabels[index] || '';\n };\n }\n config.options.scales.xAxes[i].stacked = this._isStacked();\n }.bind(this));\n\n this._chart.getYAxes().forEach(function (axis, i) {\n var axisLabels = axis.getLabels();\n\n config.options.scales = config.options.scales || {};\n config.options.scales.yAxes = config.options.scales.yAxes || [];\n config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i);\n\n if (axisLabels !== null) {\n config.options.scales.yAxes[i].ticks.callback = function (value) {\n return axisLabels[parseInt(value, 10)] || '';\n };\n }\n config.options.scales.yAxes[i].stacked = this._isStacked();\n }.bind(this));\n\n config.options.tooltips = {\n callbacks: {\n label: this._makeTooltip.bind(this)\n }\n };\n\n config.options.maintainAspectRatio = aspectRatio;\n\n return config;\n };\n\n /**\n * Get the aspect ratio setting and initialise the chart.\n *\n * @param {string} chartImage The image to replace.\n * @param {object} ChartInst The chart instance.\n * @param {boolean} aspect The aspect ratio.\n * @param {object} legend The legend options.\n */\n ChartOutput.init = function (chartImage, ChartInst, aspect, legend) {\n aspectRatio = aspect;\n rtLegendoptions = legend;\n new Output(chartImage, ChartInst);\n };\n\n return ChartOutput;\n\n});\n"],"names":["define","Output","ChartOutput","aspectRatio","rtLegendoptions","prototype","_makeConfig","config","type","this","_getChartType","data","labels","_cleanData","_chart","getLabels","datasets","_makeDatasetsConfig","options","title","display","getTitle","text","legendOptions","getLegendOptions","legend","getXAxes","forEach","axis","i","axisLabels","scales","xAxes","_makeAxisConfig","ticks","callback","value","index","stacked","_isStacked","bind","getYAxes","yAxes","parseInt","tooltips","callbacks","label","_makeTooltip","maintainAspectRatio","init","chartImage","ChartInst","aspect"],"mappings":";;;;;;AAqBAA,+CAAO,CAAC,8BAA8B,SAAUC,YAKxCC,YAAc,GACdC,aAAc,EACdC,iBAAkB,SAQtBH,OAAOI,UAAUC,YAAc,eACvBC,OAAS,CACTC,KAAMC,KAAKC,gBACXC,KAAM,CACFC,OAAQH,KAAKI,WAAWJ,KAAKK,OAAOC,aACpCC,SAAUP,KAAKQ,uBAEnBC,QAAS,CACLC,MAAO,CACHC,QAAoC,OAA3BX,KAAKK,OAAOO,WACrBC,KAAMb,KAAKI,WAAWJ,KAAKK,OAAOO,eAI1CE,cAAgBd,KAAKK,OAAOU,0BAC5BD,gBACAhB,OAAOW,QAAQO,OAASF,eAIxBnB,kBACAG,OAAOW,QAAQO,OAASrB,sBAGvBU,OAAOY,WAAWC,QAAQ,SAAUC,KAAMC,OACvCC,WAAaF,KAAKb,YAEtBR,OAAOW,QAAQa,OAASxB,OAAOW,QAAQa,QAAU,GACjDxB,OAAOW,QAAQa,OAAOC,MAAQzB,OAAOW,QAAQa,OAAOC,OAAS,GAC7DzB,OAAOW,QAAQa,OAAOC,MAAMH,GAAKpB,KAAKwB,gBAAgBL,KAAM,IAAKC,GAE9C,OAAfC,aACAvB,OAAOW,QAAQa,OAAOC,MAAMH,GAAGK,MAAMC,SAAW,SAAUC,MAAOC,cACtDP,WAAWO,QAAU,KAGpC9B,OAAOW,QAAQa,OAAOC,MAAMH,GAAGS,QAAU7B,KAAK8B,cAChDC,KAAK/B,YAEFK,OAAO2B,WAAWd,QAAQ,SAAUC,KAAMC,OACvCC,WAAaF,KAAKb,YAEtBR,OAAOW,QAAQa,OAASxB,OAAOW,QAAQa,QAAU,GACjDxB,OAAOW,QAAQa,OAAOW,MAAQnC,OAAOW,QAAQa,OAAOW,OAAS,GAC7DnC,OAAOW,QAAQa,OAAOW,MAAMb,GAAKpB,KAAKwB,gBAAgBL,KAAM,IAAKC,GAE9C,OAAfC,aACAvB,OAAOW,QAAQa,OAAOW,MAAMb,GAAGK,MAAMC,SAAW,SAAUC,cAC/CN,WAAWa,SAASP,MAAO,MAAQ,KAGlD7B,OAAOW,QAAQa,OAAOW,MAAMb,GAAGS,QAAU7B,KAAK8B,cAChDC,KAAK/B,OAEPF,OAAOW,QAAQ0B,SAAW,CACtBC,UAAW,CACPC,MAAOrC,KAAKsC,aAAaP,KAAK/B,QAItCF,OAAOW,QAAQ8B,oBAAsB7C,YAE9BI,QAWXL,YAAY+C,KAAO,SAAUC,WAAYC,UAAWC,OAAQ3B,QACxDtB,YAAciD,OACdhD,gBAAkBqB,WACdxB,OAAOiD,WAAYC,YAGpBjD"} \ No newline at end of file diff --git a/amd/build/course_selector.min.js b/amd/build/course_selector.min.js index b3a9f75b..ea4541ef 100644 --- a/amd/build/course_selector.min.js +++ b/amd/build/course_selector.min.js @@ -7,6 +7,6 @@ * @copyright2016 Frédéric Massart - FMCorz.net * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("local_assessfreq/course_selector",["core/ajax","core/notification"],(function(Ajax,Notification){var CourseSelector={transport:function(selector,query,callback){Ajax.call([{methodname:"local_assessfreq_get_courses",args:{query:query}}])[0].then((response=>{let courseArray=JSON.parse(response);callback(courseArray)})).fail((()=>{Notification.exception(new Error("Failed to get events"))}))},processResults:function(selector,results){let options=[];return results.forEach((element=>{options.push({value:element.id,label:element.fullname})})),options}};return CourseSelector})); +define("local_assessfreq/course_selector",["core/ajax","core/notification"],(function(Ajax,Notification){let CourseSelector={transport:function(selector,query,callback){Ajax.call([{methodname:"local_assessfreq_get_courses",args:{query:query}}])[0].then((response=>{let courseArray=JSON.parse(response);callback(courseArray)}))},processResults:function(selector,results){let options=[];return results.forEach((element=>{options.push({value:element.id,label:element.fullname})})),options}};return CourseSelector})); //# sourceMappingURL=course_selector.min.js.map \ No newline at end of file diff --git a/amd/build/course_selector.min.js.map b/amd/build/course_selector.min.js.map index 21d71eb6..fe26f533 100644 --- a/amd/build/course_selector.min.js.map +++ b/amd/build/course_selector.min.js.map @@ -1 +1 @@ -{"version":3,"file":"course_selector.min.js","sources":["../src/course_selector.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.If not, see .\n\n/**\n * Frameworks datasource.\n *\n * This module is compatible with core/form-autocomplete.\n *\n * @packagetool_lpmigrate\n * @copyright2016 Frédéric Massart - FMCorz.net\n * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/ajax', 'core/notification'], function (Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n var CourseSelector = {};\n\n /**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} callback A callback function receiving an array of results.\n * @return {Void}\n */\n CourseSelector.transport = function (selector, query, callback) {\n Ajax.call([{\n methodname: 'local_assessfreq_get_courses',\n args: {\n query: query\n },\n }])[0].then((response) => {\n let courseArray = JSON.parse(response);\n callback(courseArray);\n }).fail(() => {\n Notification.exception(new Error('Failed to get events'));\n });\n };\n\n /**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\n CourseSelector.processResults = function (selector, results) {\n let options = [];\n results.forEach((element) => {\n options.push({\n value: element.id,\n label: element.fullname\n });\n });\n\n return options;\n };\n\n return CourseSelector;\n});\n"],"names":["define","Ajax","Notification","CourseSelector","selector","query","callback","call","methodname","args","then","response","courseArray","JSON","parse","fail","exception","Error","results","options","forEach","element","push","value","id","label","fullname"],"mappings":";;;;;;;;;AAyBAA,0CAAO,CAAC,YAAa,sBAAsB,SAAUC,KAAMC,kBAKnDC,eAAiB,CAUrBA,UAA2B,SAAUC,SAAUC,MAAOC,UAClDL,KAAKM,KAAK,CAAC,CACPC,WAAY,+BACZC,KAAM,CACFJ,MAAOA,UAEX,GAAGK,MAAMC,eACLC,YAAcC,KAAKC,MAAMH,UAC7BL,SAASM,gBACVG,MAAK,KACJb,aAAac,UAAU,IAAIC,MAAM,6BAWzCd,eAAgC,SAAUC,SAAUc,aAC5CC,QAAU,UACdD,QAAQE,SAASC,UACbF,QAAQG,KAAK,CACTC,MAAOF,QAAQG,GACfC,MAAOJ,QAAQK,cAIhBP,iBAGJhB"} \ No newline at end of file +{"version":3,"file":"course_selector.min.js","sources":["../src/course_selector.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.If not, see .\n\n/**\n * Frameworks datasource.\n *\n * This module is compatible with core/form-autocomplete.\n *\n * @packagetool_lpmigrate\n * @copyright2016 Frédéric Massart - FMCorz.net\n * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/ajax', 'core/notification'], function (Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n let CourseSelector = {};\n\n /**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} callback A callback function receiving an array of results.\n */\n CourseSelector.transport = function(selector, query, callback) {\n Ajax.call([{\n methodname: 'local_assessfreq_get_courses',\n args: {\n query: query\n },\n }])[0].then((response) => {\n let courseArray = JSON.parse(response);\n // eslint-disable-next-line promise/no-callback-in-promise\n callback(courseArray);\n });\n };\n\n /**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\n CourseSelector.processResults = function (selector, results) {\n let options = [];\n results.forEach((element) => {\n options.push({\n value: element.id,\n label: element.fullname\n });\n });\n\n return options;\n };\n\n return CourseSelector;\n});\n"],"names":["define","Ajax","Notification","CourseSelector","selector","query","callback","call","methodname","args","then","response","courseArray","JSON","parse","results","options","forEach","element","push","value","id","label","fullname"],"mappings":";;;;;;;;;AAyBAA,0CAAO,CAAC,YAAa,sBAAsB,SAAUC,KAAMC,kBAKnDC,eAAiB,CASrBA,UAA2B,SAASC,SAAUC,MAAOC,UACjDL,KAAKM,KAAK,CAAC,CACPC,WAAY,+BACZC,KAAM,CACFJ,MAAOA,UAEX,GAAGK,MAAMC,eACLC,YAAcC,KAAKC,MAAMH,UAE7BL,SAASM,iBAWjBT,eAAgC,SAAUC,SAAUW,aAC5CC,QAAU,UACdD,QAAQE,SAASC,UACbF,QAAQG,KAAK,CACTC,MAAOF,QAAQG,GACfC,MAAOJ,QAAQK,cAIhBP,iBAGJb"} \ No newline at end of file diff --git a/amd/build/dashboard.min.js b/amd/build/dashboard.min.js new file mode 100644 index 00000000..eb2054c7 --- /dev/null +++ b/amd/build/dashboard.min.js @@ -0,0 +1,12 @@ +define("local_assessfreq/dashboard",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0, +/** + * Chart data JS module. + * + * @module local_assessfreq/dashboard + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +globalThis.reports=0;_exports.init=()=>{require(["core/form-autocomplete","core/str"],(function(Autocomplete,Str){Str.get_string("courseselect","local_assessfreq").then((loading=>{Autocomplete.enhance("#local-assessfreq-course-filter",!1,"local_assessfreq/course_selector",loading,!1,!0);document.getElementById("local-assessfreq-course-filter").addEventListener("change",(event=>{let courseid=event.target.value,url=new URL(window.location);url.searchParams.set("courseid",courseid),window.location=url}))}))})),tabs(),window.setTimeout(loading,2e3)};const loading=()=>{let loaderwrapper=document.getElementById("loader-wrapper"),index=document.getElementById("local-assessfreq-index");0===globalThis.reports?(loaderwrapper.style.display="none",index.style.display="block"):window.setTimeout(loading,1e3)},tabs=()=>{document.getElementsByClassName("tablinks").forEach((el=>el.addEventListener("click",(event=>{let target=event.target.dataset.target,tabcontent=document.getElementsByClassName("tabcontent");for(let i=0;i1?urlParts[1]:null;anchor&&null!==document.querySelector('[data-target="tab-'+anchor+'"]')?document.querySelector('[data-target="tab-'+anchor+'"]').click():document.querySelector('[data-target="tab-heatmap"]').click()}})); + +//# sourceMappingURL=dashboard.min.js.map \ No newline at end of file diff --git a/amd/build/dashboard.min.js.map b/amd/build/dashboard.min.js.map new file mode 100644 index 00000000..e15b07a5 --- /dev/null +++ b/amd/build/dashboard.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module local_assessfreq/dashboard\n * @package\n * @copyright Simon Thornett \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nglobalThis.reports = 0;\n\nexport const init = () => {\n\n // Create the course search filter.\n require(['core/form-autocomplete', 'core/str'], function(Autocomplete, Str) {\n Str.get_string('courseselect', 'local_assessfreq').then((loading) => {\n Autocomplete.enhance(\n '#local-assessfreq-course-filter',\n false,\n 'local_assessfreq/course_selector',\n loading,\n false,\n true,\n );\n const course_filter = document.getElementById(\"local-assessfreq-course-filter\");\n course_filter.addEventListener('change', event => {\n let courseid = event.target.value;\n let url = new URL(window.location);\n url.searchParams.set('courseid', courseid);\n window.location = url;\n });\n });\n });\n\n // Load the tab functionality.\n tabs();\n\n // Load the loading page whilst we wait for the reports to complete.\n window.setTimeout(loading, 2000);\n};\n\nconst loading = () => {\n\n let loaderwrapper = document.getElementById('loader-wrapper');\n let index = document.getElementById('local-assessfreq-index');\n\n // No reports loading, then show the index page.\n if (globalThis.reports === 0) {\n loaderwrapper.style.display = 'none';\n index.style.display = 'block';\n } else {\n window.setTimeout(loading, 1000);\n }\n};\n\nconst tabs = () => {\n\n const tabcontent = document.getElementsByClassName(\"tablinks\");\n\n tabcontent.forEach(el => el.addEventListener('click', event => {\n let target = event.target.dataset.target;\n\n let tabcontent = document.getElementsByClassName(\"tabcontent\");\n for (let i = 0; i < tabcontent.length; i++) {\n tabcontent[i].style.display = \"none\";\n }\n\n // Get all elements with class=\"tablinks\" and remove the class \"active\"\n let tablinks = document.getElementsByClassName(\"tablinks\");\n for (let i = 0; i < tablinks.length; i++) {\n tablinks[i].className = tablinks[i].className.replace(\" active\", \"\");\n }\n\n // Show the current tab, and add an \"active\" class to the button that opened the tab\n document.getElementById(target).style.display = \"block\";\n event.currentTarget.className += \" active\";\n }));\n\n const currentUrl = document.URL;\n const urlParts = currentUrl.split('#');\n\n const anchor = (urlParts.length > 1) ? urlParts[1] : null;\n // First tab should be open by default unless we have an anchor.\n if (!anchor || document.querySelector('[data-target=\"tab-' + anchor + '\"]') === null) {\n document.querySelector('[data-target=\"tab-heatmap\"]').click();\n } else {\n document.querySelector('[data-target=\"tab-' + anchor + '\"]').click();\n }\n};\n"],"names":["globalThis","reports","require","Autocomplete","Str","get_string","then","loading","enhance","document","getElementById","addEventListener","event","courseid","target","value","url","URL","window","location","searchParams","set","tabs","setTimeout","loaderwrapper","index","style","display","getElementsByClassName","forEach","el","dataset","tabcontent","i","length","tablinks","className","replace","currentTarget","urlParts","split","anchor","querySelector","click"],"mappings":";;;;;;;;;AAwBAA,WAAWC,QAAU,gBAED,KAGhBC,QAAQ,CAAC,yBAA0B,aAAa,SAASC,aAAcC,KACnEA,IAAIC,WAAW,eAAgB,oBAAoBC,MAAMC,UACrDJ,aAAaK,QACT,mCACA,EACA,mCACAD,SACA,GACA,GAEkBE,SAASC,eAAe,kCAChCC,iBAAiB,UAAUC,YACjCC,SAAWD,MAAME,OAAOC,MACxBC,IAAM,IAAIC,IAAIC,OAAOC,UACzBH,IAAII,aAAaC,IAAI,WAAYR,UACjCK,OAAOC,SAAWH,aAM9BM,OAGAJ,OAAOK,WAAWhB,QAAS,YAGzBA,QAAU,SAERiB,cAAgBf,SAASC,eAAe,kBACxCe,MAAQhB,SAASC,eAAe,0BAGT,IAAvBV,WAAWC,SACXuB,cAAcE,MAAMC,QAAU,OAC9BF,MAAMC,MAAMC,QAAU,SAEtBT,OAAOK,WAAWhB,QAAS,MAI7Be,KAAO,KAEUb,SAASmB,uBAAuB,YAExCC,SAAQC,IAAMA,GAAGnB,iBAAiB,SAASC,YAC9CE,OAASF,MAAME,OAAOiB,QAAQjB,OAE9BkB,WAAavB,SAASmB,uBAAuB,kBAC5C,IAAIK,EAAI,EAAGA,EAAID,WAAWE,OAAQD,IACnCD,WAAWC,GAAGP,MAAMC,QAAU,WAI9BQ,SAAW1B,SAASmB,uBAAuB,gBAC1C,IAAIK,EAAI,EAAGA,EAAIE,SAASD,OAAQD,IACjCE,SAASF,GAAGG,UAAYD,SAASF,GAAGG,UAAUC,QAAQ,UAAW,IAIrE5B,SAASC,eAAeI,QAAQY,MAAMC,QAAU,QAChDf,MAAM0B,cAAcF,WAAa,qBAI/BG,SADa9B,SAASQ,IACAuB,MAAM,KAE5BC,OAAUF,SAASL,OAAS,EAAKK,SAAS,GAAK,KAEhDE,QAA2E,OAAjEhC,SAASiC,cAAc,qBAAuBD,OAAS,MAGlEhC,SAASiC,cAAc,qBAAuBD,OAAS,MAAME,QAF7DlC,SAASiC,cAAc,+BAA+BC"} \ No newline at end of file diff --git a/amd/build/dashboard_assessment.min.js b/amd/build/dashboard_assessment.min.js deleted file mode 100644 index 913662d3..00000000 --- a/amd/build/dashboard_assessment.min.js +++ /dev/null @@ -1,10 +0,0 @@ -define("local_assessfreq/dashboard_assessment",["exports","core/notification","local_assessfreq/calendar","local_assessfreq/chart_data","local_assessfreq/dayview","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],(function(_exports,_notification,_calendar,ChartData,_dayview,UserPreference,_zoom_modal){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_assessment - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */var contextid,yearselect,yearselectheatmap,metricselectheatmap,timeout;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_calendar=_interopRequireDefault(_calendar),ChartData=_interopRequireWildcard(ChartData),_dayview=_interopRequireDefault(_dayview),UserPreference=_interopRequireWildcard(UserPreference),_zoom_modal=_interopRequireDefault(_zoom_modal);var modulesJson="",heatmapOptionsJson="";const cards=[{cardId:"local-assessfreq-assess-due-month",call:"assess_by_month"},{cardId:"local-assessfreq-assess-by-activity",call:"assess_by_activity"},{cardId:"local-assessfreq-assess-due-month-student",call:"assess_by_month_student"}],yearButtonAction=event=>{event.preventDefault();var element=event.target;"a"===element.tagName.toLowerCase()&&element.dataset.year!==yearselect&&(yearselect=element.dataset.year,UserPreference.setUserPreference("local_assessfreq_overview_year_preference",yearselect),document.getElementById("local-assessfreq-report-overview").getElementsByClassName("local-assessfreq-year")[0].innerHTML=yearselect,ChartData.getCardCharts(0,null,yearselect))},updateHeatmapDebounce=()=>{clearTimeout(timeout),timeout=setTimeout(updateHeatmap(),750)},detailView=event=>{let element=event.target;"td"===element.tagName.toLowerCase()&&"true"===element.dataset.event&&_dayview.default.display(element.dataset.date)},updateHeatmap=()=>{for(var links=document.getElementById("local-assessfreq-heatmap-modules").getElementsByTagName("a"),modules=[],i=0;i{let heatmapOptions=JSON.parse(heatmapOptionsJson),year=parseInt(heatmapOptions.year),metric=heatmapOptions.metric,modules=heatmapOptions.modules,spinner=document.getElementById("local-assessfreq-report-heatmap").getElementsByClassName("overlay-icon-container")[0];spinner.classList.remove("hide"),_calendar.default.generate(year,0,11,metric,modules).then((calendar=>{let calendarContainer=document.getElementById("local-assessfreq-report-heatmap-months");calendarContainer.innerHTML=calendar.innerHTML,calendarContainer.addEventListener("click",detailView)})).then(_calendar.default.createHeatScale).then((heatScale=>{document.getElementById("local-assessfreq-report-heatmap-scale").innerHTML=heatScale.outerHTML,spinner.classList.add("hide")})).catch((()=>{_notification.default.exception(new Error("Failed to calendar."))}))})(),(_ref=>{let{year:year,metric:metric,modules:modules}=_ref,downloadForm=document.getElementById("local-assessfreq-heatmap-form"),formElements=downloadForm.elements,toRemove=new Array;0===modules.length&&(modules=["all"]);for(let i=0;i{event.preventDefault();var element=event.target;"a"===element.tagName.toLowerCase()&&element.dataset.year!==yearselectheatmap&&(yearselectheatmap=element.dataset.year,UserPreference.setUserPreference("local_assessfreq_heatmap_year_preference",yearselectheatmap),document.getElementById("local-assessfreq-report-heatmap").getElementsByClassName("local-assessfreq-year")[0].innerHTML=yearselectheatmap,updateHeatmapDebounce())},metricHeatmapButtonAction=event=>{event.preventDefault();var element=event.target;"a"===element.tagName.toLowerCase()&&element.dataset.metric!==metricselectheatmap&&(metricselectheatmap=element.dataset.metric,UserPreference.setUserPreference("local_assessfreq_heatmap_metric_preference",metricselectheatmap),updateHeatmapDebounce())},triggerZoomGraph=event=>{let call=event.target.closest("div").dataset.call,params={data:JSON.stringify({year:yearselect,call:call})};_zoom_modal.default.zoomGraph(event,params,"get_chart")};_exports.init=context=>{contextid=context;let cardsYearSelectElement=document.getElementById("local-assessfreq-cards-year");yearselect=cardsYearSelectElement.getElementsByClassName("active")[0].dataset.year,cardsYearSelectElement.addEventListener("click",yearButtonAction);let cardsYearSelectHeatmapElement=document.getElementById("local-assessfreq-heatmap-year");yearselectheatmap=cardsYearSelectHeatmapElement.getElementsByClassName("active")[0].dataset.year,cardsYearSelectHeatmapElement.addEventListener("click",yearHeatmapButtonAction);let cardsMetricSelectHeatmapElement=document.getElementById("local-assessfreq-heatmap-metrics");metricselectheatmap=cardsMetricSelectHeatmapElement.getElementsByClassName("active")[0].dataset.metric,cardsMetricSelectHeatmapElement.addEventListener("click",metricHeatmapButtonAction),(element=>{for(var links=element.getElementsByTagName("a"),all=links[0],i=0;i.\n\n/**\n * Javascript for report card display and processing.\n *\n * @module local_assessfreq/dashboard_assessment\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Notification from 'core/notification';\nimport Calendar from 'local_assessfreq/calendar';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport Dayview from 'local_assessfreq/dayview';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport ZoomModal from 'local_assessfreq/zoom_modal';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar yearselect;\nvar yearselectheatmap;\nvar metricselectheatmap;\nvar timeout;\nvar modulesJson = '';\nvar heatmapOptionsJson = '';\n\nconst cards = [\n {cardId: 'local-assessfreq-assess-due-month', call: 'assess_by_month'},\n {cardId: 'local-assessfreq-assess-by-activity', call: 'assess_by_activity'},\n {cardId: 'local-assessfreq-assess-due-month-student', call: 'assess_by_month_student'}\n];\n\n/**\n * Get and process the selected year from the dropdown,\n * and update the corresponding user perference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst yearButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselect) { // Only act on certain elements.\n yearselect = element.dataset.year;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_overview_year_preference', yearselect);\n\n // Update card data based on selected year.\n var yeartitle = document.getElementById('local-assessfreq-report-overview')\n .getElementsByClassName('local-assessfreq-year')[0];\n yeartitle.innerHTML = yearselect;\n\n ChartData.getCardCharts(0, null, yearselect); // Process loading for the assessment cards.\n }\n};\n\n/**\n * Quick and dirty debounce method for the heatmap settings menu.\n * This stops the ajax method that updates the heatmap from being updated\n * while the user is still checking options.\n *\n */\nconst updateHeatmapDebounce = () => {\n clearTimeout(timeout);\n timeout = setTimeout(updateHeatmap(), 750);\n};\n\n/**\n * Display heatmap calendar.\n *\n * @param {event} event The triggered event for the element.\n */\nconst detailView = (event) => {\n let element = event.target;\n if (element.tagName.toLowerCase() === 'td' && element.dataset.event === 'true') { // Only act on certain elements.\n Dayview.display(element.dataset.date);\n }\n};\n\n/**\n * Start heatmap generation.\n *\n */\nconst generateHeatmap = () => {\n let heatmapOptions = JSON.parse(heatmapOptionsJson);\n let year = parseInt(heatmapOptions.year);\n let metric = heatmapOptions.metric;\n let modules = heatmapOptions.modules;\n let heatmapContainer = document.getElementById('local-assessfreq-report-heatmap');\n let spinner = heatmapContainer.getElementsByClassName('overlay-icon-container')[0];\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n\n Calendar.generate(year, 0, 11, metric, modules)\n .then(calendar => {\n let calendarContainer = document.getElementById('local-assessfreq-report-heatmap-months');\n calendarContainer.innerHTML = calendar.innerHTML;\n calendarContainer.addEventListener('click', detailView);\n })\n .then(Calendar.createHeatScale)\n .then((heatScale) => {\n let heatScaleContainer = document.getElementById('local-assessfreq-report-heatmap-scale');\n heatScaleContainer.innerHTML = heatScale.outerHTML;\n spinner.classList.add('hide'); // Hide sinner if not already hidden.\n })\n .catch(() => {\n Notification.exception(new Error('Failed to calendar.'));\n return;\n });\n};\n\nconst updateDownload = ({year, metric, modules}) => {\n let downloadForm = document.getElementById('local-assessfreq-heatmap-form');\n let formElements = downloadForm.elements;\n let toRemove = new Array();\n\n if (modules.length === 0) {\n modules = ['all'];\n }\n\n for (let i = 0; i < formElements.length; i++) {\n if (formElements[i] === undefined) {\n continue;\n }\n // Update year field.\n if ((formElements[i].type === 'hidden') && (formElements[i].name === 'year')) {\n formElements[i].value = year;\n continue;\n }\n\n // Update metric field.\n if ((formElements[i].type === 'hidden') && (formElements[i].name === 'metric')) {\n formElements[i].value = metric;\n continue;\n }\n\n // Update module fields.\n if ((formElements[i].type === 'hidden') && (formElements[i].name.startsWith('modules'))) {\n toRemove.push(formElements[i]);\n continue;\n }\n }\n\n for (const element of toRemove) {\n element.remove();\n }\n\n for (let i = 0; i < modules.length; i++) {\n let input = document.createElement('input');\n input.type = 'hidden';\n input.name = 'modules[' + modules[i] + ']';\n input.value = modules[i];\n\n downloadForm.appendChild(input);\n }\n};\n\n/**\n * Update the heatmap based on the current filter settings.\n *\n */\nconst updateHeatmap = () => {\n // Get current state of select menu items.\n var cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules');\n var links = cardsModulesSelectHeatmapElement.getElementsByTagName('a');\n var modules = [];\n\n for (var i = 0; i < links.length; i++) {\n if (links[i].classList.contains('active')) {\n let module = links[i].dataset.module;\n modules.push(module);\n }\n }\n\n // Save selection as a user preference.\n if (modulesJson !== JSON.stringify(modules)) {\n modulesJson = JSON.stringify(modules);\n UserPreference.setUserPreference('local_assessfreq_heatmap_modules_preference', modulesJson);\n }\n\n // Build settings object.\n var optionsObj = {\n 'year': yearselectheatmap,\n 'metric': metricselectheatmap,\n 'modules': modules\n };\n\n var optionsJson = JSON.stringify(optionsObj);\n\n if (optionsJson !== heatmapOptionsJson) { // Compare to global to see if there are any changes.\n // If list has changed fetch heatmap and update user preference.\n heatmapOptionsJson = optionsJson;\n generateHeatmap();\n\n // Update the download options.\n updateDownload(optionsObj);\n }\n};\n\n/**\n * Get and process the selected year from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst yearHeatmapButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselectheatmap) { // Only act on certain elements.\n yearselectheatmap = element.dataset.year;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_heatmap_year_preference', yearselectheatmap);\n\n // Update card data based on selected year.\n var yeartitle = document.getElementById('local-assessfreq-report-heatmap')\n .getElementsByClassName('local-assessfreq-year')[0];\n yeartitle.innerHTML = yearselectheatmap;\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n }\n};\n\n/**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst metricHeatmapButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.metric !== metricselectheatmap) {\n metricselectheatmap = element.dataset.metric;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_heatmap_metric_preference', metricselectheatmap);\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n }\n};\n\n/**\n * Add the event listeners to the modules in the module select dropdown.\n *\n * @param {Object} element The dropdown HTML element that contains the list of modules as links.\n */\nconst moduleListChildrenEvents = (element) => {\n var links = element.getElementsByTagName('a');\n var all = links[0];\n\n for (var i = 0; i < links.length; i++) {\n let module = links[i].dataset.module;\n\n if (module.toLowerCase() === 'all') {\n links[i].addEventListener('click', function (event) {\n event.preventDefault();\n // Remove active class from all other links.\n for (var j = 0; j < links.length; j++) {\n links[j].classList.remove('active');\n }\n updateHeatmapDebounce(); // Call function to update heatmap.\n });\n } else if (module.toLowerCase() === 'close') {\n links[i].addEventListener('click', function (event) {\n event.preventDefault();\n event.stopPropagation();\n\n var dropdownmenu = document.getElementById('local-assessfreq-heatmap-modules-filter');\n dropdownmenu.classList.remove('show');\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n });\n } else {\n links[i].addEventListener('click', function (event) {\n event.preventDefault();\n event.stopPropagation();\n\n all.classList.remove('active');\n\n event.target.classList.toggle('active');\n updateHeatmapDebounce();\n });\n }\n }\n};\n\n/**\n * Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'year': yearselect, 'call': call})};\n let method = 'get_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Initialise method for report card rendering.\n *\n * @param {integer} context The current context id.\n */\nexport const init = (context) => {\n contextid = context;\n\n // Set up event listener and related actions for year dropdown on report cards.\n let cardsYearSelectElement = document.getElementById('local-assessfreq-cards-year');\n yearselect = cardsYearSelectElement.getElementsByClassName('active')[0].dataset.year;\n cardsYearSelectElement.addEventListener('click', yearButtonAction);\n\n // Set up event listener and related actions for year dropdown on heatmp.\n let cardsYearSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-year');\n yearselectheatmap = cardsYearSelectHeatmapElement.getElementsByClassName('active')[0].dataset.year;\n cardsYearSelectHeatmapElement.addEventListener('click', yearHeatmapButtonAction);\n\n // Set up event listener and related actions for metric dropdown on heatmp.\n let cardsMetricSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-metrics');\n metricselectheatmap = cardsMetricSelectHeatmapElement.getElementsByClassName('active')[0].dataset.metric;\n cardsMetricSelectHeatmapElement.addEventListener('click', metricHeatmapButtonAction);\n\n // Set up event listener and related actions for module dropdown on heatmp.\n let cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules');\n moduleListChildrenEvents(cardsModulesSelectHeatmapElement);\n\n // Set up zoom event listeners.\n let dueMonthZoom = document.getElementById('local-assessfreq-assess-due-month-zoom');\n dueMonthZoom.addEventListener('click', triggerZoomGraph);\n\n let dueActivityZoom = document.getElementById('local-assessfreq-assess-by-activity-zoom');\n dueActivityZoom.addEventListener('click', triggerZoomGraph);\n\n let dueStudentZoom = document.getElementById('local-assessfreq-assess-due-month-student-zoom');\n dueStudentZoom.addEventListener('click', triggerZoomGraph);\n\n // Create the zoom modal.\n ZoomModal.init(context);\n\n // Setup the dayview modal.\n Dayview.init();\n\n // Setup the chart data for each card.\n ChartData.init(cards, contextid, 'get_chart', 'core/chart');\n\n // Process loading for the assessment cards.\n ChartData.getCardCharts(0, null, yearselect);\n\n // Get the data for the heatmap.\n updateHeatmap();\n\n};\n"],"names":["contextid","yearselect","yearselectheatmap","metricselectheatmap","timeout","modulesJson","heatmapOptionsJson","cards","cardId","call","yearButtonAction","event","preventDefault","element","target","tagName","toLowerCase","dataset","year","UserPreference","setUserPreference","document","getElementById","getElementsByClassName","innerHTML","ChartData","getCardCharts","updateHeatmapDebounce","clearTimeout","setTimeout","updateHeatmap","detailView","display","date","links","getElementsByTagName","modules","i","length","classList","contains","module","push","JSON","stringify","optionsObj","optionsJson","heatmapOptions","parse","parseInt","metric","spinner","remove","generate","then","calendar","calendarContainer","addEventListener","Calendar","createHeatScale","heatScale","outerHTML","add","catch","exception","Error","generateHeatmap","_ref","downloadForm","formElements","elements","toRemove","Array","undefined","type","name","startsWith","value","input","createElement","appendChild","updateDownload","yearHeatmapButtonAction","metricHeatmapButtonAction","triggerZoomGraph","closest","params","zoomGraph","context","cardsYearSelectElement","cardsYearSelectHeatmapElement","cardsMetricSelectHeatmapElement","all","j","stopPropagation","toggle","moduleListChildrenEvents","init"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/dashboard_quiz.min.js b/amd/build/dashboard_quiz.min.js deleted file mode 100644 index 2682290f..00000000 --- a/amd/build/dashboard_quiz.min.js +++ /dev/null @@ -1,10 +0,0 @@ -define("local_assessfreq/dashboard_quiz",["exports","core/ajax","core/notification","core/str","core/templates","local_assessfreq/chart_data","local_assessfreq/form_modal","local_assessfreq/override_modal","local_assessfreq/table_handler","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],(function(_exports,_ajax,_notification,Str,_templates,ChartData,FormModal,_override_modal,TableHandler,UserPreference,ZoomModal){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_quiz - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),Str=_interopRequireWildcard(Str),_templates=_interopRequireDefault(_templates),ChartData=_interopRequireWildcard(ChartData),FormModal=_interopRequireWildcard(FormModal),_override_modal=_interopRequireDefault(_override_modal),TableHandler=_interopRequireWildcard(TableHandler),UserPreference=_interopRequireWildcard(UserPreference),ZoomModal=_interopRequireWildcard(ZoomModal);var contextid,counterid,selectQuizStr="",quizId=0,refreshPeriod=60;const cards=[{cardId:"local-assessfreq-quiz-summary-graph",call:"participant_summary",aspect:!0},{cardId:"local-assessfreq-quiz-summary-trend",call:"participant_trend",aspect:!1}],refreshCounter=function(){let reset=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],progressElement=document.getElementById("local-assessfreq-period-progress");!0===reset&&(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100)),counterid||(counterid=setInterval((()=>{let progressWidthAria=progressElement.getAttribute("aria-valuenow");const progressStep=100/refreshPeriod;progressWidthAria-progressStep>0?(progressElement.setAttribute("style","width: "+(progressWidthAria-progressStep)+"%"),progressElement.setAttribute("aria-valuenow",progressWidthAria-progressStep)):(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100),processDashboard(quizId),refreshCounter())}),1e3))},processDashboard=quiz=>{quizId=quiz;let titleElement=document.getElementById("local-assessfreq-quiz-title");titleElement.innerHTML=selectQuizStr,_ajax.default.call([{methodname:"local_assessfreq_get_quiz_data",args:{quizid:quiz}}])[0].then((response=>{let quizArray=JSON.parse(response),cardsElement=document.getElementById("local-assessfreq-quiz-dashboard-cards-deck"),trendElement=document.getElementById("local-assessfreq-quiz-dashboard-participant-trend-deck"),summarySpinner=document.getElementById("local-assessfreq-quiz-summary-card").getElementsByClassName("overlay-icon-container")[0],tableElement=document.getElementById("local-assessfreq-quiz-table"),periodElement=document.getElementById("local-assessfreq-period-container"),tableSearchInputElement=document.getElementById("local-assessfreq-quiz-student-table-search"),tableSearchResetElement=document.getElementById("local-assessfreq-quiz-student-table-search-reset"),tableSearchRowsElement=document.getElementById("local-assessfreq-quiz-student-table-rows"),quizLink=document.createElement("a");quizLink.href=quizArray.url,quizLink.innerHTML='',titleElement.innerHTML=quizArray.name+" ",titleElement.appendChild(quizLink);const currentdUrl=new URL(window.location.href),newUrl=currentdUrl.origin+currentdUrl.pathname+"?id="+quizId;history.pushState({},"",newUrl),Str.get_string("dashboard:quiztitle","local_assessfreq",{quiz:quizArray.name,course:quizArray.courseshortname}).then((str=>{document.title=str})).catch((()=>{_notification.default.exception(new Error("Failed to load string: dashboard:quiztitle"))})),_templates.default.render("local_assessfreq/quiz-summary-card-content",quizArray).done((html=>{summarySpinner.classList.add("hide");let contentcontainer=document.getElementById("local-assessfreq-quiz-summary-card-content");_templates.default.replaceNodeContents(contentcontainer,html,"")})).fail((()=>{_notification.default.exception(new Error("Failed to load quiz summary template."))})),cardsElement.classList.remove("hide"),trendElement.classList.remove("hide"),tableElement.classList.remove("hide"),periodElement.classList.remove("hide"),ChartData.getCardCharts(quizId),TableHandler.getTable(quizId),refreshCounter(),tableSearchInputElement.addEventListener("keyup",TableHandler.tableSearch),tableSearchInputElement.addEventListener("paste",TableHandler.tableSearch),tableSearchResetElement.addEventListener("click",TableHandler.tableSearchReset),tableSearchRowsElement.addEventListener("click",TableHandler.tableSearchRowSet)})).fail((()=>{_notification.default.exception(new Error("Failed to get quiz data"))}))},refreshAction=event=>{event.preventDefault();var element=event.target;null!==element.closest("button")&&"local-assessfreq-refresh-quiz-dashboard"===element.closest("button").id?(refreshCounter(!0),processDashboard(quizId)):"a"===element.tagName.toLowerCase()&&(refreshPeriod=element.dataset.period,refreshCounter(!0),UserPreference.setUserPreference("local_assessfreq_quiz_refresh_preference",refreshPeriod))},triggerZoomGraph=event=>{let call=event.target.closest("div").dataset.call,params={data:JSON.stringify({quiz:quizId,call:call})};ZoomModal.zoomGraph(event,params,"get_quiz_chart")};_exports.init=(context,quiz)=>{contextid=context,FormModal.init(context,processDashboard),ZoomModal.init(context),_override_modal.default.init(context,processDashboard),TableHandler.init(quizId,contextid,"local-assessfreq-quiz-student-table","local-assessfreq-quiz-table","get_student_table","local_assessfreq_quiz_table_rows_preference","local-assessfreq-quiz-student-table-search","local_assessfreq_student_table","local_assessfreq_set_table_preference"),ChartData.init(cards,context,"get_quiz_chart","local_assessfreq/chart"),Str.get_string("loadingquiztitle","local_assessfreq").then((str=>{selectQuizStr=str})).catch((()=>{_notification.default.exception(new Error("Failed to load string: loadingquiz"))})).then((()=>{quiz>0&&(quizId=quiz,processDashboard(quiz))})),UserPreference.getUserPreference("local_assessfreq_quiz_refresh_preference").then((response=>{refreshPeriod=response.preferences[0].value?response.preferences[0].value:60})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: refresh"))})),document.getElementById("local-assessfreq-period-container").addEventListener("click",refreshAction),document.getElementById("local-assessfreq-quiz-summary-graph-zoom").addEventListener("click",triggerZoomGraph),document.getElementById("local-assessfreq-quiz-summary-trend-zoom").addEventListener("click",triggerZoomGraph)}})); - -//# sourceMappingURL=dashboard_quiz.min.js.map \ No newline at end of file diff --git a/amd/build/dashboard_quiz.min.js.map b/amd/build/dashboard_quiz.min.js.map deleted file mode 100644 index 9a8a67a5..00000000 --- a/amd/build/dashboard_quiz.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dashboard_quiz.min.js","sources":["../src/dashboard_quiz.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @module local_assessfreq/dashboard_quiz\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport * as Str from 'core/str';\nimport Templates from 'core/templates';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport * as FormModal from 'local_assessfreq/form_modal';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport * as ZoomModal from 'local_assessfreq/zoom_modal';\n\n// Module level variables.\n\nvar selectQuizStr = '';\nvar contextid;\nvar quizId = 0;\nvar refreshPeriod = 60;\nvar counterid;\n\nconst cards = [\n {cardId: 'local-assessfreq-quiz-summary-graph', call: 'participant_summary', aspect: true},\n {cardId: 'local-assessfreq-quiz-summary-trend', call: 'participant_trend', aspect: false}\n];\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n processDashboard(quizId);\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Callback function that is called when a quiz is selected from the form.\n * Starts the processing of the dashboard.\n *\n * @param {int} quiz The quiz Id.\n */\nconst processDashboard = (quiz) => {\n quizId = quiz;\n let titleElement = document.getElementById('local-assessfreq-quiz-title');\n titleElement.innerHTML = selectQuizStr;\n // Get quiz data.\n Ajax.call([{\n methodname: 'local_assessfreq_get_quiz_data',\n args: {\n quizid: quiz\n },\n }])[0].then((response) => {\n\n let quizArray = JSON.parse(response);\n let cardsElement = document.getElementById('local-assessfreq-quiz-dashboard-cards-deck');\n let trendElement = document.getElementById('local-assessfreq-quiz-dashboard-participant-trend-deck');\n let summaryElement = document.getElementById('local-assessfreq-quiz-summary-card');\n let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0];\n let tableElement = document.getElementById('local-assessfreq-quiz-table');\n let periodElement = document.getElementById('local-assessfreq-period-container');\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows');\n\n let quizLink = document.createElement('a');\n quizLink.href = quizArray.url;\n quizLink.innerHTML = '';\n titleElement.innerHTML = quizArray.name + ' ';\n titleElement.appendChild(quizLink);\n\n // Update page URL with quiz ID, without reloading page so that page navigation and bookmarking works.\n const currentdUrl = new URL(window.location.href);\n const newUrl = currentdUrl.origin + currentdUrl.pathname + '?id=' + quizId;\n history.pushState({}, '', newUrl);\n\n // Update page title with quiz name.\n Str.get_string('dashboard:quiztitle', 'local_assessfreq', {'quiz': quizArray.name, 'course': quizArray.courseshortname})\n .then((str) => {\n document.title = str;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: dashboard:quiztitle'));\n });\n\n // Populate quiz summary card with details.\n Templates.render('local_assessfreq/quiz-summary-card-content', quizArray).done((html) => {\n summarySpinner.classList.add('hide');\n let contentcontainer = document.getElementById('local-assessfreq-quiz-summary-card-content');\n Templates.replaceNodeContents(contentcontainer, html, '');\n }).fail(() => {\n Notification.exception(new Error('Failed to load quiz summary template.'));\n return;\n });\n\n // Show the cards.\n cardsElement.classList.remove('hide');\n trendElement.classList.remove('hide');\n tableElement.classList.remove('hide');\n periodElement.classList.remove('hide');\n\n ChartData.getCardCharts(quizId);\n TableHandler.getTable(quizId);\n refreshCounter();\n\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get quiz data'));\n });\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n processDashboard(quizId);\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Trigger the zoom graph. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'quiz': quizId, 'call': call})};\n let method = 'get_quiz_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Initialise method for quiz dashboard rendering.\n *\n * @param {int} context The context id.\n * @param {int} quiz The quiz id.\n */\nexport const init = (context, quiz) => {\n contextid = context;\n FormModal.init(context, processDashboard); // Create modal for quiz selection modal.\n ZoomModal.init(context); // Create the zoom modal.\n OverrideModal.init(context, processDashboard);\n TableHandler.init(\n quizId,\n contextid,\n 'local-assessfreq-quiz-student-table',\n 'local-assessfreq-quiz-table',\n 'get_student_table',\n 'local_assessfreq_quiz_table_rows_preference',\n 'local-assessfreq-quiz-student-table-search',\n 'local_assessfreq_student_table',\n 'local_assessfreq_set_table_preference'\n );\n ChartData.init(cards, context, 'get_quiz_chart', 'local_assessfreq/chart');\n Str.get_string('loadingquiztitle', 'local_assessfreq').then((str) => {\n selectQuizStr = str;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loadingquiz'));\n }).then(() => {\n if (quiz > 0) {\n quizId = quiz;\n processDashboard(quiz);\n }\n });\n\n UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference')\n .then((response) => {\n refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: refresh'));\n });\n\n // Event handling for refresh and period buttons.\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n refreshElement.addEventListener('click', refreshAction);\n\n // Set up zoom event listeners.\n let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-graph-zoom');\n summaryZoom.addEventListener('click', triggerZoomGraph);\n\n let trendZoom = document.getElementById('local-assessfreq-quiz-summary-trend-zoom');\n trendZoom.addEventListener('click', triggerZoomGraph);\n\n};\n"],"names":["contextid","counterid","selectQuizStr","quizId","refreshPeriod","cards","cardId","call","aspect","refreshCounter","reset","progressElement","document","getElementById","clearInterval","setAttribute","setInterval","progressWidthAria","getAttribute","progressStep","processDashboard","quiz","titleElement","innerHTML","methodname","args","quizid","then","response","quizArray","JSON","parse","cardsElement","trendElement","summarySpinner","getElementsByClassName","tableElement","periodElement","tableSearchInputElement","tableSearchResetElement","tableSearchRowsElement","quizLink","createElement","href","url","name","appendChild","currentdUrl","URL","window","location","newUrl","origin","pathname","history","pushState","Str","get_string","courseshortname","str","title","catch","exception","Error","render","done","html","classList","add","contentcontainer","replaceNodeContents","fail","remove","ChartData","getCardCharts","TableHandler","getTable","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","refreshAction","event","preventDefault","element","target","closest","id","tagName","toLowerCase","dataset","period","UserPreference","setUserPreference","triggerZoomGraph","params","stringify","ZoomModal","zoomGraph","context","FormModal","init","getUserPreference","preferences","value"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/dashboard_quiz_inprogress.min.js b/amd/build/dashboard_quiz_inprogress.min.js deleted file mode 100644 index 7554495d..00000000 --- a/amd/build/dashboard_quiz_inprogress.min.js +++ /dev/null @@ -1,10 +0,0 @@ -define("local_assessfreq/dashboard_quiz_inprogress",["exports","core/ajax","core/notification","core/templates","local_assessfreq/chart_data","local_assessfreq/table_handler","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],(function(_exports,_ajax,_notification,_templates,ChartData,TableHandler,UserPreference,ZoomModal){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * Javascript for quizzes in progress display and processing. - * - * @module local_assessfreq/dashboard_quiz_inprogress - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */var contextid;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates),ChartData=_interopRequireWildcard(ChartData),TableHandler=_interopRequireWildcard(TableHandler),UserPreference=_interopRequireWildcard(UserPreference),ZoomModal=_interopRequireWildcard(ZoomModal);var counterid,hoursFilter,refreshPeriod=60,tablesort="name_asc",hoursAhead=0,hoursBehind=0;const cards=[{cardId:"local-assessfreq-quiz-summary-upcomming-graph",call:"upcomming_quizzes",aspect:!0},{cardId:"local-assessfreq-quiz-summary-inprogress-graph",call:"all_participants_inprogress",aspect:!0}],refreshCounter=function(){let reset=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],progressElement=document.getElementById("local-assessfreq-period-progress");!0===reset&&(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100)),counterid||(counterid=setInterval((()=>{let progressWidthAria=progressElement.getAttribute("aria-valuenow");const progressStep=100/refreshPeriod;progressWidthAria-progressStep>0?(progressElement.setAttribute("style","width: "+(progressWidthAria-progressStep)+"%"),progressElement.setAttribute("aria-valuenow",progressWidthAria-progressStep)):(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100),processDashboard(),refreshCounter())}),1e3))},processDashboard=()=>{_ajax.default.call([{methodname:"local_assessfreq_get_inprogress_counts",args:{}}])[0].then((response=>{let quizSummary=JSON.parse(response),summaryElement=document.getElementById("local-assessfreq-quiz-dashboard-inprogress-summary-card"),summarySpinner=summaryElement.getElementsByClassName("overlay-icon-container")[0],tableSearchInputElement=document.getElementById("local-assessfreq-quiz-inprogress-table-search"),tableSearchResetElement=document.getElementById("local-assessfreq-quiz-inprogress-table-search-reset"),tableSearchRowsElement=document.getElementById("local-assessfreq-quiz-inprogress-table-rows"),tableSortElement=document.getElementById("local-assessfreq-inprogress-table-sort");summaryElement.classList.remove("hide"),_templates.default.render("local_assessfreq/quiz-dashboard-inprogress-summary-card-content",quizSummary).done((html=>{summarySpinner.classList.add("hide");let contentcontainer=document.getElementById("local-assessfreq-quiz-dashboard-inprogress-summary-card-content");_templates.default.replaceNodeContents(contentcontainer,html,"")})).fail((()=>{_notification.default.exception(new Error("Failed to load quiz counts template."))})),hoursFilter=[hoursAhead,hoursBehind],ChartData.getCardCharts(0,hoursFilter),TableHandler.getTable(0,hoursFilter,tablesort),refreshCounter(),tableSearchInputElement.addEventListener("keyup",TableHandler.tableSearch),tableSearchInputElement.addEventListener("paste",TableHandler.tableSearch),tableSearchResetElement.addEventListener("click",TableHandler.tableSearchReset),tableSearchRowsElement.addEventListener("click",TableHandler.tableSearchRowSet),tableSortElement.addEventListener("click",TableHandler.tableSortButtonAction)})).fail((()=>{_notification.default.exception(new Error("Failed to get quiz summary counts"))}))},refreshAction=event=>{event.preventDefault();var element=event.target;null!==element.closest("button")&&"local-assessfreq-refresh-quiz-dashboard"===element.closest("button").id?(refreshCounter(!0),processDashboard()):"a"===element.tagName.toLowerCase()&&(refreshPeriod=element.dataset.period,refreshCounter(!0),UserPreference.setUserPreference("local_assessfreq_quiz_refresh_preference",refreshPeriod))},triggerZoomGraph=event=>{let call=event.target.closest("div").dataset.call,params={data:JSON.stringify({call:call,hoursahead:hoursAhead,hoursbehind:hoursBehind})};ZoomModal.zoomGraph(event,params,"get_quiz_inprogress_chart")},quizzesAheadSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("local_assessfreq_quizzes_inprogress_table_hoursahead_preference",hours).then((()=>{hoursAhead=hours,processDashboard()})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: hours ahead"))}))}},quizzesBehindSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("local_assessfreq_quizzes_inprogress_table_hoursbehind_preference",hours).then((()=>{hoursBehind=hours,processDashboard()})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: hours behind"))}))}};_exports.init=context=>{contextid=context,ZoomModal.init(context),TableHandler.init(0,contextid,null,"local-assessfreq-quiz-inprogress-table","get_quizzes_inprogress_table","local_assessfreq_quiz_table_inprogress_preference","local-assessfreq-quiz-inprogress-table-search"),ChartData.init(cards,context,"get_quiz_inprogress_chart","local_assessfreq/chart"),UserPreference.getUserPreference("local_assessfreq_quiz_refresh_preference").then((response=>{refreshPeriod=response.preferences[0].value?response.preferences[0].value:60})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: refresh"))})),UserPreference.getUserPreference("local_assessfreq_quiz_table_inprogress_sort_preference").then((response=>{tablesort=response.preferences[0].value?response.preferences[0].value:"name_asc"})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: tablesort"))})),UserPreference.getUserPreference("local_assessfreq_quizzes_inprogress_table_hoursahead_preference").then((response=>{hoursAhead=response.preferences[0].value?response.preferences[0].value:0})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: hoursahead"))})),UserPreference.getUserPreference("local_assessfreq_quizzes_inprogress_table_hoursbehind_preference").then((response=>{hoursBehind=response.preferences[0].value?response.preferences[0].value:0})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: hoursbehind"))})),document.getElementById("local-assessfreq-period-container").addEventListener("click",refreshAction),document.getElementById("local-assessfreq-quiz-summary-inprogress-graph-zoom").addEventListener("click",triggerZoomGraph),document.getElementById("local-assessfreq-quiz-summary-upcomming-graph-zoom").addEventListener("click",triggerZoomGraph),document.getElementById("local-assessfreq-quiz-student-table-hoursahead").addEventListener("click",quizzesAheadSet),document.getElementById("local-assessfreq-quiz-student-table-hoursbehind").addEventListener("click",quizzesBehindSet),processDashboard()}})); - -//# sourceMappingURL=dashboard_quiz_inprogress.min.js.map \ No newline at end of file diff --git a/amd/build/dashboard_quiz_inprogress.min.js.map b/amd/build/dashboard_quiz_inprogress.min.js.map deleted file mode 100644 index c0cf6aa7..00000000 --- a/amd/build/dashboard_quiz_inprogress.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dashboard_quiz_inprogress.min.js","sources":["../src/dashboard_quiz_inprogress.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for quizzes in progress display and processing.\n *\n * @module local_assessfreq/dashboard_quiz_inprogress\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport * as ZoomModal from 'local_assessfreq/zoom_modal';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar refreshPeriod = 60;\nvar counterid;\nvar tablesort = 'name_asc';\nvar hoursAhead = 0;\nvar hoursBehind = 0;\n\n/**\n * Hours filter array.\n *\n * @type {array} Title to display on modal.\n */\nvar hoursFilter;\n\nconst cards = [\n {cardId: 'local-assessfreq-quiz-summary-upcomming-graph', call: 'upcomming_quizzes', aspect: true},\n {cardId: 'local-assessfreq-quiz-summary-inprogress-graph', call: 'all_participants_inprogress', aspect: true}\n];\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n processDashboard();\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Starts the processing of the dashboard.\n */\nconst processDashboard = () => {\n // Get summary quiz data.\n Ajax.call([{\n methodname: 'local_assessfreq_get_inprogress_counts',\n args: {},\n }])[0].then((response) => {\n let quizSummary = JSON.parse(response);\n let summaryElement = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card');\n let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0];\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-inprogress-table-rows');\n let tableSortElement = document.getElementById('local-assessfreq-inprogress-table-sort');\n\n summaryElement.classList.remove('hide'); // Show the card.\n\n // Populate summary card with details.\n Templates.render('local_assessfreq/quiz-dashboard-inprogress-summary-card-content', quizSummary)\n .done((html) => {\n summarySpinner.classList.add('hide');\n\n let contentcontainer = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card-content');\n Templates.replaceNodeContents(contentcontainer, html, '');\n }).fail(() => {\n Notification.exception(new Error('Failed to load quiz counts template.'));\n return;\n });\n\n hoursFilter = [hoursAhead, hoursBehind];\n ChartData.getCardCharts(0, hoursFilter);\n TableHandler.getTable(0, hoursFilter, tablesort);\n refreshCounter();\n\n // Table event listeners.\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n tableSortElement.addEventListener('click', TableHandler.tableSortButtonAction);\n\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get quiz summary counts'));\n });\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n processDashboard();\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Trigger the zoom graph. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'call': call, 'hoursahead': hoursAhead, 'hoursbehind': hoursBehind})};\n let method = 'get_quiz_inprogress_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Process the hours ahead event from the in progress quizzes table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst quizzesAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', hours)\n .then(() => {\n hoursAhead = hours;\n processDashboard(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours ahead'));\n });\n }\n};\n\n/**\n * Process the hours behind event from the in progress quizzes table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst quizzesBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', hours)\n .then(() => {\n hoursBehind = hours;\n processDashboard(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours behind'));\n });\n }\n};\n\n/**\n * Initialise method for quizzes in progress dashboard rendering.\n *\n * @param {int} context The context id.\n */\nexport const init = (context) => {\n contextid = context;\n ZoomModal.init(context); // Create the zoom modal.\n TableHandler.init(\n 0,\n contextid,\n null,\n 'local-assessfreq-quiz-inprogress-table',\n 'get_quizzes_inprogress_table',\n 'local_assessfreq_quiz_table_inprogress_preference',\n 'local-assessfreq-quiz-inprogress-table-search'\n );\n ChartData.init(cards, context, 'get_quiz_inprogress_chart', 'local_assessfreq/chart');\n\n UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference')\n .then((response) => {\n refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: refresh'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference')\n .then((response) => {\n tablesort = response.preferences[0].value ? response.preferences[0].value : 'name_asc';\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: tablesort'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference')\n .then((response) => {\n hoursAhead = response.preferences[0].value ? response.preferences[0].value : 0;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference')\n .then((response) => {\n hoursBehind = response.preferences[0].value ? response.preferences[0].value : 0;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursbehind'));\n });\n\n // Event handling for refresh and period buttons.\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n refreshElement.addEventListener('click', refreshAction);\n\n // Set up zoom event listeners.\n let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-inprogress-graph-zoom');\n summaryZoom.addEventListener('click', triggerZoomGraph);\n\n let upcommingZoom = document.getElementById('local-assessfreq-quiz-summary-upcomming-graph-zoom');\n upcommingZoom.addEventListener('click', triggerZoomGraph);\n\n // Set up behind and ahead quizzes event listeners.\n let quizzesAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead');\n quizzesAheadElement.addEventListener('click', quizzesAheadSet);\n\n let quizzesBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind');\n quizzesBehindElement.addEventListener('click', quizzesBehindSet);\n\n processDashboard();\n\n};\n"],"names":["contextid","counterid","hoursFilter","refreshPeriod","tablesort","hoursAhead","hoursBehind","cards","cardId","call","aspect","refreshCounter","reset","progressElement","document","getElementById","clearInterval","setAttribute","setInterval","progressWidthAria","getAttribute","progressStep","processDashboard","methodname","args","then","response","quizSummary","JSON","parse","summaryElement","summarySpinner","getElementsByClassName","tableSearchInputElement","tableSearchResetElement","tableSearchRowsElement","tableSortElement","classList","remove","render","done","html","add","contentcontainer","replaceNodeContents","fail","exception","Error","ChartData","getCardCharts","TableHandler","getTable","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","tableSortButtonAction","refreshAction","event","preventDefault","element","target","closest","id","tagName","toLowerCase","dataset","period","UserPreference","setUserPreference","triggerZoomGraph","params","stringify","ZoomModal","zoomGraph","quizzesAheadSet","hours","metric","quizzesBehindSet","context","init","getUserPreference","preferences","value"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/dayview.min.js b/amd/build/dayview.min.js deleted file mode 100644 index 8c7b3f8f..00000000 --- a/amd/build/dayview.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Javascript for heatmap calendar generation and display. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/dayview",["core/str","core/notification","core/modal","local_assessfreq/modal_large","core/templates","core/ajax"],(function(Str,Notification,Modal,ModalLarge,Templates,Ajax){var modalObj,Dayview={};const spinner='

',stringArr=[{key:"sun",component:"calendar"},{key:"mon",component:"calendar"},{key:"tue",component:"calendar"},{key:"wed",component:"calendar"},{key:"thu",component:"calendar"},{key:"fri",component:"calendar"},{key:"sat",component:"calendar"},{key:"jan",component:"local_assessfreq"},{key:"feb",component:"local_assessfreq"},{key:"mar",component:"local_assessfreq"},{key:"apr",component:"local_assessfreq"},{key:"may",component:"local_assessfreq"},{key:"jun",component:"local_assessfreq"},{key:"jul",component:"local_assessfreq"},{key:"aug",component:"local_assessfreq"},{key:"sep",component:"local_assessfreq"},{key:"oct",component:"local_assessfreq"},{key:"nov",component:"local_assessfreq"},{key:"dec",component:"local_assessfreq"}];var stringResult,systemTimezone="Australia/Melbourne",dayViewTitle="";const getUserDate=function(timestamp,format){return new Promise((resolve=>{const systemTimezoneTime=new Date(1e3*timestamp).toLocaleString("en-US",{timeZone:systemTimezone});let date=new Date(systemTimezoneTime);const year=date.getFullYear(),month=stringResult[7+date.getMonth()],day=date.getDate(),strftimetime=date.getHours()+":"+("0"+date.getMinutes()).substr(-2);resolve("strftimetime"===format?strftimetime:day+" "+month+" "+year+", "+strftimetime)}))},formatData=async function(response){let responseArr=JSON.parse(response),scaler=5/72;for(let i=0;i100&&(width=100-leftMargin),responseArr[i].leftmargin=leftMargin,responseArr[i].width=width,responseArr[i].end=await getUserDate(responseArr[i].timeend,"strftimetime")}return new Promise((resolve=>{resolve(responseArr)}))};return Dayview.display=function(date){modalObj.setBody(spinner),modalObj.show();let args={date:date,modules:["all"]},jsonArgs=JSON.stringify(args);Ajax.call([{methodname:"local_assessfreq_get_day_events",args:{jsondata:jsonArgs}}])[0].then(formatData).then((responseArr=>{let context={rows:responseArr};const year=responseArr[0].endyear,dayDate=responseArr[0].endday+" "+stringResult[6+parseInt(responseArr[0].endmonth)]+" "+year;modalObj.setTitle(dayViewTitle+" "+dayDate),modalObj.setBody(Templates.render("local_assessfreq/dayview",context))})).fail((()=>{Notification.exception(new Error("Failed to load day view"))}))},Dayview.init=function(){Str.get_strings(stringArr).catch((()=>{Notification.exception(new Error("Failed to load strings"))})).then((stringReturn=>{stringResult=stringReturn})),Ajax.call([{methodname:"local_assessfreq_get_system_timezone",args:{}}],!0,!1)[0].then((response=>{systemTimezone=response})).fail((()=>{Notification.exception(new Error("Failed to get system timezone"))})),Str.get_string("schedule","local_assessfreq").then((title=>{dayViewTitle=title,Modal.create({type:ModalLarge.TYPE,title:title,body:spinner,large:!0}).then((modal=>{modalObj=modal}))})).catch(Notification.exception)},Dayview})); - -//# sourceMappingURL=dayview.min.js.map \ No newline at end of file diff --git a/amd/build/dayview.min.js.map b/amd/build/dayview.min.js.map deleted file mode 100644 index 19659d1a..00000000 --- a/amd/build/dayview.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"dayview.min.js","sources":["../src/dayview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for heatmap calendar generation and display.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['core/str', 'core/notification', 'core/modal', 'local_assessfreq/modal_large', 'core/templates', 'core/ajax'],\n function (Str, Notification, Modal, ModalLarge, Templates, Ajax) {\n\n /**\n * Module level variables.\n */\n var Dayview = {};\n var modalObj;\n const spinner = '

'\n + ''\n + '

';\n\n const stringArr = [\n {key: 'sun', component: 'calendar'},\n {key: 'mon', component: 'calendar'},\n {key: 'tue', component: 'calendar'},\n {key: 'wed', component: 'calendar'},\n {key: 'thu', component: 'calendar'},\n {key: 'fri', component: 'calendar'},\n {key: 'sat', component: 'calendar'},\n {key: 'jan', component: 'local_assessfreq'},\n {key: 'feb', component: 'local_assessfreq'},\n {key: 'mar', component: 'local_assessfreq'},\n {key: 'apr', component: 'local_assessfreq'},\n {key: 'may', component: 'local_assessfreq'},\n {key: 'jun', component: 'local_assessfreq'},\n {key: 'jul', component: 'local_assessfreq'},\n {key: 'aug', component: 'local_assessfreq'},\n {key: 'sep', component: 'local_assessfreq'},\n {key: 'oct', component: 'local_assessfreq'},\n {key: 'nov', component: 'local_assessfreq'},\n {key: 'dec', component: 'local_assessfreq'},\n ];\n var stringResult;\n var systemTimezone = 'Australia/Melbourne';\n var dayViewTitle = '';\n\n const getUserDate = function (timestamp, format) {\n return new Promise((resolve) => {\n const systemTimezoneTime = new Date(timestamp * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n let date = new Date(systemTimezoneTime);\n const year = date.getFullYear();\n const month = stringResult[(7 + date.getMonth())];\n const day = date.getDate();\n const hours = date.getHours();\n const minutes = '0' + date.getMinutes();\n\n const strftimetime = hours + ':' + minutes.substr(-2); // Will display time in 10:30 format.\n const strftimedatetime = day + ' ' + month + ' ' + year + ', ' + strftimetime;\n\n if (format === 'strftimetime') {\n resolve(strftimetime);\n } else {\n resolve(strftimedatetime);\n }\n\n });\n };\n\n const formatData = async function (response) {\n let responseArr = JSON.parse(response);\n\n // We are displaying the event as a bar whose width represents the start and end time of the event.\n // We need to scale the width of the bar to match the width of the container. Therefore 100% width of the container\n // equals 24 hours (one day).\n // There are 1440 mins per day. 1440 mins equals 100%, therefore 1 min = (100/1440)%. 5/72 == 100/1440.\n let scaler = 5 / 72;\n\n for (let i = 0; i < responseArr.length; i++) {\n const year = responseArr[i].endyear;\n const month = (responseArr[i].endmonth) - 1; // Minus 1 for difference between months in PHP and JS.\n const day = responseArr[i].endday;\n const dayStart = (new Date(year, month, day).getTime()) / 1000;\n const timeStart = new Date(responseArr[i].timestart * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n const timeStartTimestamp = (new Date(timeStart).getTime()) / 1000;\n const timeEnd = new Date(responseArr[i].timeend * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n const timeEndTimestamp = (new Date(timeEnd).getTime()) / 1000;\n let secondsSinceDayStart = timeStartTimestamp - dayStart;\n let leftMargin = 0;\n let width = 0;\n\n if (secondsSinceDayStart <= 0) {\n secondsSinceDayStart = 0;\n width = ((timeEndTimestamp - dayStart) / 60) * scaler;\n responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimedatetime');\n } else {\n leftMargin = (secondsSinceDayStart / 60) * scaler;\n width = ((timeEndTimestamp - timeStartTimestamp) / 60) * scaler;\n responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimetime');\n }\n\n if (leftMargin + width > 100) {\n width = 100 - leftMargin;\n }\n\n responseArr[i].leftmargin = leftMargin;\n responseArr[i].width = width;\n responseArr[i].end = await getUserDate(responseArr[i].timeend, 'strftimetime');\n }\n\n return new Promise((resolve) => {\n resolve(responseArr);\n });\n };\n\n /**\n * Initialise the base modal to be used.\n *\n * @param {int} date The date to display the day view for.\n *\n */\n Dayview.display = function (date) {\n modalObj.setBody(spinner);\n modalObj.show();\n let args = {\n date: date,\n modules: ['all']\n };\n let jsonArgs = JSON.stringify(args);\n Ajax.call([{\n methodname: 'local_assessfreq_get_day_events',\n args: {jsondata: jsonArgs},\n }])[0]\n .then(formatData)\n .then((responseArr) => {\n\n let context = {rows: responseArr};\n const year = responseArr[0].endyear;\n const day = responseArr[0].endday;\n const month = stringResult[(6 + parseInt(responseArr[0].endmonth))];\n const dayDate = day + ' ' + month + ' ' + year;\n\n modalObj.setTitle(dayViewTitle + ' ' + dayDate);\n modalObj.setBody(Templates.render('local_assessfreq/dayview', context));\n\n }).fail(() => {\n Notification.exception(new Error('Failed to load day view'));\n });\n };\n\n /**\n * Initialise the base modal to be used.\n *\n */\n Dayview.init = function () {\n // Load the strings we'll need later.\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n Notification.exception(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n stringResult = stringReturn;\n });\n\n // Get the system timzone.\n Ajax.call([{\n methodname: 'local_assessfreq_get_system_timezone',\n args: {},\n }], true, false)[0].then((response) => {\n systemTimezone = response;\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get system timezone'));\n });\n\n Str.get_string('schedule', 'local_assessfreq').then((title) => {\n dayViewTitle = title;\n\n // Create the Modal.\n Modal.create({\n type: ModalLarge.TYPE,\n title: title,\n body: spinner,\n large: true\n })\n .then((modal) => {\n modalObj = modal;\n\n });\n }).catch(Notification.exception);\n\n };\n\n return Dayview;\n }\n);\n"],"names":["define","Str","Notification","Modal","ModalLarge","Templates","Ajax","modalObj","Dayview","spinner","stringArr","key","component","stringResult","systemTimezone","dayViewTitle","getUserDate","timestamp","format","Promise","resolve","systemTimezoneTime","Date","toLocaleString","timeZone","date","year","getFullYear","month","getMonth","day","getDate","strftimetime","getHours","getMinutes","substr","formatData","async","response","responseArr","JSON","parse","scaler","i","length","endyear","endmonth","endday","dayStart","getTime","timeStart","timestart","timeStartTimestamp","timeEnd","timeend","timeEndTimestamp","secondsSinceDayStart","leftMargin","width","start","leftmargin","end","display","setBody","show","args","modules","jsonArgs","stringify","call","methodname","jsondata","then","context","rows","dayDate","parseInt","setTitle","render","fail","exception","Error","init","get_strings","catch","stringReturn","get_string","title","create","type","TYPE","body","large","modal"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/debouncer.min.js.map b/amd/build/debouncer.min.js.map index d989c5ea..ceb85d0a 100644 --- a/amd/build/debouncer.min.js.map +++ b/amd/build/debouncer.min.js.map @@ -1 +1 @@ -{"version":3,"file":"debouncer.min.js","sources":["../src/debouncer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Debounce JS module.\n *\n * @module local_assessfreq/debouncer\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\n/**\n * Quick and dirty debounce method for the settings.\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n * @method debouncer\n * @param {function} func The function we want to keep calling.\n * @param {number} wait Our timeout.\n * @return {function}\n */\nexport const debouncer = (func, wait) => {\n let timeout;\n\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n};\n"],"names":["func","wait","timeout","args","later","clearTimeout","setTimeout"],"mappings":"yKAkCyB,CAACA,KAAMC,YACxBC,eAEG,yCAA6BC,6CAAAA,iCAC1BC,MAAQ,KACVC,aAAaH,SACbF,QAAQG,OAGZE,aAAaH,SACbA,QAAUI,WAAWF,MAAOH"} \ No newline at end of file +{"version":3,"file":"debouncer.min.js","sources":["../src/debouncer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Debounce JS module.\n *\n * @module local_assessfreq/debouncer\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\n/**\n * Quick and dirty debounce method for the settings.\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n * @method debouncer\n * @param {function} func The function we want to keep calling.\n * @param {number} wait Our timeout.\n * @return {function}\n */\nexport const debouncer = (func, wait) => {\n let timeout;\n\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n};\n"],"names":["func","wait","timeout","args","later","clearTimeout","setTimeout"],"mappings":"yKAmCyB,CAACA,KAAMC,YACxBC,eAEG,yCAA6BC,6CAAAA,iCAC1BC,MAAQ,KACVC,aAAaH,SACbF,QAAQG,OAGZE,aAAaH,SACbA,QAAUI,WAAWF,MAAOH"} \ No newline at end of file diff --git a/amd/build/form_modal.min.js b/amd/build/form_modal.min.js deleted file mode 100644 index 6a7baee2..00000000 --- a/amd/build/form_modal.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Javascript for report card display and processing. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/form_modal",["core/str","core/modal","core/fragment","core/ajax","core/notification"],(function(Str,Modal,Fragment,Ajax,Notification){var contextid,modalObj,callback,FormModal={},resetOptions=[];const spinner='

',observerConfig={attributes:!0,childList:!1,subtree:!0},observer=new MutationObserver((function(mutationsList){for(let i=0;i{let quizArray=JSON.parse(response),selectElement=document.getElementById("id_quiz"),selectElementLength=selectElement.options.length;null!==document.getElementById("noquizwarning")&&document.getElementById("noquizwarning").remove();for(let j=selectElementLength-1;j>=0;j--)selectElement.options[j]=null;if(quizArray.length>0){for(let k=0;k{selectElement.appendChild(option)})),document.getElementById("id_quiz").value=0,selectElement.disabled=!0})).fail((()=>{Notification.exception(new Error("Failed to get quizzes"))}));break}}})),updateModalBody=function(formdata){void 0===formdata&&(formdata={});let params={jsonformdata:JSON.stringify(formdata)};new Promise(((resolve,reject)=>{Str.get_strings([{key:"selectcourse",component:"local_assessfreq"},{key:"loadingquiz",component:"local_assessfreq"}]).catch((()=>{reject(new Error("Failed to load strings"))})).then((stringReturn=>{for(let i=0;i{Str.get_string("searchquiz","local_assessfreq").then((title=>{modalObj.setTitle(title),modalObj.setBody(Fragment.loadFragment("local_assessfreq","new_base_form",contextid,params));let modalContainer=document.querySelectorAll('[data-region*="modal-container"]')[0];observer.observe(modalContainer,observerConfig)})).catch((()=>{Notification.exception(new Error("Failed to load string: searchquiz"))}))}))},processModalForm=function(e){e.preventDefault();let quizElement=document.getElementById("id_quiz"),quizId=quizElement.options[quizElement.selectedIndex].value,courseId=document.getElementById("id_courses").dataset.course;void 0===courseId||quizId<1?null===document.getElementById("noquizwarning")&&Str.get_string("noquizselected","local_assessfreq").then((warning=>{let element=document.createElement("div");element.innerHTML=warning,element.id="noquizwarning",element.classList.add("alert","alert-danger"),modalObj.getBody().prepend(element)})).catch((()=>{Notification.exception(new Error("Failed to load string: searchquiz"))})):(modalObj.hide(),modalObj.setBody(""),observer.disconnect(),callback(quizId,courseId))},displayModalForm=function(){updateModalBody(),modalObj.show()};return FormModal.init=function(context,processDashboard){contextid=context,callback=processDashboard,Str.get_string("loading","local_assessfreq").then((title=>{Modal.create({type:Modal.types.DEFAULT,title:title,body:spinner,large:!0}).then((modal=>{(modalObj=modal).getRoot().on("click","#id_submitbutton",processModalForm),modalObj.getRoot().on("click","#id_cancel",(e=>{e.preventDefault(),modalObj.setBody(spinner),modalObj.hide()}))}))})).catch(Notification.exception),document.getElementById("local-assessfreq-find-quiz").addEventListener("click",displayModalForm)},FormModal})); - -//# sourceMappingURL=form_modal.min.js.map \ No newline at end of file diff --git a/amd/build/form_modal.min.js.map b/amd/build/form_modal.min.js.map deleted file mode 100644 index 8501197b..00000000 --- a/amd/build/form_modal.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"form_modal.min.js","sources":["../src/form_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['core/str', 'core/modal', 'core/fragment', 'core/ajax', 'core/notification'],\n function (Str, Modal, Fragment, Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n var FormModal = {};\n var contextid;\n var modalObj;\n var resetOptions = [];\n var callback;\n\n const spinner = '

'\n + ''\n + '

';\n\n const observerConfig = { attributes: true, childList: false, subtree: true };\n\n const ObserverCallback = function (mutationsList) {\n for (let i = 0; i < mutationsList.length; i++) {\n let element = mutationsList[i].target;\n if (element.tagName.toLowerCase() === 'span' && element.classList.contains('badge')) {\n element.addEventListener('click', updateModalBody);\n document.getElementById('id_courses').dataset.course = element.dataset.value;\n\n document.getElementById('id_quiz').value = -1;\n Ajax.call([{\n methodname: 'local_assessfreq_get_quizzes',\n args: {\n query: mutationsList[i].target.dataset.value\n },\n }])[0].done((response) => {\n let quizArray = JSON.parse(response);\n let selectElement = document.getElementById('id_quiz');\n let selectElementLength = selectElement.options.length;\n if (document.getElementById('noquizwarning') !== null) {\n document.getElementById('noquizwarning').remove();\n }\n // Clear exisitng options.\n for (let j = selectElementLength - 1; j >= 0; j--) {\n selectElement.options[j] = null;\n }\n\n if (quizArray.length > 0) {\n // Add new options.\n for (let k = 0; k < quizArray.length; k++) {\n let opt = quizArray[k];\n let el = document.createElement('option');\n el.textContent = opt.name;\n el.value = opt.id;\n selectElement.appendChild(el);\n }\n selectElement.removeAttribute('disabled');\n if (document.getElementById('noquizwarning') !== null) {\n document.getElementById('noquizwarning').remove();\n }\n } else {\n resetOptions.forEach((option) => {\n selectElement.appendChild(option);\n });\n document.getElementById('id_quiz').value = 0;\n selectElement.disabled = true;\n }\n\n }).fail(() => {\n Notification.exception(new Error('Failed to get quizzes'));\n });\n\n break;\n }\n }\n };\n\n const observer = new MutationObserver(ObserverCallback);\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function () {\n Str.get_string('loading', 'local_assessfreq').then((title) => {\n // Create the Modal.\n Modal.create({\n type: Modal.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n })\n .then((modal) => {\n modalObj = modal;\n\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', (e) => {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n return;\n }).catch(Notification.exception);\n };\n\n const getOptionPlaceholders = function () {\n return new Promise((resolve, reject) => {\n const stringArr = [\n {key: 'selectcourse', component: 'local_assessfreq'},\n {key: 'loadingquiz', component: 'local_assessfreq'},\n ];\n\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n reject(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n for (let i = 0; i < stringReturn.length; i++) {\n let el = document.createElement('option');\n el.textContent = stringReturn[i];\n el.value = 0 - i;\n resetOptions.push(el);\n }\n resolve();\n });\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {Object} formdata\n * @private\n */\n const updateModalBody = function (formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata)\n };\n\n getOptionPlaceholders()\n .then(() => {\n Str.get_string('searchquiz', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_base_form', contextid, params));\n let modalContainer = document.querySelectorAll('[data-region*=\"modal-container\"]')[0];\n observer.observe(modalContainer, observerConfig);\n\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: searchquiz'));\n });\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n const processModalForm = function (e) {\n e.preventDefault(); // Stop modal from closing.\n\n let quizElement = document.getElementById('id_quiz');\n let quizId = quizElement.options[quizElement.selectedIndex].value;\n let courseId = document.getElementById('id_courses').dataset.course;\n\n if (courseId === undefined || quizId < 1) {\n if (document.getElementById('noquizwarning') === null) {\n Str.get_string('noquizselected', 'local_assessfreq').then((warning) => {\n let element = document.createElement('div');\n element.innerHTML = warning;\n element.id = 'noquizwarning';\n element.classList.add('alert', 'alert-danger');\n modalObj.getBody().prepend(element);\n\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: searchquiz'));\n });\n }\n } else {\n modalObj.hide(); // Close modal.\n modalObj.setBody(''); // Cleaer form.\n observer.disconnect(); // Remove observer.\n callback(quizId, courseId); // Trigger dashboard update.\n }\n\n };\n\n /**\n * Display the Modal form.\n */\n const displayModalForm = function () {\n updateModalBody();\n modalObj.show();\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n *\n * @param {int} context The context id for the dashboard.\n * @param {function} processDashboard The callback function to process the dashboard.\n *\n */\n FormModal.init = function (context, processDashboard) {\n contextid = context;\n callback = processDashboard;\n createModal();\n\n let createBroadcastButton = document.getElementById('local-assessfreq-find-quiz');\n createBroadcastButton.addEventListener('click', displayModalForm);\n };\n\n return FormModal;\n }\n);\n"],"names":["define","Str","Modal","Fragment","Ajax","Notification","contextid","modalObj","callback","FormModal","resetOptions","spinner","observerConfig","attributes","childList","subtree","observer","MutationObserver","mutationsList","i","length","element","target","tagName","toLowerCase","classList","contains","addEventListener","updateModalBody","document","getElementById","dataset","course","value","call","methodname","args","query","done","response","quizArray","JSON","parse","selectElement","selectElementLength","options","remove","j","k","opt","el","createElement","textContent","name","id","appendChild","removeAttribute","forEach","option","disabled","fail","exception","Error","formdata","params","stringify","Promise","resolve","reject","get_strings","key","component","catch","then","stringReturn","push","get_string","title","setTitle","setBody","loadFragment","modalContainer","querySelectorAll","observe","processModalForm","e","preventDefault","quizElement","quizId","selectedIndex","courseId","undefined","warning","innerHTML","add","getBody","prepend","hide","disconnect","displayModalForm","show","init","context","processDashboard","create","type","types","DEFAULT","body","large","modal","getRoot","on"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/modal_large.min.js b/amd/build/modal_large.min.js index 0a4d9a2c..19b59b2b 100644 --- a/amd/build/modal_large.min.js +++ b/amd/build/modal_large.min.js @@ -1,9 +1,11 @@ /** * Javascript for large modal . * + * @module local_assessfreq/modal_large + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("local_assessfreq/modal_large",["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry"],(function($,Notification,CustomEvents,Modal,ModalRegistry){var registered=!1,ModalLarge=function(root){Modal.call(this,root)};return ModalLarge.TYPE="local_assesfreq-large_modal",(ModalLarge.prototype=Object.create(Modal.prototype)).constructor=ModalLarge,ModalLarge.prototype.registerEventListeners=function(){Modal.prototype.registerEventListeners.call(this)},registered||(ModalRegistry.register(ModalLarge.TYPE,ModalLarge,"local_assessfreq/modal_large"),registered=!0),ModalLarge})); +define("local_assessfreq/modal_large",["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry"],(function($,Notification,CustomEvents,Modal,ModalRegistry){let registered=!1,ModalLarge=function(root){Modal.call(this,root)};return ModalLarge.TYPE="local_assesfreq-large_modal",(ModalLarge.prototype=Object.create(Modal.prototype)).constructor=ModalLarge,ModalLarge.prototype.registerEventListeners=function(){Modal.prototype.registerEventListeners.call(this)},registered||(ModalRegistry.register(ModalLarge.TYPE,ModalLarge,"local_assessfreq/modal_large"),registered=!0),ModalLarge})); //# sourceMappingURL=modal_large.min.js.map \ No newline at end of file diff --git a/amd/build/modal_large.min.js.map b/amd/build/modal_large.min.js.map index a0e2851c..dfdc2028 100644 --- a/amd/build/modal_large.min.js.map +++ b/amd/build/modal_large.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_large.min.js","sources":["../src/modal_large.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for large modal .\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'],\n function ($, Notification, CustomEvents, Modal, ModalRegistry) {\n\n var registered = false;\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n var ModalLarge = function (root) {\n Modal.call(this, root);\n };\n\n ModalLarge.TYPE = 'local_assesfreq-large_modal';\n ModalLarge.prototype = Object.create(Modal.prototype);\n ModalLarge.prototype.constructor = ModalLarge;\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n ModalLarge.prototype.registerEventListeners = function () {\n // Apply parent event listeners.\n Modal.prototype.registerEventListeners.call(this);\n };\n\n // Automatically register with the modal registry the first time this module is imported so that you can create modals\n // of this type using the modal factory.\n if (!registered) {\n ModalRegistry.register(ModalLarge.TYPE, ModalLarge, 'local_assessfreq/modal_large');\n registered = true;\n }\n\n return ModalLarge;\n }\n);\n"],"names":["define","$","Notification","CustomEvents","Modal","ModalRegistry","registered","ModalLarge","root","call","this","TYPE","prototype","Object","create","constructor","registerEventListeners","register"],"mappings":";;;;;;AAsBAA,sCACI,CAAC,SAAU,oBAAqB,iCAAkC,aAAc,wBAChF,SAAUC,EAAGC,aAAcC,aAAcC,MAAOC,mBAExCC,YAAa,EAObC,WAAa,SAAUC,MACvBJ,MAAMK,KAAKC,KAAMF,cAGrBD,WAAWI,KAAO,+BAClBJ,WAAWK,UAAYC,OAAOC,OAAOV,MAAMQ,YACtBG,YAAcR,WAOnCA,WAAWK,UAAUI,uBAAyB,WAE1CZ,MAAMQ,UAAUI,uBAAuBP,KAAKC,OAK3CJ,aACDD,cAAcY,SAASV,WAAWI,KAAMJ,WAAY,gCACpDD,YAAa,GAGVC"} \ No newline at end of file +{"version":3,"file":"modal_large.min.js","sources":["../src/modal_large.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for large modal .\n *\n * @module local_assessfreq/modal_large\n * @package\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'],\n function($, Notification, CustomEvents, Modal, ModalRegistry) {\n\n let registered = false;\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n let ModalLarge = function(root) {\n Modal.call(this, root);\n };\n\n ModalLarge.TYPE = 'local_assesfreq-large_modal';\n ModalLarge.prototype = Object.create(Modal.prototype);\n ModalLarge.prototype.constructor = ModalLarge;\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n ModalLarge.prototype.registerEventListeners = function () {\n // Apply parent event listeners.\n Modal.prototype.registerEventListeners.call(this);\n };\n\n // Automatically register with the modal registry the first time this module is imported so that you can create modals\n // of this type using the modal factory.\n if (!registered) {\n ModalRegistry.register(ModalLarge.TYPE, ModalLarge, 'local_assessfreq/modal_large');\n registered = true;\n }\n\n return ModalLarge;\n }\n);\n"],"names":["define","$","Notification","CustomEvents","Modal","ModalRegistry","registered","ModalLarge","root","call","this","TYPE","prototype","Object","create","constructor","registerEventListeners","register"],"mappings":";;;;;;;;AAwBAA,sCACI,CAAC,SAAU,oBAAqB,iCAAkC,aAAc,wBAChF,SAASC,EAAGC,aAAcC,aAAcC,MAAOC,mBAEvCC,YAAa,EAObC,WAAa,SAASC,MACtBJ,MAAMK,KAAKC,KAAMF,cAGrBD,WAAWI,KAAO,+BAClBJ,WAAWK,UAAYC,OAAOC,OAAOV,MAAMQ,YACtBG,YAAcR,WAOnCA,WAAWK,UAAUI,uBAAyB,WAE1CZ,MAAMQ,UAAUI,uBAAuBP,KAAKC,OAK3CJ,aACDD,cAAcY,SAASV,WAAWI,KAAMJ,WAAY,gCACpDD,YAAa,GAGVC"} \ No newline at end of file diff --git a/amd/build/override_modal.min.js b/amd/build/override_modal.min.js index 5f571ae4..1afe8683 100644 --- a/amd/build/override_modal.min.js +++ b/amd/build/override_modal.min.js @@ -1,9 +1,10 @@ /** * Javascript for report card display and processing. * + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("local_assessfreq/override_modal",["jquery","core/str","core/modal_factory","core/modal_events","core/fragment","core/ajax","core/notification"],(function($,Str,Modal,ModalEvents,Fragment,Ajax,Notification){var contextid,modalObj,callback,quizid,userid,hoursFilter,OverrideModal={};const spinner='

',createModal=function(){Str.get_string("loading","local_assessfreq").then((title=>{Modal.create({type:Modal.types.DEFAULT,title:title,body:spinner,large:!0}).then((modal=>{(modalObj=modal).getRoot().on("click","#id_submitbutton",processModalForm),modalObj.getRoot().on("click","#id_cancel",(function(e){e.preventDefault(),modalObj.setBody(spinner),modalObj.hide()}))}))})).catch((()=>{Notification.exception(new Error("Failed to load string: loading"))}))},updateModalBody=function(quiz,user,formdata){void 0===formdata&&(formdata={});let params={jsonformdata:JSON.stringify(formdata),quizid:quiz,userid:user};modalObj.setBody(spinner),Str.get_string("useroverride","local_assessfreq").then((title=>{modalObj.setTitle(title),modalObj.setBody(Fragment.loadFragment("local_assessfreq","new_override_form",contextid,params))})).catch((()=>{Notification.exception(new Error("Failed to load string: useroverride"))}))};function processModalForm(e){e.preventDefault();let overrideform=modalObj.getRoot().find("form").serialize(),formjson=JSON.stringify(overrideform);var invalid=$.merge(modalObj.getRoot().find('[aria-invalid="true"]'),modalObj.getRoot().find(".error"));invalid.length?invalid.first().focus():Ajax.call([{methodname:"local_assessfreq_process_override_form",args:{jsonformdata:formjson,quizid:quizid}}])[0].done((()=>{modalObj.setBody(spinner),modalObj.hide(),hoursFilter?callback(quizid,hoursFilter):callback(quizid)})).fail((()=>{updateModalBody(quizid,userid,overrideform)}))}return OverrideModal.displayModalForm=function(quiz,user){let hours=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;quizid=quiz,userid=user,hoursFilter=hours,updateModalBody(quiz,user),modalObj.show()},OverrideModal.init=function(context,callbackFunction){let hours=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;contextid=context,callback=callbackFunction,hoursFilter=hours,createModal()},OverrideModal})); +define("local_assessfreq/override_modal",["jquery","core/str","core/modal","core/modal_factory","core/modal_events","core/fragment","core/ajax"],(function($,Str,Modal,ModalFactory,ModalEvents,Fragment,Ajax){let contextid,activitytype,modalObj,activityid,userid,tableHandler,OverrideModal={};const spinner='

',createModal=function(){Str.get_string("loading").then((title=>{Modal.create({type:ModalFactory.types.DEFAULT,title:title,body:spinner,large:!0}).then((modal=>{modalObj=modal,modalObj.getRoot().on("click","#id_submitbutton",processModalForm),modalObj.getRoot().on("click","#id_cancel",(function(e){e.preventDefault(),modalObj.setBody(spinner),modalObj.hide()}))}))}))},updateModalBody=function(activity,user,formdata){void 0===formdata&&(formdata={});let params={jsonformdata:JSON.stringify(formdata),activitytype:activitytype,activityid:activity,userid:user};modalObj.setBody(spinner),Str.get_string("modal:useroverride","local_assessfreq").then((title=>{modalObj.setTitle(title),modalObj.setBody(Fragment.loadFragment("local_assessfreq","new_override_form",contextid,params))}))};function processModalForm(e){e.preventDefault();let overrideform=modalObj.getRoot().find("form").serialize(),formjson=JSON.stringify(overrideform),invalid=$.merge(modalObj.getRoot().find('[aria-invalid="true"]'),modalObj.getRoot().find(".error"));invalid.length?invalid.first().focus():Ajax.call([{methodname:"local_assessfreq_process_override_form",args:{jsonformdata:formjson,activityid:activityid,activitytype:activitytype}}])[0].done((()=>{modalObj.setBody(spinner),modalObj.hide(),void 0!==tableHandler&&tableHandler.getTable()})).fail((()=>{updateModalBody(activityid,userid,overrideform)}))}return OverrideModal.displayModalForm=function(activity,user){activityid=activity,userid=user,updateModalBody(activityid,user),modalObj.show()},OverrideModal.init=function(context,module){let tablehandler=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;activitytype=module,contextid=context,tableHandler=tablehandler,createModal()},OverrideModal})); //# sourceMappingURL=override_modal.min.js.map \ No newline at end of file diff --git a/amd/build/override_modal.min.js.map b/amd/build/override_modal.min.js.map index 3352be45..19e086b2 100644 --- a/amd/build/override_modal.min.js.map +++ b/amd/build/override_modal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"override_modal.min.js","sources":["../src/override_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/notification'],\n function ($,Str, Modal, ModalEvents, Fragment, Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n var OverrideModal = {};\n var contextid;\n var modalObj;\n var callback;\n var quizid;\n var userid;\n var hoursFilter;\n\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function () {\n Str.get_string('loading', 'local_assessfreq').then((title) => {\n // Create the Modal.\n Modal.create({\n type: Modal.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n })\n .then((modal) => {\n modalObj = modal;\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', function (e) {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loading'));\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {int} quiz The quiz id.\n * @param {int} user The user id.\n * @param {object} formdata The form data.\n */\n const updateModalBody = function (quiz, user, formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata),\n 'quizid': quiz,\n 'userid': user\n };\n\n modalObj.setBody(spinner);\n Str.get_string('useroverride', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params));\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: useroverride'));\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n let overrideform = modalObj.getRoot().find('form').serialize();\n let formjson = JSON.stringify(overrideform);\n\n // Handle invalid form fields for better UX.\n // I hate that I had to use JQuery for this.\n var invalid = $.merge(\n modalObj.getRoot().find('[aria-invalid=\"true\"]'),\n modalObj.getRoot().find('.error')\n );\n\n if (invalid.length) {\n invalid.first().focus();\n return;\n }\n\n // Submit form via ajax.\n Ajax.call([{\n methodname: 'local_assessfreq_process_override_form',\n args: {\n 'jsonformdata': formjson,\n 'quizid': quizid\n },\n }])[0].done(() => {\n // For submission succeeded.\n modalObj.setBody(spinner);\n modalObj.hide();\n if (hoursFilter) {\n callback(quizid, hoursFilter);\n } else {\n callback(quizid);\n }\n }).fail(() => {\n // Form submission failed server side, redisplay with errors.\n updateModalBody(quizid, userid, overrideform);\n });\n }\n\n /**\n * Display the Modal form.\n *\n * @param {int} quiz The quiz id.\n * @param {int} user The user id.\n * @param {int} hours The hours to filter the quiz by.\n */\n OverrideModal.displayModalForm = function (quiz, user, hours = null) {\n quizid = quiz;\n userid = user;\n hoursFilter = hours;\n updateModalBody(quiz, user);\n modalObj.show();\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n *\n * @param {int} context The context id for the dashboard.\n * @param {function} callbackFunction The callback function to call after the modal is closed.\n * @param {int} hours The hours to filter the quiz by.\n */\n OverrideModal.init = function (context, callbackFunction, hours = null) {\n contextid = context;\n callback = callbackFunction;\n hoursFilter = hours;\n createModal();\n };\n\n return OverrideModal;\n }\n);\n"],"names":["define","$","Str","Modal","ModalEvents","Fragment","Ajax","Notification","contextid","modalObj","callback","quizid","userid","hoursFilter","OverrideModal","spinner","createModal","get_string","then","title","create","type","types","DEFAULT","body","large","modal","getRoot","on","processModalForm","e","preventDefault","setBody","hide","catch","exception","Error","updateModalBody","quiz","user","formdata","params","JSON","stringify","setTitle","loadFragment","overrideform","find","serialize","formjson","invalid","merge","length","first","focus","call","methodname","args","done","fail","displayModalForm","hours","show","init","context","callbackFunction"],"mappings":";;;;;;AAsBAA,yCACI,CAAC,SAAU,WAAY,qBAAsB,oBAAqB,gBAAiB,YAAa,sBAChG,SAAUC,EAAEC,IAAKC,MAAOC,YAAaC,SAAUC,KAAMC,kBAM7CC,UACAC,SACAC,SACAC,OACAC,OACAC,YANAC,cAAgB,SAQdC,QAAU,sFASVC,YAAc,WAChBd,IAAIe,WAAW,UAAW,oBAAoBC,MAAMC,QAEhDhB,MAAMiB,OAAO,CACTC,KAAMlB,MAAMmB,MAAMC,QAClBJ,MAAOA,MACPK,KAAMT,QACNU,OAAO,IAEVP,MAAMQ,SACHjB,SAAWiB,OAEFC,UAAUC,GAAG,QAAS,mBAAoBC,kBACnDpB,SAASkB,UAAUC,GAAG,QAAS,cAAc,SAAUE,GACnDA,EAAEC,iBACFtB,SAASuB,QAAQjB,SACjBN,SAASwB,gBAIlBC,OAAM,KACL3B,aAAa4B,UAAU,IAAIC,MAAM,uCAWnCC,gBAAkB,SAAUC,KAAMC,KAAMC,eAClB,IAAbA,WACPA,SAAW,QAGXC,OAAS,cACOC,KAAKC,UAAUH,iBACrBF,YACAC,MAGd9B,SAASuB,QAAQjB,SACjBb,IAAIe,WAAW,eAAgB,oBAAoBC,MAAMC,QACrDV,SAASmC,SAASzB,OAClBV,SAASuB,QAAQ3B,SAASwC,aAAa,mBAAoB,oBAAqBrC,UAAWiC,YAE5FP,OAAM,KACL3B,aAAa4B,UAAU,IAAIC,MAAM,qDAUhCP,iBAAiBC,GACtBA,EAAEC,qBAGEe,aAAerC,SAASkB,UAAUoB,KAAK,QAAQC,YAC/CC,SAAWP,KAAKC,UAAUG,kBAI1BI,QAAUjD,EAAEkD,MACZ1C,SAASkB,UAAUoB,KAAK,yBACxBtC,SAASkB,UAAUoB,KAAK,WAGxBG,QAAQE,OACRF,QAAQG,QAAQC,QAKpBhD,KAAKiD,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,cACcR,gBACNtC,WAEd,GAAG+C,MAAK,KAERjD,SAASuB,QAAQjB,SACjBN,SAASwB,OACLpB,YACAH,SAASC,OAAQE,aAEjBH,SAASC,WAEdgD,MAAK,KAEJtB,gBAAgB1B,OAAQC,OAAQkC,wBAWxChC,cAAc8C,iBAAmB,SAAUtB,KAAMC,UAAMsB,6DAAQ,KAC3DlD,OAAS2B,KACT1B,OAAS2B,KACT1B,YAAcgD,MACdxB,gBAAgBC,KAAMC,MACtB9B,SAASqD,QAUbhD,cAAciD,KAAO,SAAUC,QAASC,sBAAkBJ,6DAAQ,KAC9DrD,UAAYwD,QACZtD,SAAWuD,iBACXpD,YAAcgD,MACd7C,eAGGF"} \ No newline at end of file +{"version":3,"file":"override_modal.min.js","sources":["../src/override_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @package\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'],\n function($, Str, Modal, ModalFactory, ModalEvents, Fragment, Ajax) {\n\n /**\n * Module level variables.\n */\n let OverrideModal = {};\n let contextid;\n let activitytype;\n let modalObj;\n let activityid;\n let userid;\n let tableHandler;\n\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function() {\n Str.get_string('loading').then((title) => {\n // Create the Modal.\n Modal.create({\n type: ModalFactory.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n }).then((modal) => {\n modalObj = modal;\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', function(e) {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {Integer} activity\n * @param {Integer} user\n * @param {Object} formdata\n * @private\n */\n const updateModalBody = function(activity, user, formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata),\n 'activitytype': activitytype,\n 'activityid': activity,\n 'userid': user\n };\n\n modalObj.setBody(spinner);\n Str.get_string('modal:useroverride', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params));\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n let overrideform = modalObj.getRoot().find('form').serialize();\n let formjson = JSON.stringify(overrideform);\n\n // Handle invalid form fields for better UX.\n // I hate that I had to use JQuery for this.\n let invalid = $.merge(\n modalObj.getRoot().find('[aria-invalid=\"true\"]'),\n modalObj.getRoot().find('.error')\n );\n\n if (invalid.length) {\n invalid.first().focus();\n return;\n }\n\n // Submit form via ajax.\n Ajax.call([{\n methodname: 'local_assessfreq_process_override_form',\n args: {\n 'jsonformdata': formjson,\n 'activityid': activityid,\n 'activitytype': activitytype,\n },\n }])[0].done(() => {\n // For submission succeeded.\n modalObj.setBody(spinner);\n modalObj.hide();\n if (tableHandler !== undefined) {\n tableHandler.getTable();\n }\n }).fail(() => {\n // Form submission failed server side, redisplay with errors.\n updateModalBody(activityid, userid, overrideform);\n });\n }\n\n /**\n * Display the Modal form.\n * @param {Integer} activity\n * @param {Integer} user\n */\n OverrideModal.displayModalForm = function(activity, user) {\n activityid = activity;\n userid = user;\n updateModalBody(activityid, user);\n modalObj.show();\n };\n\n /**\n * Initialise method for dashboard rendering.\n * @param {Integer} context\n * @param {String} module\n * @param {TableHandler} tablehandler If defined will trigger a table refresh on form save.\n */\n OverrideModal.init = function(context, module, tablehandler = undefined) {\n activitytype = module;\n contextid = context;\n tableHandler = tablehandler;\n createModal();\n };\n\n return OverrideModal;\n }\n);\n"],"names":["define","$","Str","Modal","ModalFactory","ModalEvents","Fragment","Ajax","contextid","activitytype","modalObj","activityid","userid","tableHandler","OverrideModal","spinner","createModal","get_string","then","title","create","type","types","DEFAULT","body","large","modal","getRoot","on","processModalForm","e","preventDefault","setBody","hide","updateModalBody","activity","user","formdata","params","JSON","stringify","setTitle","loadFragment","overrideform","find","serialize","formjson","invalid","merge","length","first","focus","call","methodname","args","done","undefined","getTable","fail","displayModalForm","show","init","context","module","tablehandler"],"mappings":";;;;;;;AAuBAA,yCACI,CAAC,SAAU,WAAY,aAAc,qBAAsB,oBAAqB,gBAAiB,cACjG,SAASC,EAAGC,IAAKC,MAAOC,aAAcC,YAAaC,SAAUC,UAMrDC,UACAC,aACAC,SACAC,WACAC,OACAC,aANAC,cAAgB,SAQdC,QAAU,sFASVC,YAAc,WAChBd,IAAIe,WAAW,WAAWC,MAAMC,QAE5BhB,MAAMiB,OAAO,CACTC,KAAMjB,aAAakB,MAAMC,QACzBJ,MAAOA,MACPK,KAAMT,QACNU,OAAO,IACRP,MAAMQ,QACDhB,SAAWgB,MAEXhB,SAASiB,UAAUC,GAAG,QAAS,mBAAoBC,kBACnDnB,SAASiB,UAAUC,GAAG,QAAS,cAAc,SAASE,GAClDA,EAAEC,iBACFrB,SAASsB,QAAQjB,SACjBL,SAASuB,iBAcvBC,gBAAkB,SAASC,SAAUC,KAAMC,eACrB,IAAbA,WACPA,SAAW,QAGXC,OAAS,cACOC,KAAKC,UAAUH,uBACf5B,wBACF0B,gBACJC,MAGd1B,SAASsB,QAAQjB,SACjBb,IAAIe,WAAW,qBAAsB,oBAAoBC,MAAMC,QAC3DT,SAAS+B,SAAStB,OAClBT,SAASsB,QAAQ1B,SAASoC,aAAa,mBAAoB,oBAAqBlC,UAAW8B,sBAU1FT,iBAAiBC,GACtBA,EAAEC,qBAGEY,aAAejC,SAASiB,UAAUiB,KAAK,QAAQC,YAC/CC,SAAWP,KAAKC,UAAUG,cAI1BI,QAAU9C,EAAE+C,MACZtC,SAASiB,UAAUiB,KAAK,yBACxBlC,SAASiB,UAAUiB,KAAK,WAGxBG,QAAQE,OACRF,QAAQG,QAAQC,QAKpB5C,KAAK6C,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,cACcR,oBACFnC,wBACEF,iBAEpB,GAAG8C,MAAK,KAER7C,SAASsB,QAAQjB,SACjBL,SAASuB,YACYuB,IAAjB3C,cACAA,aAAa4C,cAElBC,MAAK,KAEJxB,gBAAgBvB,WAAYC,OAAQ+B,wBAS5C7B,cAAc6C,iBAAmB,SAASxB,SAAUC,MAChDzB,WAAawB,SACbvB,OAASwB,KACTF,gBAAgBvB,WAAYyB,MAC5B1B,SAASkD,QASb9C,cAAc+C,KAAO,SAASC,QAASC,YAAQC,yEAAeR,EAC1D/C,aAAesD,OACfvD,UAAYsD,QACZjD,aAAemD,aACfhD,eAGGF"} \ No newline at end of file diff --git a/amd/build/student_search.min.js b/amd/build/student_search.min.js index c2b1dfed..2d401351 100644 --- a/amd/build/student_search.min.js +++ b/amd/build/student_search.min.js @@ -3,6 +3,7 @@ define("local_assessfreq/student_search",["exports","jquery","core/notification" * Javascript for student search display and processing. * * @module local_assessfreq/student_search + * @package local_assessfreq * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */var contextid;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),_notification=_interopRequireDefault(_notification),_override_modal=_interopRequireDefault(_override_modal),TableHandler=_interopRequireWildcard(TableHandler),UserPreference=_interopRequireWildcard(UserPreference);var counterid,hoursAhead=4,hoursBehind=1,refreshPeriod=60;const refreshCounter=function(){let reset=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],progressElement=document.getElementById("local-assessfreq-period-progress");!0===reset&&(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100)),counterid||(counterid=setInterval((()=>{let progressWidthAria=progressElement.getAttribute("aria-valuenow");const progressStep=100/refreshPeriod;progressWidthAria-progressStep>0?(progressElement.setAttribute("style","width: "+(progressWidthAria-progressStep)+"%"),progressElement.setAttribute("aria-valuenow",progressWidthAria-progressStep)):(clearInterval(counterid),counterid=null,progressElement.setAttribute("style","width: 100%"),progressElement.setAttribute("aria-valuenow",100),TableHandler.getTable(0,[hoursAhead,hoursBehind],null),refreshCounter())}),1e3))},tableSearchAheadSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("local_assessfreq_student_search_table_hoursahead_preference",hours).then((()=>{hoursAhead=hours,TableHandler.getTable(0,[hoursAhead,hoursBehind],null)})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: hours ahead"))}))}},tableSearchBehindSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("local_assessfreq_student_search_table_hoursbehind_preference",hours).then((()=>{hoursBehind=hours,TableHandler.getTable(0,[hoursAhead,hoursBehind],null)})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: hours behind"))}))}},refreshAction=event=>{event.preventDefault();var element=event.target;null!==element.closest("button")&&"local-assessfreq-refresh-quiz-dashboard"===element.closest("button").id?(refreshCounter(!0),TableHandler.getTable(0,[hoursAhead,hoursBehind],null)):"a"===element.tagName.toLowerCase()&&(refreshPeriod=element.dataset.period,refreshCounter(!0),UserPreference.setUserPreference("local_assessfreq_quiz_refresh_preference",refreshPeriod))};_exports.init=context=>{contextid=context,TableHandler.init(0,contextid,"local-assessfreq-student-search-table","local-assessfreq-student-search","get_student_search_table","local_assessfreq_student_search_table_rows_preference","local-assessfreq-quiz-student-table-search","local_assessfreq_student_search_table","local_assessfreq_set_table_preference");let tableSearchInputElement=document.getElementById("local-assessfreq-quiz-student-table-search"),tableSearchResetElement=document.getElementById("local-assessfreq-quiz-student-table-search-reset"),tableSearchRowsElement=document.getElementById("local-assessfreq-quiz-student-table-rows"),tableSearchAheadElement=document.getElementById("local-assessfreq-quiz-student-table-hoursahead"),tableSearchBehindElement=document.getElementById("local-assessfreq-quiz-student-table-hoursbehind"),refreshElement=document.getElementById("local-assessfreq-period-container");tableSearchInputElement.addEventListener("keyup",TableHandler.tableSearch),tableSearchInputElement.addEventListener("paste",TableHandler.tableSearch),tableSearchResetElement.addEventListener("click",TableHandler.tableSearchReset),tableSearchRowsElement.addEventListener("click",TableHandler.tableSearchRowSet),tableSearchAheadElement.addEventListener("click",tableSearchAheadSet),tableSearchBehindElement.addEventListener("click",tableSearchBehindSet),refreshElement.addEventListener("click",refreshAction),_jquery.default.when(UserPreference.getUserPreference("local_assessfreq_student_search_table_hoursahead_preference").then((response=>{hoursAhead=response.preferences[0].value?response.preferences[0].value:4})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: hoursahead"))})),UserPreference.getUserPreference("local_assessfreq_student_search_table_hoursbehind_preference").then((response=>{hoursBehind=response.preferences[0].value?response.preferences[0].value:1})).fail((()=>{_notification.default.exception(new Error("Failed to get use preference: hoursahead"))}))).done((function(){TableHandler.getTable(0,[hoursAhead,hoursBehind],null),_override_modal.default.init(context,TableHandler.getTable,[hoursAhead,hoursBehind])}))}})); diff --git a/amd/build/student_search.min.js.map b/amd/build/student_search.min.js.map index 7d134685..6a4a2b5b 100644 --- a/amd/build/student_search.min.js.map +++ b/amd/build/student_search.min.js.map @@ -1 +1 @@ -{"version":3,"file":"student_search.min.js","sources":["../src/student_search.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for student search display and processing.\n *\n * @module local_assessfreq/student_search\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Notification from 'core/notification';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar hoursAhead = 4;\nvar hoursBehind = 1;\nvar refreshPeriod = 60;\nvar counterid;\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Process the hours ahead event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursahead_preference', hours)\n .then(() => {\n hoursAhead = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours ahead'));\n });\n }\n};\n\n/**\n * Process the hours behind event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursbehind_preference', hours)\n .then(() => {\n hoursBehind = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours behind'));\n });\n }\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Initialise method for student search.\n *\n * @param {integer} context The current context id.\n */\nexport const init = (context) => {\n contextid = context;\n TableHandler.init(\n 0,\n contextid,\n 'local-assessfreq-student-search-table',\n 'local-assessfreq-student-search',\n 'get_student_search_table',\n 'local_assessfreq_student_search_table_rows_preference',\n 'local-assessfreq-quiz-student-table-search',\n 'local_assessfreq_student_search_table',\n 'local_assessfreq_set_table_preference'\n );\n\n // Add required initial event listeners.\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows');\n let tableSearchAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead');\n let tableSearchBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind');\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n tableSearchAheadElement.addEventListener('click', tableSearchAheadSet);\n tableSearchBehindElement.addEventListener('click', tableSearchBehindSet);\n refreshElement.addEventListener('click', refreshAction);\n\n $.when(\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursahead_preference')\n .then((response) => {\n hoursAhead = response.preferences[0].value ? response.preferences[0].value : 4;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n }),\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursbehind_preference')\n .then((response) => {\n hoursBehind = response.preferences[0].value ? response.preferences[0].value : 1;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n })\n ).done(function () {\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n OverrideModal.init(context, TableHandler.getTable, [hoursAhead, hoursBehind]);\n });\n};\n"],"names":["contextid","counterid","hoursAhead","hoursBehind","refreshPeriod","refreshCounter","reset","progressElement","document","getElementById","clearInterval","setAttribute","setInterval","progressWidthAria","getAttribute","progressStep","TableHandler","getTable","tableSearchAheadSet","event","preventDefault","target","tagName","toLowerCase","hours","dataset","metric","UserPreference","setUserPreference","then","fail","exception","Error","tableSearchBehindSet","refreshAction","element","closest","id","period","context","init","tableSearchInputElement","tableSearchResetElement","tableSearchRowsElement","tableSearchAheadElement","tableSearchBehindElement","refreshElement","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","when","getUserPreference","response","preferences","value","done"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"student_search.min.js","sources":["../src/student_search.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for student search display and processing.\n *\n * @module local_assessfreq/student_search\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Notification from 'core/notification';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar hoursAhead = 4;\nvar hoursBehind = 1;\nvar refreshPeriod = 60;\nvar counterid;\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Process the hours ahead event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursahead_preference', hours)\n .then(() => {\n hoursAhead = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours ahead'));\n });\n }\n};\n\n/**\n * Process the hours behind event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursbehind_preference', hours)\n .then(() => {\n hoursBehind = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours behind'));\n });\n }\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Initialise method for student search.\n *\n * @param {integer} context The current context id.\n */\nexport const init = (context) => {\n contextid = context;\n TableHandler.init(\n 0,\n contextid,\n 'local-assessfreq-student-search-table',\n 'local-assessfreq-student-search',\n 'get_student_search_table',\n 'local_assessfreq_student_search_table_rows_preference',\n 'local-assessfreq-quiz-student-table-search',\n 'local_assessfreq_student_search_table',\n 'local_assessfreq_set_table_preference'\n );\n\n // Add required initial event listeners.\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows');\n let tableSearchAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead');\n let tableSearchBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind');\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n tableSearchAheadElement.addEventListener('click', tableSearchAheadSet);\n tableSearchBehindElement.addEventListener('click', tableSearchBehindSet);\n refreshElement.addEventListener('click', refreshAction);\n\n $.when(\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursahead_preference')\n .then((response) => {\n hoursAhead = response.preferences[0].value ? response.preferences[0].value : 4;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n }),\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursbehind_preference')\n .then((response) => {\n hoursBehind = response.preferences[0].value ? response.preferences[0].value : 1;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n })\n ).done(function () {\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n OverrideModal.init(context, TableHandler.getTable, [hoursAhead, hoursBehind]);\n });\n};\n"],"names":["contextid","counterid","hoursAhead","hoursBehind","refreshPeriod","refreshCounter","reset","progressElement","document","getElementById","clearInterval","setAttribute","setInterval","progressWidthAria","getAttribute","progressStep","TableHandler","getTable","tableSearchAheadSet","event","preventDefault","target","tagName","toLowerCase","hours","dataset","metric","UserPreference","setUserPreference","then","fail","exception","Error","tableSearchBehindSet","refreshAction","element","closest","id","period","context","init","tableSearchInputElement","tableSearchResetElement","tableSearchRowsElement","tableSearchAheadElement","tableSearchBehindElement","refreshElement","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","when","getUserPreference","response","preferences","value","done"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/summary_participants.min.js b/amd/build/summary_participants.min.js deleted file mode 100644 index e03b1e59..00000000 --- a/amd/build/summary_participants.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Javascript for summary participants graph. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/summary_participants",["core/fragment","core/templates","core/str","core/notification"],(function(Fragment,Templates,Str,Notification){var Summary={chart:function(assessids,contextid){assessids.forEach((assessid=>{let chartElement=document.getElementById(assessid+"-summary-graph"),params={data:JSON.stringify({quiz:assessid,call:"participant_summary"})};Fragment.loadFragment("local_assessfreq","get_quiz_chart",contextid,params).done((response=>{let resObj=JSON.parse(response);if(1!=resObj.hasdata)Str.get_string("nodata","local_assessfreq").then((str=>{const noDatastr=document.createElement("h3");noDatastr.innerHTML=str,chartElement.innerHTML=noDatastr.outerHTML})).catch((()=>{Notification.exception(new Error("Failed to load string: nodata"))}));else{let legend={position:"left"},context={withtable:!1,chartdata:JSON.stringify(resObj.chart),aspect:!1,legend:JSON.stringify(legend)};Templates.render("local_assessfreq/chart",context).done(((html,js)=>{Templates.replaceNodeContents(chartElement,html,js)})).fail((()=>{Notification.exception(new Error("Failed to load chart template."))}))}})).fail((()=>{Notification.exception(new Error("Failed to load card."))}))}))}};return Summary})); - -//# sourceMappingURL=summary_participants.min.js.map \ No newline at end of file diff --git a/amd/build/summary_participants.min.js.map b/amd/build/summary_participants.min.js.map deleted file mode 100644 index 1897539d..00000000 --- a/amd/build/summary_participants.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"summary_participants.min.js","sources":["../src/summary_participants.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for summary participants graph.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['core/fragment', 'core/templates', 'core/str', 'core/notification'],\n function (Fragment, Templates, Str, Notification) {\n\n /**\n * Module level variables.\n */\n var Summary = {};\n\n Summary.chart = function (assessids, contextid) {\n assessids.forEach((assessid) => {\n let chartElement = document.getElementById(assessid + '-summary-graph');\n let params = {'data': JSON.stringify({'quiz' : assessid, 'call': 'participant_summary'})};\n\n Fragment.loadFragment('local_assessfreq', 'get_quiz_chart', contextid, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata == true) {\n let legend = {position: 'left'};\n let context = {\n 'withtable' : false,\n 'chartdata' : JSON.stringify(resObj.chart),\n 'aspect' : false,\n 'legend' : JSON.stringify(legend)\n };\n Templates.render('local_assessfreq/chart', context).done((html, js) => {\n // Load card body.\n Templates.replaceNodeContents(chartElement, html, js);\n }).fail(() => {\n Notification.exception(new Error('Failed to load chart template.'));\n return;\n });\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n chartElement.innerHTML = noDatastr.outerHTML;\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load card.'));\n return;\n });\n });\n };\n\n return Summary;\n }\n);\n"],"names":["define","Fragment","Templates","Str","Notification","Summary","assessids","contextid","forEach","assessid","chartElement","document","getElementById","params","JSON","stringify","loadFragment","done","response","resObj","parse","hasdata","get_string","then","str","noDatastr","createElement","innerHTML","outerHTML","catch","exception","Error","legend","position","context","chart","render","html","js","replaceNodeContents","fail"],"mappings":";;;;;;AAsBAA,+CACI,CAAC,gBAAiB,iBAAkB,WAAY,sBAChD,SAAUC,SAAUC,UAAWC,IAAKC,kBAK5BC,QAAU,CAEdA,MAAgB,SAAUC,UAAWC,WACjCD,UAAUE,SAASC,eACXC,aAAeC,SAASC,eAAeH,SAAW,kBAClDI,OAAS,MAASC,KAAKC,UAAU,MAAUN,cAAkB,yBAEjER,SAASe,aAAa,mBAAoB,iBAAkBT,UAAWM,QACtEI,MAAMC,eACCC,OAASL,KAAKM,MAAMF,aACF,GAAlBC,OAAOE,QAiBPlB,IAAImB,WAAW,SAAU,oBAAoBC,MAAMC,YACzCC,UAAYd,SAASe,cAAc,MACzCD,UAAUE,UAAYH,IACtBd,aAAaiB,UAAYF,UAAUG,aAEpCC,OAAM,KACLzB,aAAa0B,UAAU,IAAIC,MAAM,8CAtBjCC,OAAS,CAACC,SAAU,QACpBC,QAAU,YACI,YACApB,KAAKC,UAAUI,OAAOgB,eACzB,SACArB,KAAKC,UAAUiB,SAE9B9B,UAAUkC,OAAO,yBAA0BF,SAASjB,MAAK,CAACoB,KAAMC,MAE5DpC,UAAUqC,oBAAoB7B,aAAc2B,KAAMC,OACnDE,MAAK,KACJpC,aAAa0B,UAAU,IAAIC,MAAM,0CAc1CS,MAAK,KACJpC,aAAa0B,UAAU,IAAIC,MAAM,wCAMtC1B"} \ No newline at end of file diff --git a/amd/build/table_handler.min.js b/amd/build/table_handler.min.js index 6b506324..3ca3a6a0 100644 --- a/amd/build/table_handler.min.js +++ b/amd/build/table_handler.min.js @@ -3,8 +3,9 @@ define("local_assessfreq/table_handler",["exports","core/ajax","core/fragment"," * Table handler JS module. * * @module local_assessfreq/table_handler + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */let cardElement,contextId,elementId,fragmentValue,hoursFilter;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.tableSortButtonAction=_exports.tableSearchRowSet=_exports.tableSearchReset=_exports.tableSearch=_exports.init=_exports.getTable=void 0,_ajax=_interopRequireDefault(_ajax),_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates),Debouncer=_interopRequireWildcard(Debouncer),_override_modal=_interopRequireDefault(_override_modal),UserPreference=_interopRequireWildcard(UserPreference);let rowPreference,sortValue,searchElement,id,methodName,quizId=0,overridden=!1;const getTable=function(quiz){let hours=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,sortValueTable=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,page=arguments.length>3?arguments[3]:void 0;void 0!==page&&!0!==overridden||(page=0),overridden=!1;let search=document.getElementById(searchElement).value.trim(),tableElement=document.getElementById(elementId),spinner=tableElement.getElementsByClassName("overlay-icon-container")[0],tableBody=tableElement.getElementsByClassName("table-body")[0],values={search:search,page:page};if(quiz>0&&(quizId=quiz,values.quiz=quizId),hours&&(hoursFilter=hours,values.hoursahead=hoursFilter[0],values.hoursbehind=hoursFilter[1]),sortValueTable){sortValue=sortValueTable;let sortArray=sortValue.split("_"),sortOn=sortArray[0],direction=sortArray[1];values.sorton=sortOn,values.direction=direction}let params={data:JSON.stringify(values)};spinner.classList.remove("hide"),_fragment.default.loadFragment("local_assessfreq",fragmentValue,contextId,params).done(((response,js)=>{tableBody.innerHTML=response,js&&_templates.default.runTemplateJS(js),spinner.classList.add("hide"),tableEventListeners()})).fail((()=>{_notification.default.exception(new Error("Failed to update table."))}))};_exports.getTable=getTable;const debounceTable=Debouncer.debouncer((()=>{getTable(quizId,hoursFilter,sortValue)}),750),tableSort=event=>{event.preventDefault();let sortArray={};const linkUrl=new URL(event.target.closest("a").href),targetSortBy=linkUrl.searchParams.get("tsort");let targetSortOrder=linkUrl.searchParams.get("tdir");""===targetSortOrder&&(targetSortOrder="4"),sortArray[targetSortBy]=targetSortOrder,_ajax.default.call([{methodname:methodName,args:{tableid:id,preference:"sortby",values:JSON.stringify(sortArray)}}])[0].then((()=>{getTable(quizId,hoursFilter,sortValue)}))},tableHide=event=>{event.preventDefault();let hideArray={};const linkUrl=new URL(event.target.closest("a").href),links=document.getElementById(elementId).querySelectorAll("a");let targetAction,targetColumn,action,column;-1!==linkUrl.search.indexOf("thide")?(targetAction="hide",targetColumn=linkUrl.searchParams.get("thide")):(targetAction="show",targetColumn=linkUrl.searchParams.get("tshow"));for(let i=0;i{getTable(quizId,hoursFilter,sortValue)}))},tableReset=event=>{event.preventDefault(),_ajax.default.call([{methodname:methodName,args:{tableid:id,preference:"reset",values:JSON.stringify({})}}])[0].then((()=>{getTable(quizId,hoursFilter,sortValue)}))};_exports.tableSearch=event=>{if("Meta"===event.key||event.ctrlKey)return!1;(0===event.target.value.length||event.target.value.length>2)&&debounceTable()};_exports.tableSearchReset=()=>{let tableSearchInputElement=document.getElementById(searchElement);tableSearchInputElement.value="",tableSearchInputElement.focus(),getTable(quizId,hoursFilter,sortValue)};_exports.tableSearchRowSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let rows=event.target.dataset.metric;UserPreference.setUserPreference(rowPreference,rows).then((()=>{getTable(quizId,hoursFilter,sortValue)})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: rows"))}))}};const tableNav=event=>{event.preventDefault();const page=new URL(event.target.closest("a").href).searchParams.get("page");page&&getTable(quizId,hoursFilter,sortValue,page)};_exports.tableSortButtonAction=event=>{event.preventDefault();var element=event.target;if("a"===element.tagName.toLowerCase()&&element.dataset.sort!==sortValue){sortValue=element.dataset.sort;let links=element.parentNode.getElementsByTagName("a");for(let i=0;i{const tableElement=document.getElementById(elementId);let tableNavElement;if(cardElement){const tableCardElement=document.getElementById(cardElement),links=tableElement.querySelectorAll("a"),resetLink=tableElement.getElementsByClassName("resettable"),overrideLinks=tableElement.getElementsByClassName("action-icon override"),disabledLinks=tableElement.getElementsByClassName("action-icon disabled");tableNavElement=tableCardElement.querySelectorAll("nav");for(let i=0;i0&&resetLink[0].addEventListener("click",tableReset);for(let i=0;i{event.preventDefault()}))}else tableNavElement=tableElement.querySelectorAll("nav");tableNavElement.forEach((navElement=>{navElement.addEventListener("click",tableNav)}))},triggerOverrideModal=event=>{event.preventDefault();let userid=event.target.closest("a").id.substring(25);if(userid.includes("-")){let elements=userid.split("-");quizId=elements.pop(),userid=elements.pop()}_override_modal.default.displayModalForm(quizId,userid,hoursFilter)};_exports.init=function(quiz,context,tableCardElement,tableElementId,tableFragmentValue,tableRowPreference,tableSearchElement){let tableId=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null,tableMethodName=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null;quizId=quiz,contextId=context,cardElement=tableCardElement,elementId=tableElementId,fragmentValue=tableFragmentValue,rowPreference=tableRowPreference,searchElement=tableSearchElement,id=tableId,methodName=tableMethodName}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates),Debouncer=_interopRequireWildcard(Debouncer),_override_modal=_interopRequireDefault(_override_modal),UserPreference=_interopRequireWildcard(UserPreference);return _exports.default=class{constructor(activity,context,tableElementId,tableFragmentComponent,tableFragmentValue,tableRowPreference,tableSortPreference,tableSearchElement){let tableId=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null,tableMethodName=arguments.length>9&&void 0!==arguments[9]?arguments[9]:null;this.activityId=activity,this.contextId=context,this.elementId=tableElementId,this.fragmentComponent=tableFragmentComponent,this.fragmentValue=tableFragmentValue,this.rowPreference=tableRowPreference,this.sortPreference=tableSortPreference,this.searchElement=tableSearchElement,this.id=tableId,this.methodName=tableMethodName,this.overridden=!1}getTable=(()=>{var _this=this;return function(){let page=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;globalThis.reports++,_this.overridden=!1;let search=document.getElementById(_this.searchElement).value.trim(),tableElement=document.getElementById(_this.elementId),spinner=tableElement.getElementsByClassName("overlay-icon-container")[0],tableBody=tableElement.getElementsByClassName("table-body")[0],values={search:search,page:page};_this.activityId>0&&(values.activityid=_this.activityId);let params={data:JSON.stringify(values)};spinner.classList.remove("hide"),_fragment.default.loadFragment(_this.fragmentComponent,_this.fragmentValue,_this.contextId,params).done(((response,js)=>{tableBody.innerHTML=response,js&&_templates.default.runTemplateJS(js),spinner.classList.add("hide"),_this.tableEventListeners(),globalThis.reports--})).fail((()=>{globalThis.reports--,_notification.default.exception(new Error("Failed to update table."))}))}})();debounceTable=Debouncer.debouncer((()=>{this.getTable()}),750);tableSort=event=>{event.preventDefault();let sortArray={};const linkUrl=new URL(event.target.closest("a").href),targetSortBy=linkUrl.searchParams.get("tsort");let targetSortOrder=linkUrl.searchParams.get("tdir");""===targetSortOrder&&(targetSortOrder="4"),sortArray[targetSortBy]=targetSortOrder,_ajax.default.call([{methodname:this.methodName,args:{tableid:this.id,preference:"sortby",values:JSON.stringify(sortArray)}}])[0].then((()=>{this.getTable()}))};tableHide=event=>{event.preventDefault();let hideArray={};const linkUrl=new URL(event.target.closest("a").href),links=document.getElementById(this.elementId).querySelectorAll("a");let targetAction,targetColumn,action,column;-1!==linkUrl.search.indexOf("thide")?(targetAction="hide",targetColumn=linkUrl.searchParams.get("thide")):(targetAction="show",targetColumn=linkUrl.searchParams.get("tshow"));for(let i=0;i{this.getTable()}))};tableReset=event=>{event.preventDefault(),_ajax.default.call([{methodname:this.methodName,args:{tableid:this.id,preference:"reset",values:JSON.stringify({})}}])[0].then((()=>{this.getTable()}))};tableSearch=event=>"Meta"!==event.key&&!event.ctrlKey&&((0===event.target.value.length||event.target.value.length>2)&&this.debounceTable(),!0);tableSearchReset=()=>{let tableSearchInputElement=document.getElementById(this.searchElement);tableSearchInputElement.value="",tableSearchInputElement.focus(),this.getTable()};tableSearchRowSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let rows=event.target.dataset.metric;UserPreference.setUserPreference(this.rowPreference,rows).then((()=>{this.getTable()})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: rows"))}))}};tableNav=event=>{event.preventDefault();const page=new URL(event.target.closest("a").href).searchParams.get("page");page&&this.getTable(page)};tableSortButtonAction=event=>{event.preventDefault();var element=event.target;if("a"===element.tagName.toLowerCase()&&element.dataset.sort!==this.sortValue){this.sortValue=element.dataset.sort;let links=element.parentNode.getElementsByTagName("a");for(let i=0;i{const tableElement=document.getElementById(this.elementId),links=tableElement.querySelectorAll("a"),resetLink=tableElement.getElementsByClassName("resettable"),overrideLinks=tableElement.getElementsByClassName("action-icon override"),disabledLinks=tableElement.getElementsByClassName("action-icon disabled"),tableNavElement=tableElement.querySelectorAll("nav");for(let i=0;i0&&resetLink[0].addEventListener("click",this.tableReset);for(let i=0;i{event.preventDefault()}));tableNavElement.forEach((navElement=>{navElement.addEventListener("click",this.tableNav)}))};triggerOverrideModal=event=>{event.preventDefault();let userid=event.target.closest("a").id.substring(25);if(userid.includes("-")){let elements=userid.split("-");this.activityId=elements.pop(),userid=elements.pop()}_override_modal.default.displayModalForm(this.activityId,userid,this.hoursFilter)}},_exports.default})); //# sourceMappingURL=table_handler.min.js.map \ No newline at end of file diff --git a/amd/build/table_handler.min.js.map b/amd/build/table_handler.min.js.map index 8fb95138..7c82789d 100644 --- a/amd/build/table_handler.min.js.map +++ b/amd/build/table_handler.min.js.map @@ -1 +1 @@ -{"version":3,"file":"table_handler.min.js","sources":["../src/table_handler.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Table handler JS module.\n *\n * @module local_assessfreq/table_handler\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as Debouncer from 'local_assessfreq/debouncer';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Module level variables.\n */\nlet cardElement;\nlet contextId;\nlet elementId;\nlet fragmentValue;\nlet hoursFilter;\nlet quizId = 0;\nlet overridden = false;\nlet rowPreference;\nlet sortValue;\nlet searchElement;\n\n/**\n * Table id variable.\n *\n * @type {string}\n */\nlet id;\n\n/**\n * Table method name variable.\n *\n * @type {string}\n */\nlet methodName;\n\n/**\n * Display the table that contains all the students in the exam as well as their attempts.\n *\n * @param {int} quiz The Quiz Id.\n * @param {array|null} hours Array with hour ahead or behind preference.\n * @param {string|null} sortValueTable Sort preference.\n * @param {int|string|null} page Page number.\n */\nexport const getTable = (quiz, hours = null, sortValueTable = null, page) => {\n if (typeof page === \"undefined\" || overridden === true) {\n page = 0;\n }\n\n overridden = false;\n\n let search = document.getElementById(searchElement).value.trim();\n let tableElement = document.getElementById(elementId);\n let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0];\n let tableBody = tableElement.getElementsByClassName('table-body')[0];\n let values = {'search': search, 'page': page};\n\n // Add values to Object depending on dashboard type.\n if (quiz > 0) {\n quizId = quiz;\n values.quiz = quizId;\n }\n if (hours) {\n hoursFilter = hours;\n values.hoursahead = hoursFilter[0];\n values.hoursbehind = hoursFilter[1];\n }\n if (sortValueTable) {\n sortValue = sortValueTable;\n let sortArray = sortValue.split('_');\n let sortOn = sortArray[0];\n let direction = sortArray[1];\n values.sorton = sortOn;\n values.direction = direction;\n }\n\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n Fragment.loadFragment('local_assessfreq', fragmentValue, contextId, params)\n .done((response, js) => {\n tableBody.innerHTML = response;\n if (js) {\n Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML.\n }\n spinner.classList.add('hide');\n tableEventListeners(); // Re-add table event listeners.\n\n }).fail(() => {\n Notification.exception(new Error('Failed to update table.'));\n });\n};\n\n/**\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n */\nconst debounceTable = Debouncer.debouncer(() => {\n getTable(quizId, hoursFilter, sortValue);\n}, 750);\n\n/**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSort = (event) => {\n event.preventDefault();\n\n let sortArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const targetSortBy = linkUrl.searchParams.get('tsort');\n let targetSortOrder = linkUrl.searchParams.get('tdir');\n\n // We want to flip the clicked column.\n if (targetSortOrder === '') {\n targetSortOrder = \"4\";\n }\n\n sortArray[targetSortBy] = targetSortOrder;\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'sortby',\n values: JSON.stringify(sortArray)\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableHide = (event) => {\n event.preventDefault();\n\n let hideArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const tableElement = document.getElementById(elementId);\n const links = tableElement.querySelectorAll('a');\n let targetAction;\n let targetColumn;\n let action;\n let column;\n\n if (linkUrl.search.indexOf('thide') !== -1) {\n targetAction = 'hide';\n targetColumn = linkUrl.searchParams.get('thide');\n } else {\n targetAction = 'show';\n targetColumn = linkUrl.searchParams.get('tshow');\n }\n\n for (let i = 0; i < links.length; i++) {\n let hideLinkUrl = new URL(links[i].href);\n if (hideLinkUrl.search.indexOf('thide') !== -1) {\n action = 'hide';\n column = hideLinkUrl.searchParams.get('thide');\n } else {\n action = 'show';\n column = hideLinkUrl.searchParams.get('tshow');\n }\n\n if (action === 'show') {\n hideArray[column] = 1;\n }\n }\n\n hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column.\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'collapse',\n values: JSON.stringify(hideArray)\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the reset click event from the table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableReset = (event) => {\n event.preventDefault();\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'reset',\n values: JSON.stringify({})\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the search events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n *\n */\nexport const tableSearch = (event) => {\n if (event.key === 'Meta' || event.ctrlKey) {\n return false;\n }\n\n if (event.target.value.length === 0 || event.target.value.length > 2) {\n debounceTable();\n }\n};\n\n/**\n * Process the search reset click event from the student table.\n *\n */\nexport const tableSearchReset = () => {\n let tableSearchInputElement = document.getElementById(searchElement);\n tableSearchInputElement.value = '';\n tableSearchInputElement.focus();\n getTable(quizId, hoursFilter, sortValue);\n};\n\n/**\n * Process the row set event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nexport const tableSearchRowSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let rows = event.target.dataset.metric;\n UserPreference.setUserPreference(rowPreference, rows)\n .then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: rows'));\n });\n }\n};\n\n/**\n * Process the nav event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableNav = (event) => {\n event.preventDefault();\n\n const linkUrl = new URL(event.target.closest('a').href);\n const page = linkUrl.searchParams.get('page');\n\n if (page) {\n getTable(quizId, hoursFilter, sortValue, page);\n }\n};\n\n/**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {Event} event The triggered event for the element.\n */\nexport const tableSortButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== sortValue) {\n sortValue = element.dataset.sort;\n\n let links = element.parentNode.getElementsByTagName('a');\n for (let i = 0; i < links.length; i++) {\n links[i].classList.remove('active');\n }\n\n element.classList.add('active');\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference', sortValue);\n\n debounceTable(); // Call function to update table.\n }\n};\n\n/**\n * Re-add event listeners when the student table is updated.\n */\nconst tableEventListeners = () => {\n const tableElement = document.getElementById(elementId);\n let tableNavElement;\n if (cardElement) {\n const tableCardElement = document.getElementById(cardElement);\n const links = tableElement.querySelectorAll('a');\n const resetLink = tableElement.getElementsByClassName('resettable');\n const overrideLinks = tableElement.getElementsByClassName('action-icon override');\n const disabledLinks = tableElement.getElementsByClassName('action-icon disabled');\n tableNavElement = tableCardElement.querySelectorAll('nav'); // There are two nav paging elements per table.\n\n for (let i = 0; i < links.length; i++) {\n let linkUrl = new URL(links[i].href);\n if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) {\n links[i].addEventListener('click', tableHide);\n } else if (linkUrl.search.indexOf('tsort') !== -1) {\n links[i].addEventListener('click', tableSort);\n }\n }\n\n if (resetLink.length > 0) {\n resetLink[0].addEventListener('click', tableReset);\n }\n\n for (let i = 0; i < overrideLinks.length; i++) {\n overrideLinks[i].addEventListener('click', triggerOverrideModal);\n }\n\n for (let i = 0; i < disabledLinks.length; i++) {\n disabledLinks[i].addEventListener('click', (event) => {\n event.preventDefault();\n });\n }\n } else {\n tableNavElement = tableElement.querySelectorAll('nav');\n }\n\n tableNavElement.forEach((navElement) => {\n navElement.addEventListener('click', tableNav);\n });\n};\n\n/**\n * Trigger the override modal form. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerOverrideModal = (event) => {\n event.preventDefault();\n let userid = event.target.closest('a').id.substring(25);\n if (userid.includes('-')) {\n let elements = userid.split('-');\n quizId = elements.pop();\n userid = elements.pop();\n }\n\n OverrideModal.displayModalForm(quizId, userid, hoursFilter);\n};\n\n/**\n * Initialise method for table handler.\n *\n * @param {int} quiz The quiz id.\n * @param {int} context The context id.\n * @param {string} tableCardElement The table card element.\n * @param {string} tableElementId The table element id.\n * @param {string} tableFragmentValue The table fragment value.\n * @param {string} tableRowPreference The table row preference.\n * @param {string} tableSearchElement The table search element.\n * @param {string|null} tableId The table id.\n * @param {string|null} tableMethodName The table method name.\n */\nexport const init = (quiz,\n context,\n tableCardElement,\n tableElementId,\n tableFragmentValue,\n tableRowPreference,\n tableSearchElement,\n tableId = null,\n tableMethodName = null) => {\n quizId = quiz;\n contextId = context;\n cardElement = tableCardElement;\n elementId = tableElementId;\n fragmentValue = tableFragmentValue;\n rowPreference = tableRowPreference;\n searchElement = tableSearchElement;\n id = tableId;\n methodName = tableMethodName;\n };\n"],"names":["cardElement","contextId","elementId","fragmentValue","hoursFilter","rowPreference","sortValue","searchElement","id","methodName","quizId","overridden","getTable","quiz","hours","sortValueTable","page","search","document","getElementById","value","trim","tableElement","spinner","getElementsByClassName","tableBody","values","hoursahead","hoursbehind","sortArray","split","sortOn","direction","sorton","params","JSON","stringify","classList","remove","loadFragment","done","response","js","innerHTML","runTemplateJS","add","tableEventListeners","fail","exception","Error","debounceTable","Debouncer","debouncer","tableSort","event","preventDefault","linkUrl","URL","target","closest","href","targetSortBy","searchParams","get","targetSortOrder","call","methodname","args","tableid","preference","then","tableHide","hideArray","links","querySelectorAll","targetAction","targetColumn","action","column","indexOf","i","length","hideLinkUrl","tableReset","key","ctrlKey","tableSearchInputElement","focus","tagName","toLowerCase","rows","dataset","metric","UserPreference","setUserPreference","tableNav","element","sort","parentNode","getElementsByTagName","tableNavElement","tableCardElement","resetLink","overrideLinks","disabledLinks","addEventListener","triggerOverrideModal","forEach","navElement","userid","substring","includes","elements","pop","displayModalForm","context","tableElementId","tableFragmentValue","tableRowPreference","tableSearchElement","tableId","tableMethodName"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"table_handler.min.js","sources":["../src/table_handler.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Table handler JS module.\n *\n * @module local_assessfreq/table_handler\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as Debouncer from 'local_assessfreq/debouncer';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\nexport default class TableHandler {\n\n constructor(activity,\n context,\n tableElementId,\n tableFragmentComponent,\n tableFragmentValue,\n tableRowPreference,\n tableSortPreference,\n tableSearchElement,\n tableId = null,\n tableMethodName = null) {\n this.activityId = activity;\n this.contextId = context;\n this.elementId = tableElementId;\n this.fragmentComponent = tableFragmentComponent;\n this.fragmentValue = tableFragmentValue;\n this.rowPreference = tableRowPreference;\n this.sortPreference = tableSortPreference;\n this.searchElement = tableSearchElement;\n this.id = tableId;\n this.methodName = tableMethodName;\n this.overridden = false;\n }\n\n /**\n * Display the table that contains all the students in the exam as well as their attempts.\n *\n * @param {int|string|null} page Page number.\n */\n getTable = (page = 0) => {\n\n globalThis.reports++;\n\n this.overridden = false;\n\n let search = document.getElementById(this.searchElement).value.trim();\n let tableElement = document.getElementById(this.elementId);\n let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0];\n let tableBody = tableElement.getElementsByClassName('table-body')[0];\n let values = {'search': search, 'page': page};\n\n // Add values to Object depending on dashboard type.\n if (this.activityId > 0) {\n values.activityid = this.activityId;\n }\n\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n Fragment.loadFragment(this.fragmentComponent, this.fragmentValue, this.contextId, params)\n .done((response, js) => {\n tableBody.innerHTML = response;\n if (js) {\n Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML.\n }\n spinner.classList.add('hide');\n this.tableEventListeners(); // Re-add table event listeners.\n globalThis.reports--;\n })\n .fail(() => {\n globalThis.reports--;\n Notification.exception(new Error('Failed to update table.'));\n });\n };\n\n /**\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n */\n debounceTable = Debouncer.debouncer(() => {\n this.getTable();\n }, 750);\n\n /**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSort = (event) => {\n event.preventDefault();\n\n let sortArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const targetSortBy = linkUrl.searchParams.get('tsort');\n let targetSortOrder = linkUrl.searchParams.get('tdir');\n\n // We want to flip the clicked column.\n if (targetSortOrder === '') {\n targetSortOrder = \"4\";\n }\n\n sortArray[targetSortBy] = targetSortOrder;\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'sortby',\n values: JSON.stringify(sortArray)\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableHide = (event) => {\n event.preventDefault();\n\n let hideArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const tableElement = document.getElementById(this.elementId);\n const links = tableElement.querySelectorAll('a');\n let targetAction;\n let targetColumn;\n let action;\n let column;\n\n if (linkUrl.search.indexOf('thide') !== -1) {\n targetAction = 'hide';\n targetColumn = linkUrl.searchParams.get('thide');\n } else {\n targetAction = 'show';\n targetColumn = linkUrl.searchParams.get('tshow');\n }\n\n for (let i = 0; i < links.length; i++) {\n let hideLinkUrl = new URL(links[i].href);\n if (hideLinkUrl.search.indexOf('thide') !== -1) {\n action = 'hide';\n column = hideLinkUrl.searchParams.get('thide');\n } else {\n action = 'show';\n column = hideLinkUrl.searchParams.get('tshow');\n }\n\n if (action === 'show') {\n hideArray[column] = 1;\n }\n }\n\n hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column.\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'collapse',\n values: JSON.stringify(hideArray)\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the reset click event from the table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableReset = (event) => {\n event.preventDefault();\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'reset',\n values: JSON.stringify({})\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the search events from the student table.\n *\n * @param {Event} event\n * @return {Boolean}\n */\n tableSearch = (event) => {\n if (event.key === 'Meta' || event.ctrlKey) {\n return false;\n }\n\n if (event.target.value.length === 0 || event.target.value.length > 2) {\n this.debounceTable();\n }\n return true;\n };\n\n /**\n * Process the search reset click event from the student table.\n *\n */\n tableSearchReset = () => {\n let tableSearchInputElement = document.getElementById(this.searchElement);\n tableSearchInputElement.value = '';\n tableSearchInputElement.focus();\n this.getTable();\n };\n\n /**\n * Process the row set event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSearchRowSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let rows = event.target.dataset.metric;\n UserPreference.setUserPreference(this.rowPreference, rows)\n // eslint-disable-next-line promise/always-return\n .then(() => {\n this.getTable(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: rows'));\n });\n }\n };\n\n /**\n * Process the nav event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableNav = (event) => {\n event.preventDefault();\n\n const linkUrl = new URL(event.target.closest('a').href);\n const page = linkUrl.searchParams.get('page');\n\n if (page) {\n this.getTable(page);\n }\n };\n\n /**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSortButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== this.sortValue) {\n this.sortValue = element.dataset.sort;\n\n let links = element.parentNode.getElementsByTagName('a');\n for (let i = 0; i < links.length; i++) {\n links[i].classList.remove('active');\n }\n\n element.classList.add('active');\n\n // Save selection as a user preference.\n UserPreference.setUserPreference(this.sortPreference, this.sortValue);\n\n this.debounceTable(); // Call function to update table.\n }\n };\n\n /**\n * Re-add event listeners when the student table is updated.\n */\n tableEventListeners = () => {\n const tableElement = document.getElementById(this.elementId);\n const links = tableElement.querySelectorAll('a');\n const resetLink = tableElement.getElementsByClassName('resettable');\n const overrideLinks = tableElement.getElementsByClassName('action-icon override');\n const disabledLinks = tableElement.getElementsByClassName('action-icon disabled');\n const tableNavElement = tableElement.querySelectorAll('nav'); // There are two nav paging elements per table.\n\n for (let i = 0; i < links.length; i++) {\n let linkUrl = new URL(links[i].href);\n if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) {\n links[i].addEventListener('click', this.tableHide);\n } else if (linkUrl.search.indexOf('tsort') !== -1) {\n links[i].addEventListener('click', this.tableSort);\n }\n }\n\n if (resetLink.length > 0) {\n resetLink[0].addEventListener('click', this.tableReset);\n }\n\n for (let i = 0; i < overrideLinks.length; i++) {\n overrideLinks[i].addEventListener('click', this.triggerOverrideModal);\n }\n\n for (let i = 0; i < disabledLinks.length; i++) {\n disabledLinks[i].addEventListener('click', (event) => {\n event.preventDefault();\n });\n }\n\n tableNavElement.forEach((navElement) => {\n navElement.addEventListener('click', this.tableNav);\n });\n };\n\n /**\n * Trigger the override modal form. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\n triggerOverrideModal = (event) => {\n event.preventDefault();\n let userid = event.target.closest('a').id.substring(25);\n if (userid.includes('-')) {\n let elements = userid.split('-');\n this.activityId = elements.pop();\n userid = elements.pop();\n }\n\n OverrideModal.displayModalForm(this.activityId, userid, this.hoursFilter);\n };\n}\n"],"names":["constructor","activity","context","tableElementId","tableFragmentComponent","tableFragmentValue","tableRowPreference","tableSortPreference","tableSearchElement","tableId","tableMethodName","activityId","contextId","elementId","fragmentComponent","fragmentValue","rowPreference","sortPreference","searchElement","id","methodName","overridden","getTable","page","globalThis","reports","_this","search","document","getElementById","value","trim","tableElement","spinner","getElementsByClassName","tableBody","values","activityid","params","JSON","stringify","classList","remove","loadFragment","done","response","js","innerHTML","runTemplateJS","add","tableEventListeners","fail","exception","Error","debounceTable","Debouncer","debouncer","tableSort","event","preventDefault","sortArray","linkUrl","URL","target","closest","href","targetSortBy","searchParams","get","targetSortOrder","call","methodname","this","args","tableid","preference","then","tableHide","hideArray","links","querySelectorAll","targetAction","targetColumn","action","column","indexOf","i","length","hideLinkUrl","tableReset","tableSearch","key","ctrlKey","tableSearchReset","tableSearchInputElement","focus","tableSearchRowSet","tagName","toLowerCase","rows","dataset","metric","UserPreference","setUserPreference","tableNav","tableSortButtonAction","element","sort","sortValue","parentNode","getElementsByTagName","resetLink","overrideLinks","disabledLinks","tableNavElement","addEventListener","triggerOverrideModal","forEach","navElement","userid","substring","includes","elements","split","pop","displayModalForm","hoursFilter"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/user_preferences.min.js b/amd/build/user_preferences.min.js index ab61f9b9..2540e4f3 100644 --- a/amd/build/user_preferences.min.js +++ b/amd/build/user_preferences.min.js @@ -3,6 +3,7 @@ define("local_assessfreq/user_preferences",["exports","core/ajax","core/notifica * User preferences JS module. * * @module local_assessfreq/user_preferences + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setUserPreference=_exports.getUserPreference=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.setUserPreference=(type,value)=>{const request={methodname:"core_user_update_user_preferences",args:{preferences:[{type:type,value:value}]}};return _ajax.default.call([request])[0].fail((()=>{_notification.default.exception(new Error("Failed to update user preference"))}))};_exports.getUserPreference=name=>{const request={methodname:"core_user_get_user_preferences",args:{name:name}};return _ajax.default.call([request])[0]}})); diff --git a/amd/build/user_preferences.min.js.map b/amd/build/user_preferences.min.js.map index 4806adb6..d0054997 100644 --- a/amd/build/user_preferences.min.js.map +++ b/amd/build/user_preferences.min.js.map @@ -1 +1 @@ -{"version":3,"file":"user_preferences.min.js","sources":["../src/user_preferences.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * User preferences JS module.\n *\n * @module local_assessfreq/user_preferences\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Generic handler to persist user preferences.\n *\n * @method setUserPreference\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n * @return {promise} jQuery promise\n */\nexport const setUserPreference = (type, value) => {\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [{type: type, value: value}]\n }\n };\n\n return Ajax.call([request])[0]\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference'));\n });\n};\n\n/**\n * Generic handler to get user preference.\n *\n * @method getUserPreference\n * @param {string} name The name of the attribute you're getting.\n * @return {promise} jQuery promise\n */\nexport const getUserPreference = (name) => {\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n 'name': name\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["type","value","request","methodname","args","preferences","Ajax","call","fail","exception","Error","name"],"mappings":";;;;;;;6OAkCiC,CAACA,KAAMC,eAC9BC,QAAU,CACZC,WAAY,oCACZC,KAAM,CACFC,YAAa,CAAC,CAACL,KAAMA,KAAMC,MAAOA,iBAInCK,cAAKC,KAAK,CAACL,UAAU,GAC3BM,MAAK,2BACWC,UAAU,IAAIC,MAAM,oEAWPC,aACxBT,QAAU,CACZC,WAAY,iCACZC,KAAM,MACMO,cAITL,cAAKC,KAAK,CAACL,UAAU"} \ No newline at end of file +{"version":3,"file":"user_preferences.min.js","sources":["../src/user_preferences.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * User preferences JS module.\n *\n * @module local_assessfreq/user_preferences\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Generic handler to persist user preferences.\n *\n * @method setUserPreference\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n * @return {promise} jQuery promise\n */\nexport const setUserPreference = (type, value) => {\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [{type: type, value: value}]\n }\n };\n\n return Ajax.call([request])[0]\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference'));\n });\n};\n\n/**\n * Generic handler to get user preference.\n *\n * @method getUserPreference\n * @param {string} name The name of the attribute you're getting.\n * @return {promise} jQuery promise\n */\nexport const getUserPreference = (name) => {\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n 'name': name\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["type","value","request","methodname","args","preferences","Ajax","call","fail","exception","Error","name"],"mappings":";;;;;;;;6OAmCiC,CAACA,KAAMC,eAC9BC,QAAU,CACZC,WAAY,oCACZC,KAAM,CACFC,YAAa,CAAC,CAACL,KAAMA,KAAMC,MAAOA,iBAInCK,cAAKC,KAAK,CAACL,UAAU,GAC3BM,MAAK,2BACWC,UAAU,IAAIC,MAAM,oEAWPC,aACxBT,QAAU,CACZC,WAAY,iCACZC,KAAM,MACMO,cAITL,cAAKC,KAAK,CAACL,UAAU"} \ No newline at end of file diff --git a/amd/build/zoom_modal.min.js b/amd/build/zoom_modal.min.js deleted file mode 100644 index 952b3e04..00000000 --- a/amd/build/zoom_modal.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Javascript for report card display and processing. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("local_assessfreq/zoom_modal",["core/str","core/modal","core/fragment","core/ajax","core/templates","local_assessfreq/modal_large","core/notification"],(function(Str,Modal,Fragment,Ajax,Templates,ModalLarge,Notification){var contextid,modalObj,ZoomModal={};ZoomModal.zoomGraph=function(event,params,method){let title=event.target.parentElement.dataset.title;Fragment.loadFragment("local_assessfreq",method,contextid,params).done((response=>{let resObj=JSON.parse(response);if(1==resObj.hasdata){var context={withtable:!1,chartdata:JSON.stringify(resObj.chart),aspect:!1};return modalObj.setTitle(title),modalObj.setBody(Templates.render("local_assessfreq/chart",context)),void modalObj.show()}Str.get_string("nodata","local_assessfreq").then((str=>{const noDatastr=document.createElement("h3");noDatastr.innerHTML=str,modalObj.setTitle(title),modalObj.setBody(noDatastr.outerHTML),modalObj.show()})).catch((()=>{Notification.exception(new Error("Failed to load string: nodata"))}))})).fail((()=>{Notification.exception(new Error("Failed to load zoomed graph"))}))};return ZoomModal.init=function(context){contextid=context,new Promise((resolve=>{Str.get_string("loading","core").then((title=>{Modal.create({type:ModalLarge.TYPE,title:title,body:'

',large:!0}).then((modal=>{modalObj=modal,resolve()}))})).catch(Notification.exception)}))},ZoomModal})); - -//# sourceMappingURL=zoom_modal.min.js.map \ No newline at end of file diff --git a/amd/build/zoom_modal.min.js.map b/amd/build/zoom_modal.min.js.map deleted file mode 100644 index bf129277..00000000 --- a/amd/build/zoom_modal.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"zoom_modal.min.js","sources":["../src/zoom_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['core/str', 'core/modal', 'core/fragment', 'core/ajax', 'core/templates', 'local_assessfreq/modal_large',\n 'core/notification'],\n function (Str, Modal, Fragment, Ajax, Templates, ModalLarge, Notification) {\n\n /**\n * Module level variables.\n */\n var ZoomModal = {};\n var contextid;\n var modalObj;\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Provides zoom functionality for card graphs.\n *\n * @param {object} event The event object.\n * @param {object} params The parameters for the fragment call.\n * @param {string} method The method to call in the fragment.\n */\n ZoomModal.zoomGraph = function (event, params, method) {\n let title = event.target.parentElement.dataset.title;\n\n Fragment.loadFragment('local_assessfreq', method, contextid, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata == true) {\n var context = { 'withtable' : false, 'chartdata' : JSON.stringify(resObj.chart), aspect: false};\n modalObj.setTitle(title);\n modalObj.setBody(Templates.render('local_assessfreq/chart', context));\n modalObj.show();\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n modalObj.setTitle(title);\n modalObj.setBody(noDatastr.outerHTML);\n modalObj.show();\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load zoomed graph'));\n return;\n });\n\n };\n\n /**\n * Create the modal window for graph zooming.\n *\n * @private\n */\n const createModal = function () {\n return new Promise((resolve) => {\n Str.get_string('loading', 'core').then((title) => {\n // Create the Modal.\n Modal.create({\n type: ModalLarge.TYPE,\n title: title,\n body: spinner,\n large: true\n })\n .then((modal) => {\n modalObj = modal;\n resolve();\n });\n }).catch(Notification.exception);\n });\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n *\n * @param {int} context The context id for the dashboard.\n */\n ZoomModal.init = function (context) {\n contextid = context;\n createModal();\n };\n\n return ZoomModal;\n }\n);\n"],"names":["define","Str","Modal","Fragment","Ajax","Templates","ModalLarge","Notification","contextid","modalObj","ZoomModal","zoomGraph","event","params","method","title","target","parentElement","dataset","loadFragment","done","response","resObj","JSON","parse","hasdata","context","stringify","chart","aspect","setTitle","setBody","render","show","get_string","then","str","noDatastr","document","createElement","innerHTML","outerHTML","catch","exception","Error","fail","init","Promise","resolve","create","type","TYPE","body","large","modal"],"mappings":";;;;;;AAsBAA,qCACI,CAAC,WAAY,aAAc,gBAAiB,YAAa,iBAAkB,+BAC3E,sBACA,SAAUC,IAAKC,MAAOC,SAAUC,KAAMC,UAAWC,WAAYC,kBAMrDC,UACAC,SAFAC,UAAY,GAchBA,UAAUC,UAAY,SAAUC,MAAOC,OAAQC,YACvCC,MAAQH,MAAMI,OAAOC,cAAcC,QAAQH,MAE/CZ,SAASgB,aAAa,mBAAoBL,OAAQN,UAAWK,QAC5DO,MAAMC,eACCC,OAASC,KAAKC,MAAMH,aACF,GAAlBC,OAAOG,QAAiB,KACpBC,QAAU,YAAgB,YAAqBH,KAAKI,UAAUL,OAAOM,OAAQC,QAAQ,UACzFpB,SAASqB,SAASf,OAClBN,SAASsB,QAAQ1B,UAAU2B,OAAO,yBAA0BN,eAC5DjB,SAASwB,OAGThC,IAAIiC,WAAW,SAAU,oBAAoBC,MAAMC,YACzCC,UAAYC,SAASC,cAAc,MACzCF,UAAUG,UAAYJ,IACtB3B,SAASqB,SAASf,OAClBN,SAASsB,QAAQM,UAAUI,WAC3BhC,SAASwB,UAEVS,OAAM,KACLnC,aAAaoC,UAAU,IAAIC,MAAM,wCAG1CC,MAAK,KACJtC,aAAaoC,UAAU,IAAIC,MAAM,2CAkCzClC,UAAUoC,KAAO,SAAUpB,SACvBlB,UAAYkB,QAvBL,IAAIqB,SAASC,UAChB/C,IAAIiC,WAAW,UAAW,QAAQC,MAAMpB,QAEpCb,MAAM+C,OAAO,CACTC,KAAM5C,WAAW6C,KACjBpC,MAAOA,MACPqC,KAtDA,sFAuDAC,OAAO,IAEVlB,MAAMmB,QACH7C,SAAW6C,MACXN,gBAELN,MAAMnC,aAAaoC,eAcvBjC"} \ No newline at end of file diff --git a/amd/src/calendar.js b/amd/src/calendar.js deleted file mode 100644 index 726a0259..00000000 --- a/amd/src/calendar.js +++ /dev/null @@ -1,526 +0,0 @@ -// 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 . - -/** - * Javascript for heatmap calendar generation and display. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define(['core/str', 'core/notification', 'core/ajax'], function (Str, Notification, Ajax) { - - /** - * Module level variables. - */ - var Calendar = {}; - var eventArray = []; - const stringArr = [ - {key: 'sun', component: 'calendar'}, - {key: 'mon', component: 'calendar'}, - {key: 'tue', component: 'calendar'}, - {key: 'wed', component: 'calendar'}, - {key: 'thu', component: 'calendar'}, - {key: 'fri', component: 'calendar'}, - {key: 'sat', component: 'calendar'}, - {key: 'jan', component: 'local_assessfreq'}, - {key: 'feb', component: 'local_assessfreq'}, - {key: 'mar', component: 'local_assessfreq'}, - {key: 'apr', component: 'local_assessfreq'}, - {key: 'may', component: 'local_assessfreq'}, - {key: 'jun', component: 'local_assessfreq'}, - {key: 'jul', component: 'local_assessfreq'}, - {key: 'aug', component: 'local_assessfreq'}, - {key: 'sep', component: 'local_assessfreq'}, - {key: 'oct', component: 'local_assessfreq'}, - {key: 'nov', component: 'local_assessfreq'}, - {key: 'dec', component: 'local_assessfreq'}, - ]; - var stringResult; - var heatRangeMax; - var heatRangeMin; - var colorArray; - var processModules; - var heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0}; - - /** - * Pick a contrasting text color based on the background color. - * - * @param {String} hexcolor A hexcolor value. - * @return {String} The contrasting color (black or white). - */ - const getContrast = function (hexcolor) { - - if (typeof (hexcolor) === "undefined") { - return '#000000'; - } - - // If a leading # is provided, remove it. - if (hexcolor.slice(0, 1) === '#') { - hexcolor = hexcolor.slice(1); - } - - // Convert to RGB value. - var r = parseInt(hexcolor.substr(0,2),16); - var g = parseInt(hexcolor.substr(2,2),16); - var b = parseInt(hexcolor.substr(4,2),16); - - // Get YIQ ratio. - var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; - - // Check contrast. - return (yiq >= 128) ? '#000000' : '#FFFFFF'; - }; - - /** - * Check how many days in a month code. - * from https://dzone.com/articles/determining-number-days-month. - * - * @method daysInMonth - * @param {Number} month The month to get the number of days for. - * @param {Number} year The year to get the number of days for. - */ - const daysInMonth = function (month, year) { - return 32 - new Date(year, month, 32).getDate(); - }; - - /** - * Get the heat colors to use in the heat map via Ajax. - * - * @method getHeatColors - */ - const getHeatColors = function () { - return new Promise((resolve, reject) => { - Ajax.call([{ - methodname: 'local_assessfreq_get_heat_colors', - args: {}, - }], true, false)[0].done(function (response) { - colorArray = JSON.parse(response); - resolve(colorArray); - }).fail(function () { - reject(new Error('Failed to get heat colors')); - }); - }); - }; - - /** - * Get the event names that we are processing. - * - * @method getProcessEvents - */ - const getProcessModules = function () { - return new Promise((resolve, reject) => { - Ajax.call([{ - methodname: 'local_assessfreq_get_process_modules', - args: {}, - }], true, false)[0].done(function (response) { - processModules = JSON.parse(response); - resolve(processModules); - }).fail(function () { - reject(new Error('Failed to get process events')); - }); - }); - }; - - /** - * Calculate the min and max values to use in the heatmap. - * - * @method daysInMonth - * @param {Object} eventArray All the event count for the heatmap. - * @param {Object} dateObj Date details. - */ - const calcHeatRange = function (eventArray, dateObj) { - return new Promise((resolve) => { - - // Resolve early if there are no events. - if (typeof (eventArray) === "undefined") { - heatRangeMax = 0; - heatRangeMin = 0; - - resolve(eventArray); - } - // If scheduled tasks have not run yet we may not have any data. - let eventArrayLength = Object.keys(eventArray).length; - if ((eventArrayLength > 0) && (eventArray[dateObj.year] !== "undefined")) { - let eventcount = new Array; - let year = eventArray[dateObj.year]; - - // Iterate through all the event counts. - // This code looks nasty but there is only 366 days in a year. - for (let i = 0; i < 12; i++) { - if (typeof year[i] !== "undefined") { - let month = year[i]; - for (let j = 0; j < 32; j++) { - if (typeof month[j] !== "undefined") { - eventcount.push(month[j].number); - } - } - } - } - - // Get min and max values to calculate heat spread. - heatRangeMax = Math.max(...eventcount); - heatRangeMin = Math.min(...eventcount); - } else { - heatRangeMax = 0; - heatRangeMin = 0; - } - - resolve(eventArray); - }); - }; - - /** - * Translate assessment frequency to a heat value. - * - * @method getHeat - * @param {Number} eventCount The count to get the heat value. - * @return {Number} heat The heat value. - */ - const getHeat = function (eventCount) { - let scaleMin = 1; - - if (eventCount == heatRangeMin) { - return scaleMin; - } - - const scaleRange = 5; // 0 - 5 steps. - const localRange = heatRangeMax - heatRangeMin; - const localPercent = (eventCount - heatRangeMin) / localRange; - let heat = Math.round((localPercent * scaleRange) + 1); - - // Clamp values. - if (heat < 1) { - heat = 1; - } - - if (heat > 6) { - heat = 6; - } - - return heat; - }; - - /** - * Get the events to display in the calendar via ajax call. - * - * @method getEvents - * - * @param {Object} args The arguments to pass to the ajax call. - * @param {Number} args.year The year to get the events for. - * @param {String} args.metric The metric to get the events for. - * @param {Array} args.modules The modules to get the events for. - * - * @return {Promise} - */ - const getEvents = function ({year, metric, modules}) { - return new Promise((resolve, reject) => { - let args = { - year: year, - metric: metric, - modules: modules - }; - let jsonArgs = JSON.stringify(args); - - // Get the events to use in the mapping. - Ajax.call([{ - methodname: 'local_assessfreq_get_frequency', - args: { - jsondata: jsonArgs - }, - }])[0].done((response) => { - eventArray = JSON.parse(response); - resolve(eventArray); - }).fail(() => { - reject(new Error('Failed to get events')); - }); - }); - }; - - /** - * Get the events for a particular month and year. - * - * @param {Number} year The year to get the number of days for. - * @param {Number} month The month to get the number of days for. - * @return {Array} monthevents The events for the supplied month. - */ - const getMonthEvents = function (year, month) { - let monthevents; - - if ((typeof eventArray[year] !== "undefined") && (typeof eventArray[year][month] !== "undefined")) { - monthevents = eventArray[year][month]; - } - - return monthevents; - }; - - /** - * Create the table structure for the calendar months. - * - * @param {Object} args The arguments to pass to the ajax call. - * @param {Number} args.year The year to get the events for. - * @param {Number} args.startMonth The month to start the calendar - * @param {Number} args.endMonth The month to end the calendar - * - * @return {Promise} - */ - const createTables = function ({year, startMonth, endMonth}) { - return new Promise((resolve, reject) => { - let calendarContainer = document.createElement('div'); - let month = startMonth; - - // Itterate through and build are tables. - for (let i = startMonth; i <= endMonth; i++) { - // Setup some elements. - let container = document.createElement('div'); - container.classList.add('local-assessfreq-month'); - let table = document.createElement('table'); - table.classList.add('table-striped'); - let thead = document.createElement('thead'); - let tbody = document.createElement('tbody'); - tbody.id = 'calendar-body-' + i; - let monthRow = document.createElement('tr'); - let dayrow = document.createElement('tr'); - let monthHeader = document.createElement('th'); - monthHeader.colSpan = 7; - monthHeader.innerHTML = stringResult[(7 + month)]; - - for (let j = 0; j < 7; j++) { - let dayHeader = document.createElement('th'); - dayHeader.innerHTML = stringResult[j]; - dayrow.appendChild(dayHeader); - } - - // Construct the table. - monthRow.appendChild(monthHeader); - - thead.appendChild(monthRow); - thead.appendChild(dayrow); - - table.appendChild(thead); - table.appendChild(tbody); - - container.appendChild(table); - - // Add to parent. - calendarContainer.appendChild(container); - - // Increment variables. - month++; - } - - if ((typeof year === 'undefined') || (typeof startMonth === 'undefined') || (typeof endMonth === 'undefined')) { - reject(Error('Failed to create calendar tables.')); - } else { - const resultObj = { - calendarContainer : calendarContainer, - year : year, - startMonth : startMonth - }; - resolve(resultObj); - } - }); - }; - - /** - * Generate the tooltip HTML. - * - * @param {Object} dayArray The details of the events for that day/ - * @return {String} tipHTML The HTML for the tooltip. - */ - const getTooltip = function (dayArray) { - let tipHTML = ''; - - for (let [key, value] of Object.entries(dayArray)) { - tipHTML += '' + processModules[key] + ': ' + value + '
'; - } - - return tipHTML; - }; - - /** - * Generate calendar markup for the month. - * - * @param {Object} table The base table to populate. - * @param {Number} year The year to generate calendar for. - * @param {Number} month The monthe to generate calendar for. - */ - const populateCalendarDays = function (table, year, month) { - let firstDay = (new Date(year, month)).getDay(); // Get the starting day of the month. - let monthEvents = getMonthEvents(year, (month + 1)); // We add one due to month diferences between PHP and JS. - let date = 1; // Creating all cells. - - for (let i = 0; i < 6; i++) { - let row = document.createElement("tr"); // Creates a table row. - - // Creating individual cells, filing them up with data. - for (let j = 0; j < 7; j++) { - if (i === 0 && j < firstDay) { - var cell = document.createElement("td"); - var cellText = document.createTextNode(""); - cell.dataset.event = 'false'; - } else if (date > daysInMonth(month, year)) { // Break if we have generated all the days for this month. - break; - } else { - cell = document.createElement("td"); - cellText = document.createTextNode(date); - if ((typeof monthEvents !== "undefined") && (monthEvents.hasOwnProperty(date))) { - let heat = getHeat(monthEvents[date]['number']); - - if (heatRangeScale[heat] == 0 || heatRangeScale[heat] > monthEvents[date]['number']) { - heatRangeScale[heat] = monthEvents[date]['number']; - } - - cell.style.backgroundColor = colorArray[heat]; - cell.style.color = getContrast(colorArray[heat]); - - // Add tooltip to cell. - cell.dataset.toggle = 'tooltip'; - cell.dataset.html = 'true'; - cell.dataset.event = 'true'; - cell.dataset.date = year + '-' + (month + 1) + '-' + date; - cell.title = getTooltip(monthEvents[date]); - cell.style.cursor = "pointer"; - } - date++; - } - - cell.appendChild(cellText); - row.appendChild(cell); - } - table.appendChild(row); // Appending each row into calendar body. - } - }; - - /** - * Controls the population of the calendar in to the base tables. - * - * @param {Object} args The arguments to pass to the ajax call. - * @param {Object} args.calendarContainer The container to populate the calendar into. - * @param {Number} args.year The year to get the events for. - * @param {Number} args.startMonth The month to start the calendar - * - * @return {Promise} - */ - const populateCalendar = function ({calendarContainer, year, startMonth}) { - return new Promise((resolve, reject) => { - // Get the table boodies. - let tables = calendarContainer.getElementsByTagName("tbody"); - let month = startMonth; - - // For each table body populate with calendar. - for (var i = 0; i < tables.length; i++) { - let table = tables[i]; - populateCalendarDays(table, year, month); - month++; - } - - if (typeof calendarContainer === 'undefined') { - reject(Error('Failed to populate calendar tables.')); - } else { - resolve(calendarContainer); - } - }); - }; - - /** - * Create the heatmap scale for the calendar. - * - * @method createHeatScale - */ - Calendar.createHeatScale = function () { - return new Promise((resolve) => { - let table = document.createElement('table'); - let tbody = document.createElement('tbody'); - let trow = document.createElement('tr'); - - for (var i = 1; i < 7; i++) { - if (heatRangeScale[i] !== 0) { - let cell = document.createElement('td'); - let cellText = document.createTextNode(heatRangeScale[i] + '+'); - - cell.appendChild(cellText); - cell.style.backgroundColor = colorArray[i]; - cell.style.color = getContrast(colorArray[i]); - - trow.appendChild(cell); - } - } - - tbody.appendChild(trow); - table.appendChild(tbody); - - // Reset heat range scale. - heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0}; - - resolve(table); - }); - }; - - /** - * Initialise method for report calendar heatmap creation. - * - * @param {Number} year The year to generate the heatmap for. - * @param {Number} startMonth The month to start with for the heatmap calendar. - * @param {Number} endMonth The month to end with for the heatmap calendar. - * @param {String} metric The type of metric to display, 'students' or 'aseess'. - * @param {Array} modules The modules to display in the heatamp. - * @return {Promise} - */ - Calendar.generate = function (year, startMonth, endMonth, metric, modules) { - return new Promise((resolve, reject) => { - const dateObj = { - year : year, - startMonth : startMonth, - endMonth : endMonth - }; - - const eventObj = { - year : year, - metric : metric, - modules : modules - }; - - Str.get_strings(stringArr).catch(() => { // Get required strings. - Notification.exception(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - stringResult = stringReturn; - return eventObj; - }) - .then(getEvents) - .then((eventArray) => { - calcHeatRange(eventArray, dateObj); - }) - .then(getHeatColors) - .then(getProcessModules) - .then(() => { - return dateObj; - }) - .then(createTables) // Create tables for calendar. - .then(populateCalendar) - .then((calendarHTML) => { // Return the result of the generate function. - if (typeof calendarHTML !== 'undefined') { - resolve(calendarHTML); - } else { - reject(Error('Could not generate calendar')); - } - }); - }); - - }; - - return Calendar; -}); diff --git a/amd/src/chart_data.js b/amd/src/chart_data.js deleted file mode 100644 index 3587f213..00000000 --- a/amd/src/chart_data.js +++ /dev/null @@ -1,116 +0,0 @@ -// 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 . - -/** - * Chart data JS module. - * - * @module local_assessfreq/char_data - * @copyright 2020 Guillermo Gomez - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Fragment from 'core/fragment'; -import Notification from 'core/notification'; -import * as Str from 'core/str'; -import Templates from 'core/templates'; - -/** - * Module level variables. - */ -let cards; -let contextId; -let fragment; -let template; - -/** - * For each of the cards on the dashboard get their corresponding chart data. - * Data is based on the year variable from the corresponding dropdown. - * Chart data is loaded via ajax. - * - * @param {int|null} quizId The quiz Id. - * @param {array|null} hoursFilter Array with hour ahead or behind preference. - * @param {int|null} yearSelect Year selected. - */ -export const getCardCharts = (quizId, hoursFilter, yearSelect) => { - cards.forEach((cardData) => { - let cardElement = document.getElementById(cardData.cardId); - let spinner = cardElement.getElementsByClassName('overlay-icon-container')[0]; - let chartBody = cardElement.getElementsByClassName('chart-body')[0]; - let values = {'call': cardData.call}; - // Add values to Object depending on dashboard type. - if (hoursFilter) { - values.hoursahead = hoursFilter[0]; - values.hoursbehind = hoursFilter[1]; - } - if (quizId) { - values.quiz = quizId; - } - if (yearSelect) { - values.year = yearSelect; - } - let params = {'data': JSON.stringify(values)}; - - spinner.classList.remove('hide'); // Show sinner if not already shown. - Fragment.loadFragment('local_assessfreq', fragment, contextId, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata === true) { - let context = { - 'withtable': true, 'chartdata': JSON.stringify(resObj.chart) - }; - if (typeof cardData.aspect !== 'undefined') { - context.aspect = cardData.aspect; - } - Templates.render(template, context).done((html, js) => { - spinner.classList.add('hide'); // Hide spinner if not already hidden. - // Load card body. - Templates.replaceNodeContents(chartBody, html, js); - }).fail(() => { - Notification.exception(new Error('Failed to load chart template.')); - return; - }); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - chartBody.innerHTML = noDatastr.outerHTML; - spinner.classList.add('hide'); // Hide spinner if not already hidden. - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load card.')); - return; - }); - }); -}; - -/** - * Initialise method for table handler. - * - * @param {array} cardsArray Cards array. - * @param {int} contextIdChart The context id. - * @param {string} fragmentChart Fragment name. - * @param {string} templateChart Template name. - */ -export const init = (cardsArray, contextIdChart, fragmentChart, templateChart) => { - cards = cardsArray; - contextId = contextIdChart; - fragment = fragmentChart; - template = templateChart; -}; diff --git a/amd/src/chart_output_chartjs.js b/amd/src/chart_output_chartjs.js deleted file mode 100644 index 5f0f1439..00000000 --- a/amd/src/chart_output_chartjs.js +++ /dev/null @@ -1,118 +0,0 @@ -// 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 . - -/** - * Chart output for chart.js with custom override for aspect config. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define(['core/chart_output_chartjs'], function (Output) { - - /** - * Module level variables. - */ - var ChartOutput = {}; - var aspectRatio = false; - var rtLegendoptions = false; - - /** - * Overrride the config. - * - * @protected - * @return {Object} The axis config. - */ - Output.prototype._makeConfig = function () { - var config = { - type: this._getChartType(), - data: { - labels: this._cleanData(this._chart.getLabels()), - datasets: this._makeDatasetsConfig() - }, - options: { - title: { - display: this._chart.getTitle() !== null, - text: this._cleanData(this._chart.getTitle()) - } - } - }; - var legendOptions = this._chart.getLegendOptions(); - if (legendOptions) { - config.options.legend = legendOptions; - } - - // Override legend options with those provided at run time. - if (rtLegendoptions) { - config.options.legend = rtLegendoptions; - } - - this._chart.getXAxes().forEach(function (axis, i) { - var axisLabels = axis.getLabels(); - - config.options.scales = config.options.scales || {}; - config.options.scales.xAxes = config.options.scales.xAxes || []; - config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i); - - if (axisLabels !== null) { - config.options.scales.xAxes[i].ticks.callback = function (value, index) { - return axisLabels[index] || ''; - }; - } - config.options.scales.xAxes[i].stacked = this._isStacked(); - }.bind(this)); - - this._chart.getYAxes().forEach(function (axis, i) { - var axisLabels = axis.getLabels(); - - config.options.scales = config.options.scales || {}; - config.options.scales.yAxes = config.options.scales.yAxes || []; - config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i); - - if (axisLabels !== null) { - config.options.scales.yAxes[i].ticks.callback = function (value) { - return axisLabels[parseInt(value, 10)] || ''; - }; - } - config.options.scales.yAxes[i].stacked = this._isStacked(); - }.bind(this)); - - config.options.tooltips = { - callbacks: { - label: this._makeTooltip.bind(this) - } - }; - - config.options.maintainAspectRatio = aspectRatio; - - return config; - }; - - /** - * Get the aspect ratio setting and initialise the chart. - * - * @param {string} chartImage The image to replace. - * @param {object} ChartInst The chart instance. - * @param {boolean} aspect The aspect ratio. - * @param {object} legend The legend options. - */ - ChartOutput.init = function (chartImage, ChartInst, aspect, legend) { - aspectRatio = aspect; - rtLegendoptions = legend; - new Output(chartImage, ChartInst); - }; - - return ChartOutput; - -}); diff --git a/amd/src/course_selector.js b/amd/src/course_selector.js index 472cc8e5..c9ceafad 100644 --- a/amd/src/course_selector.js +++ b/amd/src/course_selector.js @@ -28,7 +28,7 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { /** * Module level variables. */ - var CourseSelector = {}; + let CourseSelector = {}; /** * Source of data for Ajax element. @@ -36,9 +36,8 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { * @param {String} selector The selector of the auto complete element. * @param {String} query The query string. * @param {Function} callback A callback function receiving an array of results. - * @return {Void} - */ - CourseSelector.transport = function (selector, query, callback) { + */ + CourseSelector.transport = function(selector, query, callback) { Ajax.call([{ methodname: 'local_assessfreq_get_courses', args: { @@ -46,9 +45,8 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { }, }])[0].then((response) => { let courseArray = JSON.parse(response); + // eslint-disable-next-line promise/no-callback-in-promise callback(courseArray); - }).fail(() => { - Notification.exception(new Error('Failed to get events')); }); }; diff --git a/amd/src/dashboard.js b/amd/src/dashboard.js new file mode 100644 index 00000000..212abf00 --- /dev/null +++ b/amd/src/dashboard.js @@ -0,0 +1,104 @@ +// 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 . + +/** + * Chart data JS module. + * + * @module local_assessfreq/dashboard + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +globalThis.reports = 0; + +export const init = () => { + + // Create the course search filter. + require(['core/form-autocomplete', 'core/str'], function(Autocomplete, Str) { + Str.get_string('courseselect', 'local_assessfreq').then((loading) => { + Autocomplete.enhance( + '#local-assessfreq-course-filter', + false, + 'local_assessfreq/course_selector', + loading, + false, + true, + ); + const course_filter = document.getElementById("local-assessfreq-course-filter"); + course_filter.addEventListener('change', event => { + let courseid = event.target.value; + let url = new URL(window.location); + url.searchParams.set('courseid', courseid); + window.location = url; + }); + }); + }); + + // Load the tab functionality. + tabs(); + + // Load the loading page whilst we wait for the reports to complete. + window.setTimeout(loading, 2000); +}; + +const loading = () => { + + let loaderwrapper = document.getElementById('loader-wrapper'); + let index = document.getElementById('local-assessfreq-index'); + + // No reports loading, then show the index page. + if (globalThis.reports === 0) { + loaderwrapper.style.display = 'none'; + index.style.display = 'block'; + } else { + window.setTimeout(loading, 1000); + } +}; + +const tabs = () => { + + const tabcontent = document.getElementsByClassName("tablinks"); + + tabcontent.forEach(el => el.addEventListener('click', event => { + let target = event.target.dataset.target; + + let tabcontent = document.getElementsByClassName("tabcontent"); + for (let i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Get all elements with class="tablinks" and remove the class "active" + let tablinks = document.getElementsByClassName("tablinks"); + for (let i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the current tab, and add an "active" class to the button that opened the tab + document.getElementById(target).style.display = "block"; + event.currentTarget.className += " active"; + })); + + const currentUrl = document.URL; + const urlParts = currentUrl.split('#'); + + const anchor = (urlParts.length > 1) ? urlParts[1] : null; + // First tab should be open by default unless we have an anchor. + if (!anchor || document.querySelector('[data-target="tab-' + anchor + '"]') === null) { + document.querySelector('[data-target="tab-heatmap"]').click(); + } else { + document.querySelector('[data-target="tab-' + anchor + '"]').click(); + } +}; diff --git a/amd/src/dashboard_assessment.js b/amd/src/dashboard_assessment.js deleted file mode 100644 index 144087a0..00000000 --- a/amd/src/dashboard_assessment.js +++ /dev/null @@ -1,371 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_assessment - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Notification from 'core/notification'; -import Calendar from 'local_assessfreq/calendar'; -import * as ChartData from 'local_assessfreq/chart_data'; -import Dayview from 'local_assessfreq/dayview'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import ZoomModal from 'local_assessfreq/zoom_modal'; - -/** - * Module level variables. - */ -var contextid; -var yearselect; -var yearselectheatmap; -var metricselectheatmap; -var timeout; -var modulesJson = ''; -var heatmapOptionsJson = ''; - -const cards = [ - {cardId: 'local-assessfreq-assess-due-month', call: 'assess_by_month'}, - {cardId: 'local-assessfreq-assess-by-activity', call: 'assess_by_activity'}, - {cardId: 'local-assessfreq-assess-due-month-student', call: 'assess_by_month_student'} -]; - -/** - * Get and process the selected year from the dropdown, - * and update the corresponding user perference. - * - * @param {event} event The triggered event for the element. - */ -const yearButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselect) { // Only act on certain elements. - yearselect = element.dataset.year; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_overview_year_preference', yearselect); - - // Update card data based on selected year. - var yeartitle = document.getElementById('local-assessfreq-report-overview') - .getElementsByClassName('local-assessfreq-year')[0]; - yeartitle.innerHTML = yearselect; - - ChartData.getCardCharts(0, null, yearselect); // Process loading for the assessment cards. - } -}; - -/** - * Quick and dirty debounce method for the heatmap settings menu. - * This stops the ajax method that updates the heatmap from being updated - * while the user is still checking options. - * - */ -const updateHeatmapDebounce = () => { - clearTimeout(timeout); - timeout = setTimeout(updateHeatmap(), 750); -}; - -/** - * Display heatmap calendar. - * - * @param {event} event The triggered event for the element. - */ -const detailView = (event) => { - let element = event.target; - if (element.tagName.toLowerCase() === 'td' && element.dataset.event === 'true') { // Only act on certain elements. - Dayview.display(element.dataset.date); - } -}; - -/** - * Start heatmap generation. - * - */ -const generateHeatmap = () => { - let heatmapOptions = JSON.parse(heatmapOptionsJson); - let year = parseInt(heatmapOptions.year); - let metric = heatmapOptions.metric; - let modules = heatmapOptions.modules; - let heatmapContainer = document.getElementById('local-assessfreq-report-heatmap'); - let spinner = heatmapContainer.getElementsByClassName('overlay-icon-container')[0]; - - spinner.classList.remove('hide'); // Show spinner if not already shown. - - Calendar.generate(year, 0, 11, metric, modules) - .then(calendar => { - let calendarContainer = document.getElementById('local-assessfreq-report-heatmap-months'); - calendarContainer.innerHTML = calendar.innerHTML; - calendarContainer.addEventListener('click', detailView); - }) - .then(Calendar.createHeatScale) - .then((heatScale) => { - let heatScaleContainer = document.getElementById('local-assessfreq-report-heatmap-scale'); - heatScaleContainer.innerHTML = heatScale.outerHTML; - spinner.classList.add('hide'); // Hide sinner if not already hidden. - }) - .catch(() => { - Notification.exception(new Error('Failed to calendar.')); - return; - }); -}; - -const updateDownload = ({year, metric, modules}) => { - let downloadForm = document.getElementById('local-assessfreq-heatmap-form'); - let formElements = downloadForm.elements; - let toRemove = new Array(); - - if (modules.length === 0) { - modules = ['all']; - } - - for (let i = 0; i < formElements.length; i++) { - if (formElements[i] === undefined) { - continue; - } - // Update year field. - if ((formElements[i].type === 'hidden') && (formElements[i].name === 'year')) { - formElements[i].value = year; - continue; - } - - // Update metric field. - if ((formElements[i].type === 'hidden') && (formElements[i].name === 'metric')) { - formElements[i].value = metric; - continue; - } - - // Update module fields. - if ((formElements[i].type === 'hidden') && (formElements[i].name.startsWith('modules'))) { - toRemove.push(formElements[i]); - continue; - } - } - - for (const element of toRemove) { - element.remove(); - } - - for (let i = 0; i < modules.length; i++) { - let input = document.createElement('input'); - input.type = 'hidden'; - input.name = 'modules[' + modules[i] + ']'; - input.value = modules[i]; - - downloadForm.appendChild(input); - } -}; - -/** - * Update the heatmap based on the current filter settings. - * - */ -const updateHeatmap = () => { - // Get current state of select menu items. - var cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules'); - var links = cardsModulesSelectHeatmapElement.getElementsByTagName('a'); - var modules = []; - - for (var i = 0; i < links.length; i++) { - if (links[i].classList.contains('active')) { - let module = links[i].dataset.module; - modules.push(module); - } - } - - // Save selection as a user preference. - if (modulesJson !== JSON.stringify(modules)) { - modulesJson = JSON.stringify(modules); - UserPreference.setUserPreference('local_assessfreq_heatmap_modules_preference', modulesJson); - } - - // Build settings object. - var optionsObj = { - 'year': yearselectheatmap, - 'metric': metricselectheatmap, - 'modules': modules - }; - - var optionsJson = JSON.stringify(optionsObj); - - if (optionsJson !== heatmapOptionsJson) { // Compare to global to see if there are any changes. - // If list has changed fetch heatmap and update user preference. - heatmapOptionsJson = optionsJson; - generateHeatmap(); - - // Update the download options. - updateDownload(optionsObj); - } -}; - -/** - * Get and process the selected year from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {event} event The triggered event for the element. - */ -const yearHeatmapButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselectheatmap) { // Only act on certain elements. - yearselectheatmap = element.dataset.year; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_heatmap_year_preference', yearselectheatmap); - - // Update card data based on selected year. - var yeartitle = document.getElementById('local-assessfreq-report-heatmap') - .getElementsByClassName('local-assessfreq-year')[0]; - yeartitle.innerHTML = yearselectheatmap; - - updateHeatmapDebounce(); // Call function to update heatmap. - } -}; - -/** - * Get and process the selected assessment metric from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {event} event The triggered event for the element. - */ -const metricHeatmapButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.metric !== metricselectheatmap) { - metricselectheatmap = element.dataset.metric; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_heatmap_metric_preference', metricselectheatmap); - - updateHeatmapDebounce(); // Call function to update heatmap. - } -}; - -/** - * Add the event listeners to the modules in the module select dropdown. - * - * @param {Object} element The dropdown HTML element that contains the list of modules as links. - */ -const moduleListChildrenEvents = (element) => { - var links = element.getElementsByTagName('a'); - var all = links[0]; - - for (var i = 0; i < links.length; i++) { - let module = links[i].dataset.module; - - if (module.toLowerCase() === 'all') { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - // Remove active class from all other links. - for (var j = 0; j < links.length; j++) { - links[j].classList.remove('active'); - } - updateHeatmapDebounce(); // Call function to update heatmap. - }); - } else if (module.toLowerCase() === 'close') { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - event.stopPropagation(); - - var dropdownmenu = document.getElementById('local-assessfreq-heatmap-modules-filter'); - dropdownmenu.classList.remove('show'); - - updateHeatmapDebounce(); // Call function to update heatmap. - }); - } else { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - event.stopPropagation(); - - all.classList.remove('active'); - - event.target.classList.toggle('active'); - updateHeatmapDebounce(); - }); - } - } -}; - -/** - * Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'year': yearselect, 'call': call})}; - let method = 'get_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Initialise method for report card rendering. - * - * @param {integer} context The current context id. - */ -export const init = (context) => { - contextid = context; - - // Set up event listener and related actions for year dropdown on report cards. - let cardsYearSelectElement = document.getElementById('local-assessfreq-cards-year'); - yearselect = cardsYearSelectElement.getElementsByClassName('active')[0].dataset.year; - cardsYearSelectElement.addEventListener('click', yearButtonAction); - - // Set up event listener and related actions for year dropdown on heatmp. - let cardsYearSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-year'); - yearselectheatmap = cardsYearSelectHeatmapElement.getElementsByClassName('active')[0].dataset.year; - cardsYearSelectHeatmapElement.addEventListener('click', yearHeatmapButtonAction); - - // Set up event listener and related actions for metric dropdown on heatmp. - let cardsMetricSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-metrics'); - metricselectheatmap = cardsMetricSelectHeatmapElement.getElementsByClassName('active')[0].dataset.metric; - cardsMetricSelectHeatmapElement.addEventListener('click', metricHeatmapButtonAction); - - // Set up event listener and related actions for module dropdown on heatmp. - let cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules'); - moduleListChildrenEvents(cardsModulesSelectHeatmapElement); - - // Set up zoom event listeners. - let dueMonthZoom = document.getElementById('local-assessfreq-assess-due-month-zoom'); - dueMonthZoom.addEventListener('click', triggerZoomGraph); - - let dueActivityZoom = document.getElementById('local-assessfreq-assess-by-activity-zoom'); - dueActivityZoom.addEventListener('click', triggerZoomGraph); - - let dueStudentZoom = document.getElementById('local-assessfreq-assess-due-month-student-zoom'); - dueStudentZoom.addEventListener('click', triggerZoomGraph); - - // Create the zoom modal. - ZoomModal.init(context); - - // Setup the dayview modal. - Dayview.init(); - - // Setup the chart data for each card. - ChartData.init(cards, contextid, 'get_chart', 'core/chart'); - - // Process loading for the assessment cards. - ChartData.getCardCharts(0, null, yearselect); - - // Get the data for the heatmap. - updateHeatmap(); - -}; diff --git a/amd/src/dashboard_quiz.js b/amd/src/dashboard_quiz.js deleted file mode 100644 index 41d6ed45..00000000 --- a/amd/src/dashboard_quiz.js +++ /dev/null @@ -1,251 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_quiz - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Ajax from 'core/ajax'; -import Notification from 'core/notification'; -import * as Str from 'core/str'; -import Templates from 'core/templates'; -import * as ChartData from 'local_assessfreq/chart_data'; -import * as FormModal from 'local_assessfreq/form_modal'; -import OverrideModal from 'local_assessfreq/override_modal'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import * as ZoomModal from 'local_assessfreq/zoom_modal'; - -// Module level variables. - -var selectQuizStr = ''; -var contextid; -var quizId = 0; -var refreshPeriod = 60; -var counterid; - -const cards = [ - {cardId: 'local-assessfreq-quiz-summary-graph', call: 'participant_summary', aspect: true}, - {cardId: 'local-assessfreq-quiz-summary-trend', call: 'participant_trend', aspect: false} -]; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - processDashboard(quizId); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Callback function that is called when a quiz is selected from the form. - * Starts the processing of the dashboard. - * - * @param {int} quiz The quiz Id. - */ -const processDashboard = (quiz) => { - quizId = quiz; - let titleElement = document.getElementById('local-assessfreq-quiz-title'); - titleElement.innerHTML = selectQuizStr; - // Get quiz data. - Ajax.call([{ - methodname: 'local_assessfreq_get_quiz_data', - args: { - quizid: quiz - }, - }])[0].then((response) => { - - let quizArray = JSON.parse(response); - let cardsElement = document.getElementById('local-assessfreq-quiz-dashboard-cards-deck'); - let trendElement = document.getElementById('local-assessfreq-quiz-dashboard-participant-trend-deck'); - let summaryElement = document.getElementById('local-assessfreq-quiz-summary-card'); - let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0]; - let tableElement = document.getElementById('local-assessfreq-quiz-table'); - let periodElement = document.getElementById('local-assessfreq-period-container'); - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows'); - - let quizLink = document.createElement('a'); - quizLink.href = quizArray.url; - quizLink.innerHTML = ''; - titleElement.innerHTML = quizArray.name + ' '; - titleElement.appendChild(quizLink); - - // Update page URL with quiz ID, without reloading page so that page navigation and bookmarking works. - const currentdUrl = new URL(window.location.href); - const newUrl = currentdUrl.origin + currentdUrl.pathname + '?id=' + quizId; - history.pushState({}, '', newUrl); - - // Update page title with quiz name. - Str.get_string('dashboard:quiztitle', 'local_assessfreq', {'quiz': quizArray.name, 'course': quizArray.courseshortname}) - .then((str) => { - document.title = str; - }).catch(() => { - Notification.exception(new Error('Failed to load string: dashboard:quiztitle')); - }); - - // Populate quiz summary card with details. - Templates.render('local_assessfreq/quiz-summary-card-content', quizArray).done((html) => { - summarySpinner.classList.add('hide'); - let contentcontainer = document.getElementById('local-assessfreq-quiz-summary-card-content'); - Templates.replaceNodeContents(contentcontainer, html, ''); - }).fail(() => { - Notification.exception(new Error('Failed to load quiz summary template.')); - return; - }); - - // Show the cards. - cardsElement.classList.remove('hide'); - trendElement.classList.remove('hide'); - tableElement.classList.remove('hide'); - periodElement.classList.remove('hide'); - - ChartData.getCardCharts(quizId); - TableHandler.getTable(quizId); - refreshCounter(); - - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - - return; - }).fail(() => { - Notification.exception(new Error('Failed to get quiz data')); - }); -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - processDashboard(quizId); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Trigger the zoom graph. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'quiz': quizId, 'call': call})}; - let method = 'get_quiz_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Initialise method for quiz dashboard rendering. - * - * @param {int} context The context id. - * @param {int} quiz The quiz id. - */ -export const init = (context, quiz) => { - contextid = context; - FormModal.init(context, processDashboard); // Create modal for quiz selection modal. - ZoomModal.init(context); // Create the zoom modal. - OverrideModal.init(context, processDashboard); - TableHandler.init( - quizId, - contextid, - 'local-assessfreq-quiz-student-table', - 'local-assessfreq-quiz-table', - 'get_student_table', - 'local_assessfreq_quiz_table_rows_preference', - 'local-assessfreq-quiz-student-table-search', - 'local_assessfreq_student_table', - 'local_assessfreq_set_table_preference' - ); - ChartData.init(cards, context, 'get_quiz_chart', 'local_assessfreq/chart'); - Str.get_string('loadingquiztitle', 'local_assessfreq').then((str) => { - selectQuizStr = str; - }).catch(() => { - Notification.exception(new Error('Failed to load string: loadingquiz')); - }).then(() => { - if (quiz > 0) { - quizId = quiz; - processDashboard(quiz); - } - }); - - UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference') - .then((response) => { - refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: refresh')); - }); - - // Event handling for refresh and period buttons. - let refreshElement = document.getElementById('local-assessfreq-period-container'); - refreshElement.addEventListener('click', refreshAction); - - // Set up zoom event listeners. - let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-graph-zoom'); - summaryZoom.addEventListener('click', triggerZoomGraph); - - let trendZoom = document.getElementById('local-assessfreq-quiz-summary-trend-zoom'); - trendZoom.addEventListener('click', triggerZoomGraph); - -}; diff --git a/amd/src/dashboard_quiz_inprogress.js b/amd/src/dashboard_quiz_inprogress.js deleted file mode 100644 index 674f7049..00000000 --- a/amd/src/dashboard_quiz_inprogress.js +++ /dev/null @@ -1,285 +0,0 @@ -// 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 . - -/** - * Javascript for quizzes in progress display and processing. - * - * @module local_assessfreq/dashboard_quiz_inprogress - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Ajax from 'core/ajax'; -import Notification from 'core/notification'; -import Templates from 'core/templates'; -import * as ChartData from 'local_assessfreq/chart_data'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import * as ZoomModal from 'local_assessfreq/zoom_modal'; - -/** - * Module level variables. - */ -var contextid; -var refreshPeriod = 60; -var counterid; -var tablesort = 'name_asc'; -var hoursAhead = 0; -var hoursBehind = 0; - -/** - * Hours filter array. - * - * @type {array} Title to display on modal. - */ -var hoursFilter; - -const cards = [ - {cardId: 'local-assessfreq-quiz-summary-upcomming-graph', call: 'upcomming_quizzes', aspect: true}, - {cardId: 'local-assessfreq-quiz-summary-inprogress-graph', call: 'all_participants_inprogress', aspect: true} -]; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - processDashboard(); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Starts the processing of the dashboard. - */ -const processDashboard = () => { - // Get summary quiz data. - Ajax.call([{ - methodname: 'local_assessfreq_get_inprogress_counts', - args: {}, - }])[0].then((response) => { - let quizSummary = JSON.parse(response); - let summaryElement = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card'); - let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0]; - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-inprogress-table-rows'); - let tableSortElement = document.getElementById('local-assessfreq-inprogress-table-sort'); - - summaryElement.classList.remove('hide'); // Show the card. - - // Populate summary card with details. - Templates.render('local_assessfreq/quiz-dashboard-inprogress-summary-card-content', quizSummary) - .done((html) => { - summarySpinner.classList.add('hide'); - - let contentcontainer = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card-content'); - Templates.replaceNodeContents(contentcontainer, html, ''); - }).fail(() => { - Notification.exception(new Error('Failed to load quiz counts template.')); - return; - }); - - hoursFilter = [hoursAhead, hoursBehind]; - ChartData.getCardCharts(0, hoursFilter); - TableHandler.getTable(0, hoursFilter, tablesort); - refreshCounter(); - - // Table event listeners. - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - tableSortElement.addEventListener('click', TableHandler.tableSortButtonAction); - - return; - }).fail(() => { - Notification.exception(new Error('Failed to get quiz summary counts')); - }); -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - processDashboard(); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Trigger the zoom graph. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'call': call, 'hoursahead': hoursAhead, 'hoursbehind': hoursBehind})}; - let method = 'get_quiz_inprogress_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Process the hours ahead event from the in progress quizzes table. - * - * @param {Event} event The triggered event for the element. - */ -const quizzesAheadSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', hours) - .then(() => { - hoursAhead = hours; - processDashboard(); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours ahead')); - }); - } -}; - -/** - * Process the hours behind event from the in progress quizzes table. - * - * @param {Event} event The triggered event for the element. - */ -const quizzesBehindSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', hours) - .then(() => { - hoursBehind = hours; - processDashboard(); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours behind')); - }); - } -}; - -/** - * Initialise method for quizzes in progress dashboard rendering. - * - * @param {int} context The context id. - */ -export const init = (context) => { - contextid = context; - ZoomModal.init(context); // Create the zoom modal. - TableHandler.init( - 0, - contextid, - null, - 'local-assessfreq-quiz-inprogress-table', - 'get_quizzes_inprogress_table', - 'local_assessfreq_quiz_table_inprogress_preference', - 'local-assessfreq-quiz-inprogress-table-search' - ); - ChartData.init(cards, context, 'get_quiz_inprogress_chart', 'local_assessfreq/chart'); - - UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference') - .then((response) => { - refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: refresh')); - }); - - UserPreference.getUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference') - .then((response) => { - tablesort = response.preferences[0].value ? response.preferences[0].value : 'name_asc'; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: tablesort')); - }); - - UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference') - .then((response) => { - hoursAhead = response.preferences[0].value ? response.preferences[0].value : 0; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }); - - UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference') - .then((response) => { - hoursBehind = response.preferences[0].value ? response.preferences[0].value : 0; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursbehind')); - }); - - // Event handling for refresh and period buttons. - let refreshElement = document.getElementById('local-assessfreq-period-container'); - refreshElement.addEventListener('click', refreshAction); - - // Set up zoom event listeners. - let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-inprogress-graph-zoom'); - summaryZoom.addEventListener('click', triggerZoomGraph); - - let upcommingZoom = document.getElementById('local-assessfreq-quiz-summary-upcomming-graph-zoom'); - upcommingZoom.addEventListener('click', triggerZoomGraph); - - // Set up behind and ahead quizzes event listeners. - let quizzesAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead'); - quizzesAheadElement.addEventListener('click', quizzesAheadSet); - - let quizzesBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind'); - quizzesBehindElement.addEventListener('click', quizzesBehindSet); - - processDashboard(); - -}; diff --git a/amd/src/dayview.js b/amd/src/dayview.js deleted file mode 100644 index 2dbfaaed..00000000 --- a/amd/src/dayview.js +++ /dev/null @@ -1,208 +0,0 @@ -// 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 . - -/** - * Javascript for heatmap calendar generation and display. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/notification', 'core/modal', 'local_assessfreq/modal_large', 'core/templates', 'core/ajax'], - function (Str, Notification, Modal, ModalLarge, Templates, Ajax) { - - /** - * Module level variables. - */ - var Dayview = {}; - var modalObj; - const spinner = '

' - + '' - + '

'; - - const stringArr = [ - {key: 'sun', component: 'calendar'}, - {key: 'mon', component: 'calendar'}, - {key: 'tue', component: 'calendar'}, - {key: 'wed', component: 'calendar'}, - {key: 'thu', component: 'calendar'}, - {key: 'fri', component: 'calendar'}, - {key: 'sat', component: 'calendar'}, - {key: 'jan', component: 'local_assessfreq'}, - {key: 'feb', component: 'local_assessfreq'}, - {key: 'mar', component: 'local_assessfreq'}, - {key: 'apr', component: 'local_assessfreq'}, - {key: 'may', component: 'local_assessfreq'}, - {key: 'jun', component: 'local_assessfreq'}, - {key: 'jul', component: 'local_assessfreq'}, - {key: 'aug', component: 'local_assessfreq'}, - {key: 'sep', component: 'local_assessfreq'}, - {key: 'oct', component: 'local_assessfreq'}, - {key: 'nov', component: 'local_assessfreq'}, - {key: 'dec', component: 'local_assessfreq'}, - ]; - var stringResult; - var systemTimezone = 'Australia/Melbourne'; - var dayViewTitle = ''; - - const getUserDate = function (timestamp, format) { - return new Promise((resolve) => { - const systemTimezoneTime = new Date(timestamp * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - let date = new Date(systemTimezoneTime); - const year = date.getFullYear(); - const month = stringResult[(7 + date.getMonth())]; - const day = date.getDate(); - const hours = date.getHours(); - const minutes = '0' + date.getMinutes(); - - const strftimetime = hours + ':' + minutes.substr(-2); // Will display time in 10:30 format. - const strftimedatetime = day + ' ' + month + ' ' + year + ', ' + strftimetime; - - if (format === 'strftimetime') { - resolve(strftimetime); - } else { - resolve(strftimedatetime); - } - - }); - }; - - const formatData = async function (response) { - let responseArr = JSON.parse(response); - - // We are displaying the event as a bar whose width represents the start and end time of the event. - // We need to scale the width of the bar to match the width of the container. Therefore 100% width of the container - // equals 24 hours (one day). - // There are 1440 mins per day. 1440 mins equals 100%, therefore 1 min = (100/1440)%. 5/72 == 100/1440. - let scaler = 5 / 72; - - for (let i = 0; i < responseArr.length; i++) { - const year = responseArr[i].endyear; - const month = (responseArr[i].endmonth) - 1; // Minus 1 for difference between months in PHP and JS. - const day = responseArr[i].endday; - const dayStart = (new Date(year, month, day).getTime()) / 1000; - const timeStart = new Date(responseArr[i].timestart * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - const timeStartTimestamp = (new Date(timeStart).getTime()) / 1000; - const timeEnd = new Date(responseArr[i].timeend * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - const timeEndTimestamp = (new Date(timeEnd).getTime()) / 1000; - let secondsSinceDayStart = timeStartTimestamp - dayStart; - let leftMargin = 0; - let width = 0; - - if (secondsSinceDayStart <= 0) { - secondsSinceDayStart = 0; - width = ((timeEndTimestamp - dayStart) / 60) * scaler; - responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimedatetime'); - } else { - leftMargin = (secondsSinceDayStart / 60) * scaler; - width = ((timeEndTimestamp - timeStartTimestamp) / 60) * scaler; - responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimetime'); - } - - if (leftMargin + width > 100) { - width = 100 - leftMargin; - } - - responseArr[i].leftmargin = leftMargin; - responseArr[i].width = width; - responseArr[i].end = await getUserDate(responseArr[i].timeend, 'strftimetime'); - } - - return new Promise((resolve) => { - resolve(responseArr); - }); - }; - - /** - * Initialise the base modal to be used. - * - * @param {int} date The date to display the day view for. - * - */ - Dayview.display = function (date) { - modalObj.setBody(spinner); - modalObj.show(); - let args = { - date: date, - modules: ['all'] - }; - let jsonArgs = JSON.stringify(args); - Ajax.call([{ - methodname: 'local_assessfreq_get_day_events', - args: {jsondata: jsonArgs}, - }])[0] - .then(formatData) - .then((responseArr) => { - - let context = {rows: responseArr}; - const year = responseArr[0].endyear; - const day = responseArr[0].endday; - const month = stringResult[(6 + parseInt(responseArr[0].endmonth))]; - const dayDate = day + ' ' + month + ' ' + year; - - modalObj.setTitle(dayViewTitle + ' ' + dayDate); - modalObj.setBody(Templates.render('local_assessfreq/dayview', context)); - - }).fail(() => { - Notification.exception(new Error('Failed to load day view')); - }); - }; - - /** - * Initialise the base modal to be used. - * - */ - Dayview.init = function () { - // Load the strings we'll need later. - Str.get_strings(stringArr).catch(() => { // Get required strings. - Notification.exception(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - stringResult = stringReturn; - }); - - // Get the system timzone. - Ajax.call([{ - methodname: 'local_assessfreq_get_system_timezone', - args: {}, - }], true, false)[0].then((response) => { - systemTimezone = response; - return; - }).fail(() => { - Notification.exception(new Error('Failed to get system timezone')); - }); - - Str.get_string('schedule', 'local_assessfreq').then((title) => { - dayViewTitle = title; - - // Create the Modal. - Modal.create({ - type: ModalLarge.TYPE, - title: title, - body: spinner, - large: true - }) - .then((modal) => { - modalObj = modal; - - }); - }).catch(Notification.exception); - - }; - - return Dayview; - } -); diff --git a/amd/src/debouncer.js b/amd/src/debouncer.js index 6e0f35ba..531d6050 100644 --- a/amd/src/debouncer.js +++ b/amd/src/debouncer.js @@ -17,6 +17,7 @@ * Debounce JS module. * * @module local_assessfreq/debouncer + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * diff --git a/amd/src/form_modal.js b/amd/src/form_modal.js deleted file mode 100644 index 5d2f6dc4..00000000 --- a/amd/src/form_modal.js +++ /dev/null @@ -1,242 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/modal', 'core/fragment', 'core/ajax', 'core/notification'], - function (Str, Modal, Fragment, Ajax, Notification) { - - /** - * Module level variables. - */ - var FormModal = {}; - var contextid; - var modalObj; - var resetOptions = []; - var callback; - - const spinner = '

' - + '' - + '

'; - - const observerConfig = { attributes: true, childList: false, subtree: true }; - - const ObserverCallback = function (mutationsList) { - for (let i = 0; i < mutationsList.length; i++) { - let element = mutationsList[i].target; - if (element.tagName.toLowerCase() === 'span' && element.classList.contains('badge')) { - element.addEventListener('click', updateModalBody); - document.getElementById('id_courses').dataset.course = element.dataset.value; - - document.getElementById('id_quiz').value = -1; - Ajax.call([{ - methodname: 'local_assessfreq_get_quizzes', - args: { - query: mutationsList[i].target.dataset.value - }, - }])[0].done((response) => { - let quizArray = JSON.parse(response); - let selectElement = document.getElementById('id_quiz'); - let selectElementLength = selectElement.options.length; - if (document.getElementById('noquizwarning') !== null) { - document.getElementById('noquizwarning').remove(); - } - // Clear exisitng options. - for (let j = selectElementLength - 1; j >= 0; j--) { - selectElement.options[j] = null; - } - - if (quizArray.length > 0) { - // Add new options. - for (let k = 0; k < quizArray.length; k++) { - let opt = quizArray[k]; - let el = document.createElement('option'); - el.textContent = opt.name; - el.value = opt.id; - selectElement.appendChild(el); - } - selectElement.removeAttribute('disabled'); - if (document.getElementById('noquizwarning') !== null) { - document.getElementById('noquizwarning').remove(); - } - } else { - resetOptions.forEach((option) => { - selectElement.appendChild(option); - }); - document.getElementById('id_quiz').value = 0; - selectElement.disabled = true; - } - - }).fail(() => { - Notification.exception(new Error('Failed to get quizzes')); - }); - - break; - } - } - }; - - const observer = new MutationObserver(ObserverCallback); - - /** - * Create the modal window. - * - * @private - */ - const createModal = function () { - Str.get_string('loading', 'local_assessfreq').then((title) => { - // Create the Modal. - Modal.create({ - type: Modal.types.DEFAULT, - title: title, - body: spinner, - large: true - }) - .then((modal) => { - modalObj = modal; - - // Explicitly handle form click events. - modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); - modalObj.getRoot().on('click', '#id_cancel', (e) => { - e.preventDefault(); - modalObj.setBody(spinner); - modalObj.hide(); - }); - }); - return; - }).catch(Notification.exception); - }; - - const getOptionPlaceholders = function () { - return new Promise((resolve, reject) => { - const stringArr = [ - {key: 'selectcourse', component: 'local_assessfreq'}, - {key: 'loadingquiz', component: 'local_assessfreq'}, - ]; - - Str.get_strings(stringArr).catch(() => { // Get required strings. - reject(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - for (let i = 0; i < stringReturn.length; i++) { - let el = document.createElement('option'); - el.textContent = stringReturn[i]; - el.value = 0 - i; - resetOptions.push(el); - } - resolve(); - }); - }); - }; - - /** - * Updates the body of the modal window. - * - * @param {Object} formdata - * @private - */ - const updateModalBody = function (formdata) { - if (typeof formdata === "undefined") { - formdata = {}; - } - - let params = { - 'jsonformdata': JSON.stringify(formdata) - }; - - getOptionPlaceholders() - .then(() => { - Str.get_string('searchquiz', 'local_assessfreq').then((title) => { - modalObj.setTitle(title); - modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_base_form', contextid, params)); - let modalContainer = document.querySelectorAll('[data-region*="modal-container"]')[0]; - observer.observe(modalContainer, observerConfig); - - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: searchquiz')); - }); - }); - }; - - /** - * Updates Moodle form with selected information. - * - * @param {Object} e - * @private - */ - const processModalForm = function (e) { - e.preventDefault(); // Stop modal from closing. - - let quizElement = document.getElementById('id_quiz'); - let quizId = quizElement.options[quizElement.selectedIndex].value; - let courseId = document.getElementById('id_courses').dataset.course; - - if (courseId === undefined || quizId < 1) { - if (document.getElementById('noquizwarning') === null) { - Str.get_string('noquizselected', 'local_assessfreq').then((warning) => { - let element = document.createElement('div'); - element.innerHTML = warning; - element.id = 'noquizwarning'; - element.classList.add('alert', 'alert-danger'); - modalObj.getBody().prepend(element); - - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: searchquiz')); - }); - } - } else { - modalObj.hide(); // Close modal. - modalObj.setBody(''); // Cleaer form. - observer.disconnect(); // Remove observer. - callback(quizId, courseId); // Trigger dashboard update. - } - - }; - - /** - * Display the Modal form. - */ - const displayModalForm = function () { - updateModalBody(); - modalObj.show(); - }; - - /** - * Initialise method for quiz dashboard rendering. - * - * @param {int} context The context id for the dashboard. - * @param {function} processDashboard The callback function to process the dashboard. - * - */ - FormModal.init = function (context, processDashboard) { - contextid = context; - callback = processDashboard; - createModal(); - - let createBroadcastButton = document.getElementById('local-assessfreq-find-quiz'); - createBroadcastButton.addEventListener('click', displayModalForm); - }; - - return FormModal; - } -); diff --git a/amd/src/modal_large.js b/amd/src/modal_large.js index f00fc33e..16422da1 100644 --- a/amd/src/modal_large.js +++ b/amd/src/modal_large.js @@ -16,22 +16,24 @@ /** * Javascript for large modal . * + * @module local_assessfreq/modal_large + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( ['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'], - function ($, Notification, CustomEvents, Modal, ModalRegistry) { + function($, Notification, CustomEvents, Modal, ModalRegistry) { - var registered = false; + let registered = false; /** * Constructor for the Modal. * * @param {object} root The root jQuery element for the modal */ - var ModalLarge = function (root) { + let ModalLarge = function(root) { Modal.call(this, root); }; diff --git a/amd/src/override_modal.js b/amd/src/override_modal.js index 23be45b4..f9747f3a 100644 --- a/amd/src/override_modal.js +++ b/amd/src/override_modal.js @@ -16,24 +16,25 @@ /** * Javascript for report card display and processing. * + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( - ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/notification'], - function ($,Str, Modal, ModalEvents, Fragment, Ajax, Notification) { + ['jquery', 'core/str', 'core/modal', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'], + function($, Str, Modal, ModalFactory, ModalEvents, Fragment, Ajax) { /** * Module level variables. */ - var OverrideModal = {}; - var contextid; - var modalObj; - var callback; - var quizid; - var userid; - var hoursFilter; + let OverrideModal = {}; + let contextid; + let activitytype; + let modalObj; + let activityid; + let userid; + let tableHandler; const spinner = '

' + '' @@ -44,56 +45,51 @@ define( * * @private */ - const createModal = function () { - Str.get_string('loading', 'local_assessfreq').then((title) => { + const createModal = function() { + Str.get_string('loading').then((title) => { // Create the Modal. Modal.create({ - type: Modal.types.DEFAULT, + type: ModalFactory.types.DEFAULT, title: title, body: spinner, large: true - }) - .then((modal) => { - modalObj = modal; - // Explicitly handle form click events. - modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); - modalObj.getRoot().on('click', '#id_cancel', function (e) { - e.preventDefault(); - modalObj.setBody(spinner); - modalObj.hide(); + }).then((modal) => { + modalObj = modal; + // Explicitly handle form click events. + modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); + modalObj.getRoot().on('click', '#id_cancel', function(e) { + e.preventDefault(); + modalObj.setBody(spinner); + modalObj.hide(); + }); }); - }); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: loading')); }); }; /** * Updates the body of the modal window. * - * @param {int} quiz The quiz id. - * @param {int} user The user id. - * @param {object} formdata The form data. + * @param {Integer} activity + * @param {Integer} user + * @param {Object} formdata + * @private */ - const updateModalBody = function (quiz, user, formdata) { + const updateModalBody = function(activity, user, formdata) { if (typeof formdata === "undefined") { formdata = {}; } let params = { 'jsonformdata': JSON.stringify(formdata), - 'quizid': quiz, + 'activitytype': activitytype, + 'activityid': activity, 'userid': user }; modalObj.setBody(spinner); - Str.get_string('useroverride', 'local_assessfreq').then((title) => { + Str.get_string('modal:useroverride', 'local_assessfreq').then((title) => { modalObj.setTitle(title); modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params)); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: useroverride')); }); }; @@ -112,7 +108,7 @@ define( // Handle invalid form fields for better UX. // I hate that I had to use JQuery for this. - var invalid = $.merge( + let invalid = $.merge( modalObj.getRoot().find('[aria-invalid="true"]'), modalObj.getRoot().find('.error') ); @@ -127,49 +123,44 @@ define( methodname: 'local_assessfreq_process_override_form', args: { 'jsonformdata': formjson, - 'quizid': quizid + 'activityid': activityid, + 'activitytype': activitytype, }, }])[0].done(() => { // For submission succeeded. modalObj.setBody(spinner); modalObj.hide(); - if (hoursFilter) { - callback(quizid, hoursFilter); - } else { - callback(quizid); + if (tableHandler !== undefined) { + tableHandler.getTable(); } }).fail(() => { // Form submission failed server side, redisplay with errors. - updateModalBody(quizid, userid, overrideform); + updateModalBody(activityid, userid, overrideform); }); } /** * Display the Modal form. - * - * @param {int} quiz The quiz id. - * @param {int} user The user id. - * @param {int} hours The hours to filter the quiz by. + * @param {Integer} activity + * @param {Integer} user */ - OverrideModal.displayModalForm = function (quiz, user, hours = null) { - quizid = quiz; + OverrideModal.displayModalForm = function(activity, user) { + activityid = activity; userid = user; - hoursFilter = hours; - updateModalBody(quiz, user); + updateModalBody(activityid, user); modalObj.show(); }; /** - * Initialise method for quiz dashboard rendering. - * - * @param {int} context The context id for the dashboard. - * @param {function} callbackFunction The callback function to call after the modal is closed. - * @param {int} hours The hours to filter the quiz by. + * Initialise method for dashboard rendering. + * @param {Integer} context + * @param {String} module + * @param {TableHandler} tablehandler If defined will trigger a table refresh on form save. */ - OverrideModal.init = function (context, callbackFunction, hours = null) { + OverrideModal.init = function(context, module, tablehandler = undefined) { + activitytype = module; contextid = context; - callback = callbackFunction; - hoursFilter = hours; + tableHandler = tablehandler; createModal(); }; diff --git a/amd/src/student_search.js b/amd/src/student_search.js deleted file mode 100644 index 6bbe09a3..00000000 --- a/amd/src/student_search.js +++ /dev/null @@ -1,191 +0,0 @@ -// 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 . - -/** - * Javascript for student search display and processing. - * - * @module local_assessfreq/student_search - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import $ from 'jquery'; -import Notification from 'core/notification'; -import OverrideModal from 'local_assessfreq/override_modal'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; - -/** - * Module level variables. - */ -var contextid; -var hoursAhead = 4; -var hoursBehind = 1; -var refreshPeriod = 60; -var counterid; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Process the hours ahead event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSearchAheadSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursahead_preference', hours) - .then(() => { - hoursAhead = hours; - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours ahead')); - }); - } -}; - -/** - * Process the hours behind event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSearchBehindSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursbehind_preference', hours) - .then(() => { - hoursBehind = hours; - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours behind')); - }); - } -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Initialise method for student search. - * - * @param {integer} context The current context id. - */ -export const init = (context) => { - contextid = context; - TableHandler.init( - 0, - contextid, - 'local-assessfreq-student-search-table', - 'local-assessfreq-student-search', - 'get_student_search_table', - 'local_assessfreq_student_search_table_rows_preference', - 'local-assessfreq-quiz-student-table-search', - 'local_assessfreq_student_search_table', - 'local_assessfreq_set_table_preference' - ); - - // Add required initial event listeners. - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows'); - let tableSearchAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead'); - let tableSearchBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind'); - let refreshElement = document.getElementById('local-assessfreq-period-container'); - - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - tableSearchAheadElement.addEventListener('click', tableSearchAheadSet); - tableSearchBehindElement.addEventListener('click', tableSearchBehindSet); - refreshElement.addEventListener('click', refreshAction); - - $.when( - UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursahead_preference') - .then((response) => { - hoursAhead = response.preferences[0].value ? response.preferences[0].value : 4; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }), - UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursbehind_preference') - .then((response) => { - hoursBehind = response.preferences[0].value ? response.preferences[0].value : 1; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }) - ).done(function () { - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - OverrideModal.init(context, TableHandler.getTable, [hoursAhead, hoursBehind]); - }); -}; diff --git a/amd/src/summary_participants.js b/amd/src/summary_participants.js deleted file mode 100644 index 8d036b68..00000000 --- a/amd/src/summary_participants.js +++ /dev/null @@ -1,75 +0,0 @@ -// 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 . - -/** - * Javascript for summary participants graph. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/fragment', 'core/templates', 'core/str', 'core/notification'], - function (Fragment, Templates, Str, Notification) { - - /** - * Module level variables. - */ - var Summary = {}; - - Summary.chart = function (assessids, contextid) { - assessids.forEach((assessid) => { - let chartElement = document.getElementById(assessid + '-summary-graph'); - let params = {'data': JSON.stringify({'quiz' : assessid, 'call': 'participant_summary'})}; - - Fragment.loadFragment('local_assessfreq', 'get_quiz_chart', contextid, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata == true) { - let legend = {position: 'left'}; - let context = { - 'withtable' : false, - 'chartdata' : JSON.stringify(resObj.chart), - 'aspect' : false, - 'legend' : JSON.stringify(legend) - }; - Templates.render('local_assessfreq/chart', context).done((html, js) => { - // Load card body. - Templates.replaceNodeContents(chartElement, html, js); - }).fail(() => { - Notification.exception(new Error('Failed to load chart template.')); - return; - }); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - chartElement.innerHTML = noDatastr.outerHTML; - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load card.')); - return; - }); - }); - }; - - return Summary; - } -); diff --git a/amd/src/table_handler.js b/amd/src/table_handler.js index 86a144c0..8005cd32 100644 --- a/amd/src/table_handler.js +++ b/amd/src/table_handler.js @@ -17,6 +17,7 @@ * Table handler JS module. * * @module local_assessfreq/table_handler + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -29,330 +30,316 @@ import * as Debouncer from 'local_assessfreq/debouncer'; import OverrideModal from 'local_assessfreq/override_modal'; import * as UserPreference from 'local_assessfreq/user_preferences'; -/** - * Module level variables. - */ -let cardElement; -let contextId; -let elementId; -let fragmentValue; -let hoursFilter; -let quizId = 0; -let overridden = false; -let rowPreference; -let sortValue; -let searchElement; - -/** - * Table id variable. - * - * @type {string} - */ -let id; - -/** - * Table method name variable. - * - * @type {string} - */ -let methodName; - -/** - * Display the table that contains all the students in the exam as well as their attempts. - * - * @param {int} quiz The Quiz Id. - * @param {array|null} hours Array with hour ahead or behind preference. - * @param {string|null} sortValueTable Sort preference. - * @param {int|string|null} page Page number. - */ -export const getTable = (quiz, hours = null, sortValueTable = null, page) => { - if (typeof page === "undefined" || overridden === true) { - page = 0; - } - - overridden = false; - - let search = document.getElementById(searchElement).value.trim(); - let tableElement = document.getElementById(elementId); - let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0]; - let tableBody = tableElement.getElementsByClassName('table-body')[0]; - let values = {'search': search, 'page': page}; - - // Add values to Object depending on dashboard type. - if (quiz > 0) { - quizId = quiz; - values.quiz = quizId; - } - if (hours) { - hoursFilter = hours; - values.hoursahead = hoursFilter[0]; - values.hoursbehind = hoursFilter[1]; - } - if (sortValueTable) { - sortValue = sortValueTable; - let sortArray = sortValue.split('_'); - let sortOn = sortArray[0]; - let direction = sortArray[1]; - values.sorton = sortOn; - values.direction = direction; - } - - let params = {'data': JSON.stringify(values)}; - - spinner.classList.remove('hide'); // Show spinner if not already shown. - Fragment.loadFragment('local_assessfreq', fragmentValue, contextId, params) - .done((response, js) => { - tableBody.innerHTML = response; - if (js) { - Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML. - } - spinner.classList.add('hide'); - tableEventListeners(); // Re-add table event listeners. - - }).fail(() => { - Notification.exception(new Error('Failed to update table.')); - }); -}; - -/** - * This stops the ajax method that updates the table from being updated - * while the user is still checking options. - * - */ -const debounceTable = Debouncer.debouncer(() => { - getTable(quizId, hoursFilter, sortValue); -}, 750); - -/** - * Process the sort click events from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSort = (event) => { - event.preventDefault(); - - let sortArray = {}; - const linkUrl = new URL(event.target.closest('a').href); - const targetSortBy = linkUrl.searchParams.get('tsort'); - let targetSortOrder = linkUrl.searchParams.get('tdir'); - - // We want to flip the clicked column. - if (targetSortOrder === '') { - targetSortOrder = "4"; +export default class TableHandler { + + constructor(activity, + context, + tableElementId, + tableFragmentComponent, + tableFragmentValue, + tableRowPreference, + tableSortPreference, + tableSearchElement, + tableId = null, + tableMethodName = null) { + this.activityId = activity; + this.contextId = context; + this.elementId = tableElementId; + this.fragmentComponent = tableFragmentComponent; + this.fragmentValue = tableFragmentValue; + this.rowPreference = tableRowPreference; + this.sortPreference = tableSortPreference; + this.searchElement = tableSearchElement; + this.id = tableId; + this.methodName = tableMethodName; + this.overridden = false; } - sortArray[targetSortBy] = targetSortOrder; + /** + * Display the table that contains all the students in the exam as well as their attempts. + * + * @param {int|string|null} page Page number. + */ + getTable = (page = 0) => { - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'sortby', - values: JSON.stringify(sortArray) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); + globalThis.reports++; -}; + this.overridden = false; -/** - * Process the sort click events from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableHide = (event) => { - event.preventDefault(); - - let hideArray = {}; - const linkUrl = new URL(event.target.closest('a').href); - const tableElement = document.getElementById(elementId); - const links = tableElement.querySelectorAll('a'); - let targetAction; - let targetColumn; - let action; - let column; - - if (linkUrl.search.indexOf('thide') !== -1) { - targetAction = 'hide'; - targetColumn = linkUrl.searchParams.get('thide'); - } else { - targetAction = 'show'; - targetColumn = linkUrl.searchParams.get('tshow'); - } + let search = document.getElementById(this.searchElement).value.trim(); + let tableElement = document.getElementById(this.elementId); + let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0]; + let tableBody = tableElement.getElementsByClassName('table-body')[0]; + let values = {'search': search, 'page': page}; - for (let i = 0; i < links.length; i++) { - let hideLinkUrl = new URL(links[i].href); - if (hideLinkUrl.search.indexOf('thide') !== -1) { - action = 'hide'; - column = hideLinkUrl.searchParams.get('thide'); - } else { - action = 'show'; - column = hideLinkUrl.searchParams.get('tshow'); + // Add values to Object depending on dashboard type. + if (this.activityId > 0) { + values.activityid = this.activityId; } - if (action === 'show') { - hideArray[column] = 1; + let params = {'data': JSON.stringify(values)}; + + spinner.classList.remove('hide'); // Show spinner if not already shown. + Fragment.loadFragment(this.fragmentComponent, this.fragmentValue, this.contextId, params) + .done((response, js) => { + tableBody.innerHTML = response; + if (js) { + Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML. + } + spinner.classList.add('hide'); + this.tableEventListeners(); // Re-add table event listeners. + globalThis.reports--; + }) + .fail(() => { + globalThis.reports--; + Notification.exception(new Error('Failed to update table.')); + }); + }; + + /** + * This stops the ajax method that updates the table from being updated + * while the user is still checking options. + * + */ + debounceTable = Debouncer.debouncer(() => { + this.getTable(); + }, 750); + + /** + * Process the sort click events from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableSort = (event) => { + event.preventDefault(); + + let sortArray = {}; + const linkUrl = new URL(event.target.closest('a').href); + const targetSortBy = linkUrl.searchParams.get('tsort'); + let targetSortOrder = linkUrl.searchParams.get('tdir'); + + // We want to flip the clicked column. + if (targetSortOrder === '') { + targetSortOrder = "4"; } - } - - hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column. - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'collapse', - values: JSON.stringify(hideArray) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); + sortArray[targetSortBy] = targetSortOrder; + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'sortby', + values: JSON.stringify(sortArray) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); -}; + }; -/** - * Process the reset click event from the table. - * - * @param {Event} event The triggered event for the element. - */ -const tableReset = (event) => { - event.preventDefault(); - - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'reset', - values: JSON.stringify({}) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); - -}; + /** + * Process the sort click events from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableHide = (event) => { + event.preventDefault(); -/** - * Process the search events from the student table. - * - * @param {Event} event The triggered event for the element. - * - */ -export const tableSearch = (event) => { - if (event.key === 'Meta' || event.ctrlKey) { - return false; - } + let hideArray = {}; + const linkUrl = new URL(event.target.closest('a').href); + const tableElement = document.getElementById(this.elementId); + const links = tableElement.querySelectorAll('a'); + let targetAction; + let targetColumn; + let action; + let column; + + if (linkUrl.search.indexOf('thide') !== -1) { + targetAction = 'hide'; + targetColumn = linkUrl.searchParams.get('thide'); + } else { + targetAction = 'show'; + targetColumn = linkUrl.searchParams.get('tshow'); + } - if (event.target.value.length === 0 || event.target.value.length > 2) { - debounceTable(); - } -}; + for (let i = 0; i < links.length; i++) { + let hideLinkUrl = new URL(links[i].href); + if (hideLinkUrl.search.indexOf('thide') !== -1) { + action = 'hide'; + column = hideLinkUrl.searchParams.get('thide'); + } else { + action = 'show'; + column = hideLinkUrl.searchParams.get('tshow'); + } -/** - * Process the search reset click event from the student table. - * - */ -export const tableSearchReset = () => { - let tableSearchInputElement = document.getElementById(searchElement); - tableSearchInputElement.value = ''; - tableSearchInputElement.focus(); - getTable(quizId, hoursFilter, sortValue); -}; + if (action === 'show') { + hideArray[column] = 1; + } + } -/** - * Process the row set event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -export const tableSearchRowSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let rows = event.target.dataset.metric; - UserPreference.setUserPreference(rowPreference, rows) - .then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: rows')); - }); - } -}; + hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column. + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'collapse', + values: JSON.stringify(hideArray) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); -/** - * Process the nav event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableNav = (event) => { - event.preventDefault(); + }; + + /** + * Process the reset click event from the table. + * + * @param {Event} event The triggered event for the element. + */ + tableReset = (event) => { + event.preventDefault(); + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'reset', + values: JSON.stringify({}) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); - const linkUrl = new URL(event.target.closest('a').href); - const page = linkUrl.searchParams.get('page'); + }; + + /** + * Process the search events from the student table. + * + * @param {Event} event + * @return {Boolean} + */ + tableSearch = (event) => { + if (event.key === 'Meta' || event.ctrlKey) { + return false; + } - if (page) { - getTable(quizId, hoursFilter, sortValue, page); - } -}; + if (event.target.value.length === 0 || event.target.value.length > 2) { + this.debounceTable(); + } + return true; + }; + + /** + * Process the search reset click event from the student table. + * + */ + tableSearchReset = () => { + let tableSearchInputElement = document.getElementById(this.searchElement); + tableSearchInputElement.value = ''; + tableSearchInputElement.focus(); + this.getTable(); + }; + + /** + * Process the row set event from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableSearchRowSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + let rows = event.target.dataset.metric; + UserPreference.setUserPreference(this.rowPreference, rows) + // eslint-disable-next-line promise/always-return + .then(() => { + this.getTable(); // Reload the table. + }) + .fail(() => { + Notification.exception(new Error('Failed to update user preference: rows')); + }); + } + }; -/** - * Get and process the selected assessment metric from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {Event} event The triggered event for the element. - */ -export const tableSortButtonAction = (event) => { - event.preventDefault(); - var element = event.target; + /** + * Process the nav event from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableNav = (event) => { + event.preventDefault(); - if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== sortValue) { - sortValue = element.dataset.sort; + const linkUrl = new URL(event.target.closest('a').href); + const page = linkUrl.searchParams.get('page'); - let links = element.parentNode.getElementsByTagName('a'); - for (let i = 0; i < links.length; i++) { - links[i].classList.remove('active'); + if (page) { + this.getTable(page); } + }; + + /** + * Get and process the selected assessment metric from the dropdown for the heatmap display, + * and update the corresponding user preference. + * + * @param {Event} event The triggered event for the element. + */ + tableSortButtonAction = (event) => { + event.preventDefault(); + var element = event.target; + + if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== this.sortValue) { + this.sortValue = element.dataset.sort; + + let links = element.parentNode.getElementsByTagName('a'); + for (let i = 0; i < links.length; i++) { + links[i].classList.remove('active'); + } - element.classList.add('active'); + element.classList.add('active'); - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference', sortValue); + // Save selection as a user preference. + UserPreference.setUserPreference(this.sortPreference, this.sortValue); - debounceTable(); // Call function to update table. - } -}; + this.debounceTable(); // Call function to update table. + } + }; -/** - * Re-add event listeners when the student table is updated. - */ -const tableEventListeners = () => { - const tableElement = document.getElementById(elementId); - let tableNavElement; - if (cardElement) { - const tableCardElement = document.getElementById(cardElement); + /** + * Re-add event listeners when the student table is updated. + */ + tableEventListeners = () => { + const tableElement = document.getElementById(this.elementId); const links = tableElement.querySelectorAll('a'); const resetLink = tableElement.getElementsByClassName('resettable'); const overrideLinks = tableElement.getElementsByClassName('action-icon override'); const disabledLinks = tableElement.getElementsByClassName('action-icon disabled'); - tableNavElement = tableCardElement.querySelectorAll('nav'); // There are two nav paging elements per table. + const tableNavElement = tableElement.querySelectorAll('nav'); // There are two nav paging elements per table. for (let i = 0; i < links.length; i++) { let linkUrl = new URL(links[i].href); if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) { - links[i].addEventListener('click', tableHide); + links[i].addEventListener('click', this.tableHide); } else if (linkUrl.search.indexOf('tsort') !== -1) { - links[i].addEventListener('click', tableSort); + links[i].addEventListener('click', this.tableSort); } } if (resetLink.length > 0) { - resetLink[0].addEventListener('click', tableReset); + resetLink[0].addEventListener('click', this.tableReset); } for (let i = 0; i < overrideLinks.length; i++) { - overrideLinks[i].addEventListener('click', triggerOverrideModal); + overrideLinks[i].addEventListener('click', this.triggerOverrideModal); } for (let i = 0; i < disabledLinks.length; i++) { @@ -360,61 +347,26 @@ const tableEventListeners = () => { event.preventDefault(); }); } - } else { - tableNavElement = tableElement.querySelectorAll('nav'); - } - - tableNavElement.forEach((navElement) => { - navElement.addEventListener('click', tableNav); - }); -}; - -/** - * Trigger the override modal form. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerOverrideModal = (event) => { - event.preventDefault(); - let userid = event.target.closest('a').id.substring(25); - if (userid.includes('-')) { - let elements = userid.split('-'); - quizId = elements.pop(); - userid = elements.pop(); - } - OverrideModal.displayModalForm(quizId, userid, hoursFilter); -}; + tableNavElement.forEach((navElement) => { + navElement.addEventListener('click', this.tableNav); + }); + }; + + /** + * Trigger the override modal form. Thin wrapper to add extra data to click event. + * + * @param {Event} event The triggered event for the element. + */ + triggerOverrideModal = (event) => { + event.preventDefault(); + let userid = event.target.closest('a').id.substring(25); + if (userid.includes('-')) { + let elements = userid.split('-'); + this.activityId = elements.pop(); + userid = elements.pop(); + } -/** - * Initialise method for table handler. - * - * @param {int} quiz The quiz id. - * @param {int} context The context id. - * @param {string} tableCardElement The table card element. - * @param {string} tableElementId The table element id. - * @param {string} tableFragmentValue The table fragment value. - * @param {string} tableRowPreference The table row preference. - * @param {string} tableSearchElement The table search element. - * @param {string|null} tableId The table id. - * @param {string|null} tableMethodName The table method name. - */ -export const init = (quiz, - context, - tableCardElement, - tableElementId, - tableFragmentValue, - tableRowPreference, - tableSearchElement, - tableId = null, - tableMethodName = null) => { - quizId = quiz; - contextId = context; - cardElement = tableCardElement; - elementId = tableElementId; - fragmentValue = tableFragmentValue; - rowPreference = tableRowPreference; - searchElement = tableSearchElement; - id = tableId; - methodName = tableMethodName; - }; + OverrideModal.displayModalForm(this.activityId, userid, this.hoursFilter); + }; +} diff --git a/amd/src/user_preferences.js b/amd/src/user_preferences.js index 7d18bd28..544408a0 100644 --- a/amd/src/user_preferences.js +++ b/amd/src/user_preferences.js @@ -17,6 +17,7 @@ * User preferences JS module. * * @module local_assessfreq/user_preferences + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/amd/src/zoom_modal.js b/amd/src/zoom_modal.js deleted file mode 100644 index c1ea2366..00000000 --- a/amd/src/zoom_modal.js +++ /dev/null @@ -1,111 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/modal', 'core/fragment', 'core/ajax', 'core/templates', 'local_assessfreq/modal_large', - 'core/notification'], - function (Str, Modal, Fragment, Ajax, Templates, ModalLarge, Notification) { - - /** - * Module level variables. - */ - var ZoomModal = {}; - var contextid; - var modalObj; - const spinner = '

' - + '' - + '

'; - - /** - * Provides zoom functionality for card graphs. - * - * @param {object} event The event object. - * @param {object} params The parameters for the fragment call. - * @param {string} method The method to call in the fragment. - */ - ZoomModal.zoomGraph = function (event, params, method) { - let title = event.target.parentElement.dataset.title; - - Fragment.loadFragment('local_assessfreq', method, contextid, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata == true) { - var context = { 'withtable' : false, 'chartdata' : JSON.stringify(resObj.chart), aspect: false}; - modalObj.setTitle(title); - modalObj.setBody(Templates.render('local_assessfreq/chart', context)); - modalObj.show(); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - modalObj.setTitle(title); - modalObj.setBody(noDatastr.outerHTML); - modalObj.show(); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load zoomed graph')); - return; - }); - - }; - - /** - * Create the modal window for graph zooming. - * - * @private - */ - const createModal = function () { - return new Promise((resolve) => { - Str.get_string('loading', 'core').then((title) => { - // Create the Modal. - Modal.create({ - type: ModalLarge.TYPE, - title: title, - body: spinner, - large: true - }) - .then((modal) => { - modalObj = modal; - resolve(); - }); - }).catch(Notification.exception); - }); - }; - - /** - * Initialise method for quiz dashboard rendering. - * - * @param {int} context The context id for the dashboard. - */ - ZoomModal.init = function (context) { - contextid = context; - createModal(); - }; - - return ZoomModal; - } -); diff --git a/ci.yml b/ci.yml new file mode 100644 index 00000000..f1044690 --- /dev/null +++ b/ci.yml @@ -0,0 +1,13 @@ +# .github/workflows/ci.yml +name: ci + +on: [push, pull_request] + +jobs: + ci: + uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main + # Required if you plan to publish (uncomment the below) + # secrets: + # moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }} + with: + disable_phpcpd: true diff --git a/classes/event/event_processed.php b/classes/event/event_processed.php index e42cdeab..13f38253 100644 --- a/classes/event/event_processed.php +++ b/classes/event/event_processed.php @@ -24,6 +24,8 @@ namespace local_assessfreq\event; +use core\event\base; + /** * Event class. * @@ -31,7 +33,8 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class event_processed extends \core\event\base { +class event_processed extends base { + /** * Init method. */ @@ -45,7 +48,7 @@ protected function init() { * * @return string */ - public static function get_name() { + public static function get_name() : string { return get_string('eventeventprocessed', 'local_assessfreq'); } @@ -54,7 +57,7 @@ public static function get_name() { * * @return string */ - public function get_description() { + public function get_description() : string { return get_string('eventeven_processed_desc', 'local_assessfreq'); } } diff --git a/classes/external.php b/classes/external.php index feebbc07..3938d2f0 100644 --- a/classes/external.php +++ b/classes/external.php @@ -21,9 +21,14 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + +use core\session\manager; +use local_assessfreq\source_base; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . "/externallib.php"); +require_once(dirname(__FILE__, 2) . '/lib.php'); /** * Local assessfreq Web Service. @@ -36,194 +41,23 @@ class local_assessfreq_external extends external_api { /** * Returns description of method parameters. * - * @return void - */ - public static function get_frequency_parameters() { - return new external_function_parameters([ - 'jsondata' => new external_value(PARAM_RAW, 'The data encoded as a json array'), - ]); - } - - /** - * Returns event frequency map for all users in site. - * - * @param string $jsondata JSON data. - * @return string JSON response. - */ - public static function get_frequency($jsondata) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_frequency_parameters(), - ['jsondata' => $jsondata] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $data = json_decode($jsondata, true); - $frequency = new \local_assessfreq\frequency(); - $freqarr = $frequency->get_frequency_array($data['year'], $data['metric'], $data['modules']); - - return json_encode($freqarr); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_frequency_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_heat_colors_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns heat map colors. - * This method doesn't require login or user session update. - * It also doesn't need any capability check. - * - * @return string JSON response. - */ - public static function get_heat_colors() { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Execute API call. - $frequency = new \local_assessfreq\frequency(); - $heatarray = $frequency->get_heat_colors(); - - return json_encode($heatarray); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_heat_colors_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_process_modules_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns modules enabled for processing along with their module name string. - * - * @return string JSON response. - */ - public static function get_process_modules() { - \core\session\manager::write_close(); // Close session early this is a read op. - - $modulesandstrings = ['number' => get_string('numberevents', 'local_assessfreq')]; - - // Execute API call. - $frequency = new \local_assessfreq\frequency(); - $processmodules = $frequency->get_process_modules(); - - foreach ($processmodules as $module) { - $modulesandstrings[$module] = get_string('modulename', $module); - } - - return json_encode($modulesandstrings); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_process_modules_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_day_events_parameters() { - return new external_function_parameters([ - 'jsondata' => new external_value(PARAM_RAW, 'The data encoded as a json array'), - ]); - } - - /** - * Returns event frequency map for all users in site. - * - * @param string $jsondata JSON data. - * @return string JSON response. - */ - public static function get_day_events($jsondata) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_day_events_parameters(), - ['jsondata' => $jsondata] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $data = json_decode($jsondata, true); - $frequency = new \local_assessfreq\frequency(); - $freqarr = $frequency->get_day_events($data['date'], $data['modules']); - - return json_encode($freqarr); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_day_events_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void + * @return external_function_parameters */ - public static function get_courses_parameters() { + public static function get_courses_parameters() : external_function_parameters { return new external_function_parameters([ 'query' => new external_value(PARAM_TEXT, 'The query to find'), ]); } /** - * Returns courses and quizzes in that course that match search data. + * Returns courses that match search data. * * @param string $query The search query. * @return string JSON response. */ - public static function get_courses($query) { - global $DB; - \core\session\manager::write_close(); // Close session early this is a read op. + public static function get_courses(string $query) : string { + global $DB, $SITE, $COURSE; + manager::write_close(); // Close session early this is a read op. // Parameter validation. self::validate_parameters( @@ -231,23 +65,28 @@ public static function get_courses($query) { ['query' => $query] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Execute API call. - $sql = 'SELECT id, fullname FROM {course} WHERE ' . $DB->sql_like('fullname', ':fullname', false) . ' AND id <> 1'; + $sql = 'SELECT id, fullname, category FROM {course} WHERE ' . $DB->sql_like('fullname', ':fullname', false) . ' AND id <> 1'; $params = ['fullname' => '%' . $DB->sql_like_escape($query) . '%']; - $courses = $DB->get_records_sql($sql, $params, 0, 11); + $courses = $DB->get_records_sql($sql, $params, 0, 30); $data = []; + if (has_capability('local/assessfreq:view', context_system::instance())) { + $data[SITEID] = [ + "id" => $SITE->id, + "fullname" => external_format_string($SITE->fullname, true, ["escape" => false]) + ]; + } + $categories = \core_course_category::make_categories_list(); foreach ($courses as $course) { - $data[$course->id] = ["id" => $course->id, "fullname" => format_string( - $course->fullname, - true, - ["context" => $context, "escape" => false] - ), ]; + $data[$course->id] = [ + "id" => $course->id, + "fullname" => $categories[$course->category] . ' / ' . external_format_string($course->fullname, true, ["escape" => false]) + ]; + } + + if (isset($data[$COURSE->id])) { + unset($data[$COURSE->id]); } return json_encode(array_values($data)); @@ -255,120 +94,80 @@ public static function get_courses($query) { /** * Returns description of method result value - * @return external_description + * @return external_value */ - public static function get_courses_returns() { + public static function get_courses_returns() : external_value { return new external_value(PARAM_RAW, 'Course result JSON'); } /** * Returns description of method parameters. * - * @return void + * @return external_function_parameters */ - public static function get_quizzes_parameters() { + public static function get_activities_parameters() : external_function_parameters { return new external_function_parameters([ - 'query' => new external_value(PARAM_INT, 'The query to find'), + 'courseid' => new external_value(PARAM_INT, 'The courseid to find'), ]); } /** - * Returns courses and quizzes in that course that match search data. + * Returns activities in the course that match search data. * - * @param string $query The search query. + * @param $courseid * @return string JSON response. */ - public static function get_quizzes($query) { + public static function get_activities($courseid) : string { global $DB; - \core\session\manager::write_close(); // Close session early this is a read op. + manager::write_close(); // Close session early this is a read op. // Parameter validation. self::validate_parameters( - self::get_quizzes_parameters(), - ['query' => $query] + self::get_activities_parameters(), + ['courseid' => $courseid] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Execute API call. - $params = ['course' => $query]; - $quizzes = $DB->get_records('quiz', $params, 'name ASC', 'id, name'); + $modules = $DB->get_records('course_modules', ['course' => $courseid]); + + $sources = get_sources(); $data = []; - foreach ($quizzes as $quiz) { - $data[$quiz->id] = ["id" => $quiz->id, "name" => format_string( - $quiz->name, - true, - ["context" => $context, "escape" => false] - ), ]; + foreach ($modules as $module) { + $modinfo = get_fast_modinfo($courseid); + $cm = $modinfo->get_cm($module->id); + // Skip over if source is not enabled or if the source doesn't have an activity dashboard. + $moduletype = $cm->modname; + if (!isset($sources[$moduletype]) || !method_exists($sources[$moduletype], 'get_activity_dashboard')) { + continue; + } + + $data[$module->id] = [ + "id" => $module->id, + "name" => $cm->get_module_type_name() . " - " . $cm->get_name() + ]; } + usort($data, fn($a, $b) => $a['name'] <=> $b['name']); + return json_encode(array_values($data)); } /** * Returns description of method result value - * @return external_description + * @return external_value */ - public static function get_quizzes_returns() { - return new external_value(PARAM_RAW, 'Quiz result JSON'); + public static function get_activities_returns() : external_value { + return new external_value(PARAM_RAW, 'Result JSON'); } - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_quiz_data_parameters() { - return new external_function_parameters([ - 'quizid' => new external_value(PARAM_INT, 'The quiz id to get data for'), - ]); - } - - /** - * Returns quiz data. - * - * @param string $quizid The quiz id to get data for. - * @return string JSON response. - */ - public static function get_quiz_data($quizid) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_quiz_data_parameters(), - ['quizid' => $quizid] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $quiz = new \local_assessfreq\quiz(); - $quizdata = $quiz->get_quiz_data($quizid); - - return json_encode($quizdata); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_quiz_data_returns() { - return new external_value(PARAM_RAW, 'Quiz data result JSON'); - } /** * Returns description of method parameters. * - * @return void + * @return external_function_parameters */ - public static function set_table_preference_parameters() { + public static function set_table_preference_parameters() : external_function_parameters { return new external_function_parameters([ 'tableid' => new external_value(PARAM_ALPHANUMEXT, 'The table id to set the preference for'), 'preference' => new external_value(PARAM_ALPHAEXT, 'The table preference to set'), @@ -377,15 +176,15 @@ public static function set_table_preference_parameters() { } /** - * Returns quiz data. + * Set table preferences. * * @param string $tableid The table id to set the preference for. * @param string $preference The name of the preference to set. * @param string $values The values to set for the preference, encoded as JSON. * @return string JSON response. */ - public static function set_table_preference($tableid, $preference, $values) { - global $SESSION; + public static function set_table_preference(string $tableid, string $preference, string $values) : string { + global $SESSION, $PAGE; // Parameter validation. self::validate_parameters( @@ -393,23 +192,14 @@ public static function set_table_preference($tableid, $preference, $values) { ['tableid' => $tableid, 'preference' => $preference, 'values' => $values] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Set up the initial preference template. - if (isset($SESSION->flextable[$tableid])) { - $prefs = $SESSION->flextable[$tableid]; - } else { - $prefs = [ - 'collapse' => [], - 'sortby' => [], - 'i_first' => '', - 'i_last' => '', - 'textsort' => [], - ]; - } + $prefs = $SESSION->flextable[$tableid] ?? [ + 'collapse' => [], + 'sortby' => [], + 'i_first' => '', + 'i_last' => '', + 'textsort' => [], + ]; // Set or reset the preferences. if ($preference == 'reset') { @@ -437,38 +227,40 @@ public static function set_table_preference_returns() { return new external_value(PARAM_ALPHAEXT, 'Name of the updated preference'); } + /** * Returns description of method parameters * * @return external_function_parameters */ - public static function process_override_form_parameters() { + public static function process_override_form_parameters() : external_function_parameters { return new external_function_parameters( [ 'jsonformdata' => new external_value(PARAM_RAW, 'The data from the create copy form, encoded as a json array'), - 'quizid' => new external_value(PARAM_INT, 'The quiz id to processs the override for'), + 'activitytype' => new external_value(PARAM_ALPHANUMEXT, 'The activity to processs the override for'), + 'activityid' => new external_value(PARAM_INT, 'The activity id to processs the override for'), ] ); } /** - * Submit the quiz override form. + * Submit the override form. * * @param string $jsonformdata The data from the form, encoded as a json array. - * @param int $quizid The quiz id to add an override for. - * @throws moodle_exception + * @param string $activitytype The activity to add an override for. + * @param int $activityid The activity id to add an override for. * @return string */ - public static function process_override_form($jsonformdata, $quizid) { + public static function process_override_form(string $jsonformdata, string $activitytype, int $activityid) : string { global $DB; // Release session lock. - \core\session\manager::write_close(); + manager::write_close(); // We always must pass webservice params through validate_parameters. $params = self::validate_parameters( self::process_override_form_parameters(), - ['jsonformdata' => $jsonformdata, 'quizid' => $quizid] + ['jsonformdata' => $jsonformdata, 'activitytype' => $activitytype, 'activityid' => $activityid] ); $formdata = json_decode($params['jsonformdata']); @@ -476,56 +268,15 @@ public static function process_override_form($jsonformdata, $quizid) { $submitteddata = []; parse_str($formdata, $submitteddata); - // Check access. - $quizdata = new \local_assessfreq\quiz(); - $context = $quizdata->get_quiz_context($quizid); - self::validate_context($context); - has_capability('mod/quiz:manageoverrides', $context); - - // Check if we have an existing override for this user. - $override = $DB->get_record('quiz_overrides', ['quiz' => $quizid, 'userid' => $submitteddata['userid']]); - - // Submit the form data. - $quiz = $DB->get_record('quiz', ['id' => $quizid], '*', MUST_EXIST); - $cm = get_course_and_cm_from_cmid($context->instanceid, 'quiz')[1]; - $mform = new \local_assessfreq\form\quiz_override_form($cm, $quiz, $context, $override, $submitteddata); - - $mdata = $mform->get_data(); - - if ($mdata) { - $params = [ - 'context' => $context, - 'other' => [ - 'quizid' => $quizid, - ], - 'relateduserid' => $mdata->userid, - ]; - $mdata->quiz = $quizid; - - if (!empty($override->id)) { - $mdata->id = $override->id; - $DB->update_record('quiz_overrides', $mdata); - - // Determine which override updated event to fire. - $params['objectid'] = $override->id; - $event = \mod_quiz\event\user_override_updated::create($params); - // Trigger the override updated event. - $event->trigger(); - } else { - unset($mdata->id); - $mdata->id = $DB->insert_record('quiz_overrides', $mdata); - - // Determine which override created event to fire. - $params['objectid'] = $mdata->id; - $event = \mod_quiz\event\user_override_created::create($params); - // Trigger the override created event. - $event->trigger(); - } - } else { - throw new moodle_exception('submitoverridefail', 'local_assessfreq'); + $processid = 0; + $sources = get_sources(); + $source = $sources[$activitytype]; + /* @var $source source_base */ + if (method_exists($source, 'process_override_form')) { + $processid = $source->process_override_form($activityid, $submitteddata); } - return json_encode(['overrideid' => $mdata->id]); + return json_encode(['overrideid' => $processid]); } /** @@ -536,82 +287,4 @@ public static function process_override_form($jsonformdata, $quizid) { public static function process_override_form_returns() { return new external_value(PARAM_RAW, 'JSON response.'); } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_system_timezone_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns system timezone. - * This method doesn't require login or user session update. - * It also doesn't need any capability check. - * - * @return string Timezone. - */ - public static function get_system_timezone() { - \core\session\manager::write_close(); // Close session early this is a read op. - global $DB; - - // Execute API call. - $timezone = $DB->get_field('config', 'value', ['name' => 'timezone'], MUST_EXIST); - - return $timezone; - } - - /** - * Returns description of method result value. - * - * @return external_description - */ - public static function get_system_timezone_returns() { - return new external_value(PARAM_TEXT, 'Timezone'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_inprogress_counts_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns quiz summary data for upcomming and inprogress quizzes. - * - * @return string JSON response. - */ - public static function get_inprogress_counts() { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $quiz = new \local_assessfreq\quiz(); - $now = time(); - $quizdata = $quiz->get_inprogress_counts($now); - - return json_encode($quizdata); - } - - /** - * Returns description of method result value. - * - * @return external_description - */ - public static function get_inprogress_counts_returns() { - return new external_value(PARAM_RAW, 'JSON quiz count data'); - } } diff --git a/classes/form/quiz_search_form.php b/classes/form/quiz_search_form.php deleted file mode 100644 index 385dc744..00000000 --- a/classes/form/quiz_search_form.php +++ /dev/null @@ -1,82 +0,0 @@ -. - -/** - * Form to search for quizzes. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\form; - -defined('MOODLE_INTERNAL') || die(); - -require_once("$CFG->libdir/formslib.php"); - -/** - * Form to search for quizzes. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_search_form extends \moodleform { - /** - * Build form for the broadcast message. - * - * {@inheritDoc} - * @see \moodleform::definition() - */ - public function definition() { - $mform = $this->_form; - $mform->disable_form_change_checker(); - - // Form heading. - $mform->addElement( - 'html', - \html_writer::div(get_string('searchquizform', 'local_assessfreq'), 'form-description mb-3') - ); - - $courseoptions = [ - 'multiple' => false, - 'placeholder' => get_string('entercourse', 'local_assessfreq'), - 'noselectionstring' => get_string('nocourse', 'local_assessfreq'), - 'ajax' => 'local_assessfreq/course_selector', - 'casesensitive' => false, - ]; - $mform->addElement('autocomplete', 'courses', get_string('course', 'local_assessfreq'), [], $courseoptions); - - $mform->addElement('hidden', 'coursechoice', '0'); - $mform->setType('coursechoice', PARAM_INT); - - $selectoptions = [ - 0 => get_string('selectcourse', 'local_assessfreq'), - -1 => get_string('loadingquiz', 'local_assessfreq'), - ]; - $mform->addElement( - 'select', - 'quiz', - get_string('quiz', 'local_assessfreq'), - $selectoptions - ); - $mform->disabledIf('quiz', 'coursechoice', 'eq', '0'); - - $btnstring = get_string('selectquiz', 'local_assessfreq'); - $this->add_action_buttons(true, $btnstring); - } -} diff --git a/classes/form/scheduler.php b/classes/form/scheduler.php deleted file mode 100644 index fa06bbf5..00000000 --- a/classes/form/scheduler.php +++ /dev/null @@ -1,55 +0,0 @@ -. - - -/** - * Text type form element - * - * Contains HTML class for a text type element - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once($CFG->libdir . '/form/static.php'); - -/** - * Text type element - * - * HTML class for a text type element - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class scheduler_form_element extends MoodleQuickForm_static implements templatable { - /** - * Form element scheduler. - * - * @param string $elementname (optional) Name of the text field. - * @param string $elementlabel (optional) text field label. - * @param string $text (optional) Text to put in text field. - */ - public function __construct($elementname = null, $elementlabel = null, $text = null) { - global $OUTPUT; - $text = $OUTPUT->render_from_template('local_assessfreq/scheduler_form_element', ['foo' => $text]); - - parent::__construct($elementname, $elementlabel, $text); - } -} diff --git a/classes/frequency.php b/classes/frequency.php index 97e9158f..9bcd2446 100644 --- a/classes/frequency.php +++ b/classes/frequency.php @@ -25,10 +25,19 @@ namespace local_assessfreq; use cache; +use context; +use core\dml\sql_join; +use core\oauth2\service\microsoft; +use Exception; +use moodle_recordset; +use stdClass; defined('MOODLE_INTERNAL') || die(); +global $CFG; + require_once($CFG->dirroot . '/calendar/lib.php'); +require_once($CFG->dirroot . '/local/assessfreq/lib.php'); /** * Frequency class. @@ -41,74 +50,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class frequency { - /** - * The due date databse field differs between module types. - * This map provides the translation. - * - * @var array $modduefield - */ - private $moduleendfield = [ - 'assign' => 'duedate', - 'choice' => 'timeclose', - 'data' => 'timeavailableto', - 'feedback' => 'timeclose', - 'forum' => 'duedate', - 'lesson' => 'deadline', - 'quiz' => 'timeclose', - 'scorm' => 'timeclose', - 'workshop' => 'submissionend', - ]; - - /** - * The start date databse field differs between module types. - * This map provides the translation. - * - * @var array $modduefield - */ - private $modulestartfield = [ - 'assign' => 'allowsubmissionsfromdate', - 'choice' => 'timeopen', - 'data' => 'timeavailablefrom', - 'feedback' => 'timeopen', - 'forum' => null, - 'lesson' => 'available', - 'quiz' => 'timeopen', - 'scorm' => 'timeopen', - 'workshop' => 'submissionstart', - ]; - - /** - * The time limit databse field differs between module types and only some support it. - * This map provides the translation - * - * @var array $moduletimelimit - */ - private $moduletimelimit = [ - 'leesson' => 'timelimit', - 'quiz' => 'timelimit', - - ]; - - - /** - * Map of capabilities that users must have - * before that activity event applies to them. - * - * @var array $capabilitymap - */ - private $capabilitymap = [ - 'assign' => ['mod/assign:submit', 'mod/assign:view'], - 'choice' => ['mod/choice:choose', 'mod/choice:view'], - 'data' => ['mod/data:writeentry', 'mod/data:viewentry', 'mod/data:view'], - 'feedback' => ['mod/feedback:complete', 'mod/feedback:viewanalysepage', 'mod/feedback:view'], - 'forum' => [ - 'mod/forum:startdiscussion', 'mod/forum:createattachment', 'mod/forum:replypost', 'mod/forum:viewdiscussion', ], - 'lesson' => ['mod/lesson:view'], - 'quiz' => ['mod/quiz:attempt', 'mod/quiz:view'], - 'scorm' => ['mod/scorm:savetrack', 'mod/scorm:viewscores'], - 'workshop' => ['mod/workshop:submit', 'mod/workshop:view'], - ]; - /** * Expiry period for caches. * @@ -121,27 +62,12 @@ class frequency { * * @var integer $batchsize */ - private $batchsize = 100; + private int $batchsize = 100; /** - * Get the modules to use in data collection. - * This is based on plugin configuration. - * - * @return array $modules The enabled modules. + * Cache of event users. */ - public function get_modules(): array { - $version = get_config('moodle', 'version'); - - // Start with a hardcoded list of modules. As there is not a good way to get a list of suppoerted modules. - // Different versions of Moodle have different supported modules. This is an anti pattern, but yeah... - if ($version < 2019052000) { // Versions less than 3.7 don't support forum due dates. - $availablemodules = ['assign', 'choice', 'data', 'feedback', 'lesson', 'quiz', 'scorm', 'workshop']; - } else { - $availablemodules = ['assign', 'choice', 'data', 'feedback', 'forum', 'lesson', 'quiz', 'scorm', 'workshop']; - } - - return $availablemodules; - } + private array $eventuserscache = []; /** * Given a modle shortname get capabilities that users must have @@ -151,20 +77,8 @@ public function get_modules(): array { * @return array Capabilities relating to the module. */ public function get_module_capabilities(string $module): array { - return $this->capabilitymap[$module]; - } - - /** - * Get currently enabled modules from the Moodle DB. - * - * @return array $modules The enabled modules. - */ - public function get_enabled_modules(): array { - global $DB; - - $modules = $DB->get_records_menu('modules', [], '', 'name, visible'); - - return $modules; + $sources = get_sources(true); + return $sources[$module]->get_user_capabilities(); } /** @@ -177,17 +91,13 @@ public function get_enabled_modules(): array { * @return array $modules Lis of modules to process. */ public function get_process_modules(): array { - $config = get_config('local_assessfreq'); - $modules = explode(',', $config->modules); - $disabledmodules = $config->disabledmodules; - - if (!$disabledmodules) { - $enabledmodules = $this->get_enabled_modules(); + $sources = get_sources(); + $modules = []; - foreach ($modules as $index => $module) { - if (empty($enabledmodules[$module])) { - unset($modules[$index]); - } + if (!empty($sources)) { + /* @var $source source_base */ + foreach ($sources as $source) { + $modules[] = $source->get_module(); } } @@ -200,19 +110,15 @@ public function get_process_modules(): array { * @param string $module Activity module to get data for. * @return string $sql The generated SQL. */ - private function get_sql_query(string $module): string { + private function get_sql_query(string $module, $duedate, $startdate, $timelimit): string { $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); - - $duedate = $this->moduleendfield[$module]; $sql = 'SELECT cm.id, cm.course, m.name, cm.instance, c.id as contextid, a.' . $duedate . ' AS duedate '; - if (!empty($this->modulestartfield[$module])) { - $startdate = $this->modulestartfield[$module]; + if ($startdate) { $sql .= ', a.' . $startdate . ' AS startdate '; } - if (!empty($this->moduletimelimit[$module])) { - $timelimit = $this->moduletimelimit[$module]; + if ($timelimit) { $sql .= ', a.' . $timelimit . ' AS timelimit '; } @@ -239,14 +145,12 @@ private function get_sql_query(string $module): string { * * @param string $sql * @param array $params - * @return \moodle_recordset + * @return moodle_recordset */ - private function get_module_events(string $sql, array $params): \moodle_recordset { + private function get_module_events(string $sql, array $params): moodle_recordset { global $DB; - $recordset = $DB->get_recordset_sql($sql, $params); - - return $recordset; + return $DB->get_recordset_sql($sql, $params); } /** @@ -257,13 +161,11 @@ private function get_module_events(string $sql, array $params): \moodle_recordse * @return array $timeelements Array of split time. */ private function format_time(int $timestamp): array { - $timeelements = [ + return [ 'endyear' => date('Y', $timestamp), 'endmonth' => date('m', $timestamp), 'endday' => date('d', $timestamp), ]; - - return $timeelements; } /** @@ -271,9 +173,9 @@ private function format_time(int $timestamp): array { * The event date may have been changed from in the past to in the future. In this case it may * not have been picked up by the delete records process. This method removes it a processing time. * - * @param \stdClass $record The record to process. + * @param stdClass $record The record to process. */ - private function cleanup_record(\stdClass $record): void { + private function cleanup_record(stdClass $record): void { global $DB; $params = ['module' => $record->module, 'instanceid' => $record->instanceid]; @@ -289,10 +191,10 @@ private function cleanup_record(\stdClass $record): void { * Take a recordest of events process * and store in correct database table. * - * @param \moodle_recordset $recordset - * @return array + * @param moodle_recordset $recordset + * @return int */ - private function process_module_events(\moodle_recordset $recordset): int { + private function process_module_events(moodle_recordset $recordset): int { global $DB; $recordsprocessed = 0; $toinsert = []; @@ -308,7 +210,7 @@ private function process_module_events(\moodle_recordset $recordset): int { // Iterate through the records and insert to database in batches. $timeelements = $this->format_time($record->duedate); - $insertrecord = new \stdClass(); + $insertrecord = new stdClass(); $insertrecord->module = $record->name; $insertrecord->instanceid = $record->instance; $insertrecord->courseid = $record->course; @@ -328,7 +230,6 @@ private function process_module_events(\moodle_recordset $recordset): int { // Insert in database. $DB->insert_records('local_assessfreq_site', $toinsert); $toinsert = []; // Reset array. - $recordsprocessed += count($toinsert); } } @@ -352,17 +253,24 @@ private function process_module_events(\moodle_recordset $recordset): int { */ public function process_site_events(int $duedate): int { $recordsprocessed = 0; - $enabledmods = $this->get_process_modules(); + $sources = get_sources(true); $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); - if (!empty($enabledmods[0])) { - // Itterate through modules. - foreach ($enabledmods as $module) { - $sql = $this->get_sql_query($module); + if (!empty($sources)) { + // Itterate through sources. + foreach ($sources as $source) { + + /* @var $source source_base */ + $sql = $this->get_sql_query( + $source->get_module_table(), + $source->get_close_field(), + $source->get_open_field(), + $source->get_timelimit_field() + ); if ($includehiddencourses) { - $params = [$module, CONTEXT_MODULE, $duedate, 1]; + $params = [$source->get_module(), CONTEXT_MODULE, $duedate, 1]; } else { - $params = [$module, CONTEXT_MODULE, $duedate, 1, 1]; + $params = [$source->get_module(), CONTEXT_MODULE, $duedate, 1, 1]; } $moduleevents = $this->get_module_events($sql, $params); // Get all events for module. @@ -378,11 +286,11 @@ public function process_site_events(int $duedate): int { * get the enrolled users with given capabilities for a given context. * Used to generte SQL for getting users in assessments. * - * @param \context $context The context to get the enrolled users for. + * @param context $context The context to get the enrolled users for. * @param array $capabilities The capabilities that users need to have. * @return array */ - public function generate_enrolled_wheres_joins_params(\context $context, array $capabilities): array { + public function generate_enrolled_wheres_joins_params(context $context, array $capabilities): array { $uid = 'u.id'; $joins = []; $wheres = []; @@ -401,33 +309,30 @@ public function generate_enrolled_wheres_joins_params(\context $context, array $ $wheres[] = "u.deleted = 0"; $wheres = implode(" AND ", $wheres); - $wherejoin = [$joins, $wheres, $params]; - - return $wherejoin; + return [$joins, $wheres, $params]; } /** * Our own implementation of get_enrolled_users. Allows us to check multiple capabilities * in less database queries. * - * @param \context $context The context to get the enrolled users for. + * @param context $context The context to get the enrolled users for. * @param array $capabilities The capabilities that users need to have. * @return array Enrolled user records */ - private function get_enrolled_users(\context $context, array $capabilities): array { + private function get_enrolled_users(context $context, array $capabilities): array { global $DB; [$joins, $wheres, $params] = $this->generate_enrolled_wheres_joins_params($context, $capabilities); - $finaljoin = new \core\dml\sql_join($joins, $wheres, $params); + $finaljoin = new sql_join($joins, $wheres, $params); $sql = "SELECT DISTINCT u.id - FROM {user} u - $finaljoin->joins - WHERE $finaljoin->wheres"; - $params = $finaljoin->params; + FROM {user} u + $finaljoin->joins + WHERE $finaljoin->wheres"; - return $DB->get_records_sql($sql, $params); + return $DB->get_records_sql($sql, $finaljoin->params); } /** @@ -436,17 +341,33 @@ private function get_enrolled_users(\context $context, array $capabilities): arr * this can take a long time. Consider using the get_event_users method * if you don't need the most up to date data. * - * @param int $contextid The context ID in a course for the event to check. + * @param int $contextid The module context ID for the event to check. * @param string $module The type of module the event is for. * @return array $users An array of user IDs. */ public function get_event_users_raw(int $contextid, string $module): array { - $context = \context::instance_by_id($contextid); + + $context = context::instance_by_id($contextid); + $coursecontext = $context->get_parent_context(); + + $cachekey = "{$coursecontext->id}-{$module}"; + if (isset($this->eventuserscache[$cachekey])) { + return $this->eventuserscache[$cachekey]; + } + $capabilities = $this->get_module_capabilities($module); - $users = $this->get_enrolled_users($context, $capabilities); + $roles = []; + foreach ($capabilities as $capability) { + $roles = $roles + get_roles_with_capability($capability, CAP_ALLOW, $context); + } + $users = []; + foreach ($roles as $role) { + $users = $users + get_users_from_role_on_context($role, $coursecontext); + } - return $users; + $this->eventuserscache[$cachekey] = $users; + return $this->eventuserscache[$cachekey]; } /** @@ -460,8 +381,7 @@ public function get_event_users_raw(int $contextid, string $module): array { */ public function get_event_users(int $contextid, string $module, bool $cache = true): array { global $DB; - $users = []; - $cachekey = (string)$contextid . '_' . $module; + $cachekey = $contextid . '_' . $module; // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventusers'); @@ -472,10 +392,11 @@ public function get_event_users(int $contextid, string $module, bool $cache = tr $users = $data->users; } else { // Not valid cache data. $sql = 'SELECT u.userid as id - FROM {local_assessfreq_user} u - INNER JOIN {local_assessfreq_site} s ON u.eventid = s.id - WHERE s.contextid = ? - AND s.module = ?'; + FROM {local_assessfreq_user} u + INNER JOIN {local_assessfreq_site} s ON u.eventid = s.id + WHERE s.contextid = ? + AND s.module = ? + GROUP BY u.userid'; $params = [$contextid, $module]; $users = $DB->get_records_sql($sql, $params); @@ -483,7 +404,7 @@ public function get_event_users(int $contextid, string $module, bool $cache = tr // Update cache. if (!empty($users)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->users = $users; $usercache->set($cachekey, $data); @@ -497,22 +418,20 @@ public function get_event_users(int $contextid, string $module, bool $cache = tr * Get stored events from a specified date. * * @param int $duedate The duedate to get events from. - * @return \moodle_recordset Recordset of event info. + * @return moodle_recordset Recordset of event info. */ - private function get_stored_events(int $duedate): \moodle_recordset { + private function get_stored_events(int $duedate): moodle_recordset { global $DB; $select = 'timeend >= ?'; $params = [$duedate]; - $recordset = $DB->get_recordset_select( + return $DB->get_recordset_select( 'local_assessfreq_site', $select, $params, 'timeend DESC', 'id, contextid, module' ); - - return $recordset; } /** @@ -526,8 +445,8 @@ private function get_stored_events(int $duedate): \moodle_recordset { private function prepare_user_event_records(array $users, int $eventid): array { $userrecords = []; foreach ($users as $user) { - $record = new \stdClass(); - $record->userid = $user->id; + $record = new stdClass(); + $record->userid = $user->userid; $record->eventid = $eventid; $userrecords[] = $record; @@ -574,8 +493,8 @@ public function delete_events(int $duedate): void { $select = 'timeend >= ?'; // We do the following in a transaction to maintain data consistency. + $transaction = $DB->start_delegated_transaction(); try { - $transaction = $DB->start_delegated_transaction(); $userevents = $DB->get_fieldset_select('local_assessfreq_site', 'id', $select, [$duedate]); // Delete site events. @@ -591,8 +510,19 @@ public function delete_events(int $duedate): void { } } + // Clear the caches to prevent desync between caches and database. + cache::make('local_assessfreq', 'siteevents')->purge(); + cache::make('local_assessfreq', 'userevents')->purge(); + cache::make('local_assessfreq', 'courseevents')->purge(); + cache::make('local_assessfreq', 'eventsduemonth')->purge(); + cache::make('local_assessfreq', 'monthlyuser')->purge(); + cache::make('local_assessfreq', 'eventsdueactivity')->purge(); + cache::make('local_assessfreq', 'yearevents')->purge(); + cache::make('local_assessfreq', 'usereventsallfrequencyarray')->purge(); + cache::make('local_assessfreq', 'eventusers')->purge(); + $transaction->allow_commit(); - } catch (\Exception $e) { + } catch (Exception $e) { $transaction->rollback($e); } } @@ -600,21 +530,20 @@ public function delete_events(int $duedate): void { /** * Delete processed event. * - * @param \stdClass $event The event to delete. + * @param stdClass $event The event to delete. */ - public function delete_event(\stdClass $event): void { + public function delete_event(stdClass $event): void { global $DB; // We do the following in a transaction to maintain data consistency. + $transaction = $DB->start_delegated_transaction(); try { - $transaction = $DB->start_delegated_transaction(); - // Delete site events. $DB->delete_records('local_assessfreq_site', ['id' => $event->id]); $DB->delete_records('local_assessfreq_user', ['eventid' => $event->id]); $transaction->allow_commit(); - } catch (\Exception $e) { + } catch (Exception $e) { $transaction->rollback($e); } } @@ -628,7 +557,7 @@ public function delete_event(\stdClass $event): void { * @param int $to Timestamp to fiter to. * @return array $filteredevents The list of filtered events. */ - private function filter_event_data($events, int $from, int $to = 0): array { + private function filter_event_data(array $events, int $from, int $to = 0): array { $filteredevents = []; // If an explicit to date was not defined default to a year from now. @@ -650,15 +579,15 @@ private function filter_event_data($events, int $from, int $to = 0): array { * Get site events. * This is events across all courses. * + * @param int $courseid The course to get events for or all events. This is not used here but kept for function mapping. * @param string $module The module to get events for or all events. * @param int $from The timestamp to get events from. * @param int $to The timestamp to get events to. * @param bool $cache If false cache won't be used fresh data will be retrieved from DB. * @return array $events An array of site events */ - public function get_site_events(string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { + public function get_site_events(int $courseid, string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { global $DB; - $events = []; // Try to get value from cache. $sitecache = cache::make('local_assessfreq', 'siteevents'); @@ -694,7 +623,7 @@ public function get_site_events(string $module = 'all', int $from = 0, int $to = // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $sitecache->set($module, $data); @@ -708,14 +637,14 @@ public function get_site_events(string $module = 'all', int $from = 0, int $to = /** * Get all events that are ending on a given date. * + * @param int $courseid The course to get events for. * @param string $date The end date for the event. * @param string $module The module to get events for or all events. * * @return array $events An array of site events */ - public function get_day_ending_events(string $date, string $module = 'all'): array { + public function get_day_ending_events(int $courseid , string $date, string $module = 'all'): array { global $DB; - $events = []; // TODO: Think about some caching here. // TODO: Improve unit test coverage for this. @@ -755,9 +684,13 @@ public function get_day_ending_events(string $date, string $module = 'all'): arr $params[] = $tostart; $params[] = $toend; - $events = $DB->get_records_sql($sql, $params); + // Add the courseid restrictions. + if ($courseid != SITEID) { + $params[] = $courseid; + $sql .= " AND c.id = ?"; + } - return $events; + return $DB->get_records_sql($sql, $params); } /** @@ -778,8 +711,8 @@ public function get_course_events( bool $cache = true ): array { global $DB; - $events = []; - $cachekey = (string)$courseid . '_' . $module; + + $cachekey = $courseid . '_' . $module . '_' . $from . '_' . $to; // Try to get value from cache. $coursecache = cache::make('local_assessfreq', 'courseevents'); @@ -801,7 +734,7 @@ public function get_course_events( // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $coursecache->set($cachekey, $data); @@ -823,8 +756,8 @@ public function get_course_events( */ public function get_user_events(int $userid, string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { global $DB; - $events = []; - $cachekey = (string)$userid . '_' . $module; + + $cachekey = $userid . '_' . $module; // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'userevents'); @@ -859,7 +792,7 @@ public function get_user_events(int $userid, string $module = 'all', int $from = // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $usercache->set($cachekey, $data); @@ -872,15 +805,16 @@ public function get_user_events(int $userid, string $module = 'all', int $from = /** * Return events for all users. * + * @param int $courseid The course to get events from. * @param string $module The module to get events for or all events. * @param int $from The timestamp to get events from. * @param int $to The timestamp to get events to. * @return array $events An array of site events */ - public function get_user_events_all(string $module = 'all', int $from = 0, int $to = 0): iterable { + public function get_user_events_all(int $courseid, string $module = 'all', int $from = 0, int $to = 0): iterable { global $DB; - $rowkey = $DB->sql_concat('s.id', "'_'", 'u.userid'); + $rowkey = $DB->sql_concat('s.id', "'_'", 'u.userid', "'_'", 'u.id'); $sql = "SELECT $rowkey as myrow, u.userid, s.* FROM {local_assessfreq_site} s INNER JOIN {local_assessfreq_user} u ON u.eventid = s.id @@ -896,12 +830,19 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ $sql .= ' WHERE s.module = ?'; } + // Should we include hidden courses. $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { $params[] = 1; $sql .= " AND c.visible = ?"; } + // Add the courseid restriction. + if ($courseid != SITEID) { + $params[] = $courseid; + $sql .= " AND c.id = ?"; + } + // If an explicit to date was not defined default to a year from now. if ($to === 0) { $to = time() + YEARSECS; @@ -911,9 +852,7 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ $params[] = $to; $sql .= " AND s.timeend >= ? AND s.timeend < ?"; - $events = $DB->get_recordset_sql($sql, $params); - - return $events; + return $DB->get_records_sql($sql, $params); } /** @@ -923,10 +862,15 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_by_month(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year; + public function get_events_due_by_month(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventsduemonth'); @@ -937,12 +881,10 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { } else { // Not valid cache data. $modules = $this->get_process_modules(); [$insql, $params] = $DB->get_in_or_equal($modules); - $params[] = $year; $sql = "SELECT s.endmonth, COUNT(s.id) as count FROM {local_assessfreq_site} s LEFT JOIN {course} c ON s.courseid = c.id - WHERE s.module $insql - AND s.endyear = ? "; + WHERE s.module $insql "; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -950,7 +892,29 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.endmonth + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.endmonth ORDER BY s.endmonth ASC'; $events = $DB->get_records_sql($sql, $params); @@ -959,7 +923,7 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -975,10 +939,15 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_monthly_by_user(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year; + public function get_events_due_monthly_by_user(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'monthlyuser'); @@ -989,13 +958,11 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a } else { // Not valid cache data. $modules = $this->get_process_modules(); [$insql, $params] = $DB->get_in_or_equal($modules); - $params[] = $year; $sql = "SELECT s.endmonth, COUNT(u.id) as count FROM {local_assessfreq_site} s INNER JOIN {local_assessfreq_user} u ON s.id = u.eventid INNER JOIN {course} c ON s.courseid = c.id - WHERE s.module $insql - AND s.endyear = ? "; + WHERE s.module $insql "; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -1003,7 +970,29 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.endmonth + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.endmonth ORDER BY s.endmonth ASC'; $events = $DB->get_records_sql($sql, $params); @@ -1012,7 +1001,7 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -1028,10 +1017,15 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_by_activity(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year . '_activity'; + public function get_events_due_by_activity(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventsdueactivity'); @@ -1040,11 +1034,11 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array if ($data && (time() < $data->expiry) && $cache) { // Valid cache data. $events = $data->events; } else { // Not valid cache data. - $params = [$year]; + $params = []; $sql = 'SELECT s.module, COUNT(s.id) as count FROM {local_assessfreq_site} s - LEFT JOIN {course} c ON s.courseid = c.id - WHERE s.endyear = ? '; + LEFT JOIN {course} c ON s.courseid = c.id + WHERE 1=1'; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -1052,7 +1046,29 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.module + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.module ORDER BY s.module ASC'; $events = $DB->get_records_sql($sql, $params); @@ -1061,7 +1077,7 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -1078,7 +1094,6 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array */ public function get_years_has_events(bool $cache = true): array { global $DB; - $years = []; $cachekey = 'yearevents'; // Try to get value from cache. @@ -1097,7 +1112,7 @@ public function get_years_has_events(bool $cache = true): array { // Update cache. if (!empty($years)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $years; $usercache->set($cachekey, $data); @@ -1109,12 +1124,14 @@ public function get_years_has_events(bool $cache = true): array { /** * Get all events on a particular day. * + * @param int $courseid The course to get events for. * @param string $date A string representations of the date to get events for. * @param array $modules The modules to get events for. * @return array $dayevents The list of events that day. */ - public function get_day_events(string $date, array $modules): array { + public function get_day_events(int $courseid, string $date, array $modules): array { $dayevents = []; + $events = []; if (empty($modules)) { $modules = ['all']; @@ -1122,21 +1139,23 @@ public function get_day_events(string $date, array $modules): array { // Get the raw events. if (in_array('all', $modules)) { - $events = $this->get_day_ending_events($date, 'all'); + $events = $this->get_day_ending_events($courseid, $date); } else { // Work through the event array. foreach ($modules as $module) { if ($module == 'all') { continue; } else { - $events = array_merge($events, $this->get_day_ending_events($date, $module)); + $events = array_merge($events, $this->get_day_ending_events($courseid, $date, $module)); } } } + $sources = get_sources(); + // Get additional information and format the event data. foreach ($events as $event) { - $context = \context::instance_by_id($event->contextid, IGNORE_MISSING); + $context = context::instance_by_id($event->contextid, IGNORE_MISSING); $course = get_course($event->courseid); if ($context) { @@ -1144,13 +1163,15 @@ public function get_day_events(string $date, array $modules): array { $event->url = $context->get_url()->out(); $event->usercount = count($this->get_event_users($event->contextid, $event->module)); $event->timelimit = - ($event->timelimit == 0) ? get_string('na', 'local_assessfreq') : round(($event->timelimit / 60)); + ($event->timelimit == 0) ? '-' : round(($event->timelimit / 60)); + $event->dashurl = ''; - if ($event->module == 'quiz') { - $dashurl = new \moodle_url('/local/assessfreq/dashboard_quiz.php', ['id' => $event->instanceid]); + /* @var $source source_base */ + $source = $sources[$event->module]; + if (method_exists($source, 'get_activity_dashboard')) { + $dashurl = new \moodle_url('/local/assessfreq/', ['activityid' => $context->instanceid], 'activity_dashboard'); $event->dashurl = $dashurl->out(); } - $event->courseshortname = $course->shortname; $dayevents[] = $event; @@ -1186,24 +1207,28 @@ public function get_day_events(string $date, array $modules): array { * @param array $modules List of modules to get events for. * @return array $freqarray The array of even frequencies. */ - public function get_frequency_array(int $year, string $metric, array $modules): array { + public function get_frequency_array(int $year = 0, int $month = 0, string $metric = 'assess', array $modules = []): array { + global $PAGE; + $freqarray = []; $events = []; - $from = mktime(0, 0, 0, 1, 1, $year); - $to = mktime(23, 59, 59, 12, 31, $year); + $from = mktime(0, 0, 0, $month, 1, $year); + $to = strtotime("+1 year", $from) - 1; $userfreqarraycache = cache::make('local_assessfreq', 'usereventsallfrequencyarray'); sort($modules); - $cachekey = implode("_", $modules) . '_' . (string)$from . '_' . (string)$to; - - if ($metric == 'assess') { + $cachekey = $PAGE->course->id . '_' . implode("_", $modules) . '_' . $from . '_' . $to; + if ($PAGE->course->id == SITEID) { $functionname = 'get_site_events'; - } else if ($metric == 'students') { + } else { + $functionname = 'get_course_events'; + } + + if ($metric == 'students') { + $functionname = 'get_user_events_all'; $data = $userfreqarraycache->get($cachekey); - if ($data && $metric == 'students' && (time() < $data->expiry)) { + if ($data && (time() < $data->expiry)) { return $data->freqarray; } - - $functionname = 'get_user_events_all'; } if (empty($modules)) { @@ -1211,20 +1236,21 @@ public function get_frequency_array(int $year, string $metric, array $modules): } // Get the raw events. - if (in_array('all', $modules)) { - $events = $this->$functionname('all', $from, $to); - } else { - // Work through the event array. - foreach ($modules as $module) { - $records = $this->$functionname($module, $from, $to); - foreach ($records as $record) { - $events[] = $record; + if (method_exists($this, $functionname)) { + if (in_array('all', $modules)) { + $events = $this->$functionname($PAGE->course->id, 'all', $from, $to); + } else { + // Work through the event array. + foreach ($modules as $module) { + $events = array_merge($events, $this->$functionname($PAGE->course->id, $module, $from, $to)); } } } // Iterate through the events, building the frequency array. + raise_memory_limit(MEMORY_EXTRA); foreach ($events as $event) { + $year = $event->endyear; $month = $event->endmonth; $day = $event->endday; $module = $event->module; @@ -1252,7 +1278,7 @@ public function get_frequency_array(int $year, string $metric, array $modules): */ if ($functionname == 'get_user_events_all') { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->freqarray = $freqarray; $userfreqarraycache->set($cachekey, $data); @@ -1269,17 +1295,21 @@ public function get_frequency_array(int $year, string $metric, array $modules): * @param array $modules The modules to get. * @return array $data The data for the download file. */ - public function get_download_data(int $year, string $metric, array $modules): array { - global $DB; + public function get_download_data(int $year, int $month, string $metric, array $modules): array { + global $DB, $PAGE; $data = []; $events = []; - $from = mktime(0, 0, 0, 1, 1, $year); - $to = mktime(23, 59, 59, 12, 31, $year); + $from = mktime(0, 0, 0, $month, 1, $year); + $to = strtotime("+1 year", $from) - 1; if ($metric == 'assess') { - $functionname = 'get_site_events'; - } else if ($metric == 'students') { + if ($PAGE->course->id == SITEID) { + $functionname = 'get_site_events'; + } else { + $functionname = 'get_course_events'; + } + } else { $functionname = 'get_user_events_all'; } @@ -1289,28 +1319,29 @@ public function get_download_data(int $year, string $metric, array $modules): ar // Get the raw events. if (in_array('all', $modules)) { - $events = $this->$functionname('all', $from, $to); + $events = $this->$functionname($PAGE->course->id, 'all', $from, $to); } else { // Work through the event array. foreach ($modules as $module) { if ($module == 'all') { continue; } else { - $events = array_merge($events, $this->$functionname($module, $from, $to)); + $events = array_merge($events, $this->$functionname($PAGE->course->id, $module, $from, $to)); } } } + // Soert the data by timeend. + usort($events, function($a, $b) { + return strcmp($a->timeend, $b->timeend); + }); + // Format the data ready for download. foreach ($events as $event) { $row = []; // Catch exception when context does not exist because assessfreq tables are out of sync. - try { - $context = \context::instance_by_id($event->contextid); - } catch (\dml_missing_record_exception $ex) { - continue; - } + $context = context::instance_by_id($event->contextid); $activity = get_string('modulename', $event->module); $startdate = userdate($event->timestart, get_string('strftimedatetimeshort', 'langconfig')); @@ -1339,116 +1370,6 @@ public function get_download_data(int $year, string $metric, array $modules): ar } $data[] = $row; } - return $data; } - - /** - * Get heat colors to use id nheatmap display from plugin configuration. - * - * @return array - */ - public function get_heat_colors(): array { - $config = get_config('local_assessfreq'); - - $heatcolors = [ - 1 => $config->heat1, - 2 => $config->heat2, - 3 => $config->heat3, - 4 => $config->heat4, - 5 => $config->heat5, - 6 => $config->heat6, - ]; - - return $heatcolors; - } - - /** - * Purge all plugin caches. - * This is invoked when a plugin setting is changed. - * - * @param string $name Name of the setting change that invoked the purge. - */ - public static function purge_caches($name): void { - global $CFG; - - // Get plugin cache definitions. - $definitions = []; - include($CFG->dirroot . '/local/assessfreq/db/caches.php'); - $definitionnames = array_keys($definitions); - - // Clear each cache. - foreach ($definitionnames as $definitionname) { - $cache = cache::make('local_assessfreq', $definitionname); - $cache->purge(); - } - } - - /** - * Get assessment conflicts. - * - * @param int $now The timestamp to get the conflicts for. - * @return array $conflicts The conflict data. - */ - private function get_conflicts(int $now): array { - global $DB; - $conflicts = []; - - // A conflict is an overlapping date range for two or more quizzes where the quiz has at least one common student. - $eventsql = 'SELECT lasa.id as eventid, lasb.id as conflictid - FROM {local_assessfreq_site} lasa - INNER JOIN {local_assessfreq_site} lasb ON (lasa.timestart > lasb.timestart AND lasa.timestart < lasb.timeend) - OR (lasa.timeend > lasb.timestart AND lasa.timeend < lasb.timeend) - OR (lasa.timeend > lasb.timeend AND lasa.timestart < lasb.timestart) - WHERE lasa.module = ? - AND lasb.module = ? - AND lasa.timestart > ?'; - $eventparams = ['quiz', 'quiz', $now, $now]; - $recordset = $DB->get_recordset_sql($eventsql, $eventparams); - - foreach ($recordset as $record) { - $usersql = 'SELECT DISTINCT laua.userid - FROM {local_assessfreq_user} laua - INNER JOIN {local_assessfreq_user} laub on laua.userid = laub.userid - WHERE laua.eventid = ? - AND laub.eventid = ?'; - - $userparams = [$record->eventid, $record->conflictid]; - $users = $DB->get_fieldset_sql($usersql, $userparams); - - if (!empty($users)) { - $conflict = new \stdClass(); - $conflict->eventid = $record->eventid; - $conflict->conflictid = $record->conflictid; - $conflict->users = $users; - - $conflicts[] = $conflict; - } - } - $recordset->close(); - - return $conflicts; - } - - /** - * Process the conflicts. - * - * @return array $conflicts Conflict data. - */ - public function process_conflicts(): array { - - // Final result should look like this. - $conflicts['eventid'] = [ - [ - 'conflicteventid' => 123, - 'effecteduserids' => [1, 2, 3], - ], - [ - 'conflicteventid' => 456, - 'effecteduserids' => [4, 5, 6], - ], - ]; - - return $conflicts; - } } diff --git a/classes/output/all_participants_inprogress.php b/classes/output/all_participants_inprogress.php deleted file mode 100644 index 6c1d746f..00000000 --- a/classes/output/all_participants_inprogress.php +++ /dev/null @@ -1,124 +0,0 @@ -. - -/** - * Renderable for all participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -use local_assessfreq\quiz; - -/** - * Renderable for all participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class all_participants_inprogress { - /** - * Generate the markup for the summary chart, - * used in the in progress quizzes dashboard. - * - * @param int $now Timestamp to get chart data for. - * @param int $hoursahead Amount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Amount of time in hours to look behind for quizzes starting. - * @return array With Generated chart object and chart data status. - */ - public function get_all_participants_inprogress_chart(int $now, int $hoursahead = 0, int $hoursbehind = 0): array { - - // Get quizzes for the supplied timestamp. - $quiz = new quiz($hoursahead, $hoursbehind); - $quizzes = $quiz->get_quiz_summaries($now); - - $inprogressquizzes = $quizzes['inprogress']; - $upcommingquizzes = $quizzes['upcomming']; - $finishedquizzes = $quizzes['finished']; - - foreach ($upcommingquizzes as $timestamp => $upcommingquiz) { - foreach ($upcommingquiz as $timestampupcomming => $upcomming) { - $inprogressquizzes[$timestampupcomming] = $upcomming; - } - } - - foreach ($finishedquizzes as $timestamp => $finishedquiz) { - foreach ($finishedquiz as $timestampfinished => $finished) { - $inprogressquizzes[$timestampfinished] = $finished; - } - } - - $notloggedin = 0; - $loggedin = 0; - $inprogress = 0; - $finished = 0; - - foreach ($inprogressquizzes as $quizobj) { - if (!empty($quizobj->tracking)) { - $notloggedin += $quizobj->tracking->notloggedin; - $loggedin += $quizobj->tracking->loggedin; - $inprogress += $quizobj->tracking->inprogress; - $finished += $quizobj->tracking->finished; - } - } - - $result = []; - - if (($notloggedin == 0) && ($loggedin == 0) && ($inprogress == 0) && ($finished == 0)) { - $result['hasdata'] = false; - $result['chart'] = false; - } else { - $result['hasdata'] = true; - - $seriesdata = [ - $notloggedin, - $loggedin, - $inprogress, - $finished, - ]; - - $labels = [ - get_string('notloggedin', 'local_assessfreq'), - get_string('loggedin', 'local_assessfreq'), - get_string('inprogress', 'local_assessfreq'), - get_string('finished', 'local_assessfreq'), - ]; - - $colors = [ - get_config('local_assessfreq', 'notloggedincolor'), - get_config('local_assessfreq', 'loggedincolor'), - get_config('local_assessfreq', 'inprogresscolor'), - get_config('local_assessfreq', 'finishedcolor'), - ]; - - // Create chart object. - $chart = new \core\chart_pie(); - $chart->set_doughnut(true); - $participants = new \core\chart_series(get_string('participants', 'local_assessfreq'), $seriesdata); - $participants->set_colors($colors); - $chart->add_series($participants); - $chart->set_labels($labels); - - $result['chart'] = $chart; - } - - return $result; - } -} diff --git a/classes/output/dashboard_table.php b/classes/output/dashboard_table.php deleted file mode 100644 index 4eaa7ffb..00000000 --- a/classes/output/dashboard_table.php +++ /dev/null @@ -1,201 +0,0 @@ -. -namespace local_assessfreq\output; - -/** - * Common code for outputting dashboard tables - * - * @package local_assessfreq - * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net} - * @author Mark Johnson - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -trait dashboard_table { - /** - * Get content for title column. - * - * @param \stdClass $row - * @return string html used to display the video field. - * @throws \moodle_exception - */ - public function col_fullname($row): string { - global $OUTPUT; - - return $OUTPUT->user_picture($row, ['size' => 35, 'includefullname' => true]); - } - - /** - * Get content for time start column. - * Displays the user attempt start time. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timestart($row) { - if ($row->timestart == 0) { - $content = \html_writer::span(get_string('na', 'local_assessfreq')); - } else { - $datetime = userdate($row->timestart, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time finish column. - * Displays the user attempt finish time. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timefinish($row) { - if ($row->timefinish == 0 && $row->timestart == 0) { - $content = \html_writer::span(get_string('na', 'local_assessfreq')); - } else if ($row->timefinish == 0 && $row->timestart > 0) { - $time = $row->timestart + $row->timelimit; - $datetime = userdate($time, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime, 'local-assessfreq-disabled'); - } else { - $datetime = userdate($row->timefinish, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for state column. - * Displays the users state in the quiz. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_state($row) { - if ($row->state == 'notloggedin') { - $color = 'background: ' . get_config('local_assessfreq', 'notloggedincolor'); - } else if ($row->state == 'loggedin') { - $color = 'background: ' . get_config('local_assessfreq', 'loggedincolor'); - } else if ($row->state == 'inprogress') { - $color = 'background: ' . get_config('local_assessfreq', 'inprogresscolor'); - } else if ($row->state == 'uploadpending') { - $color = 'background: ' . get_config('local_assessfreq', 'inprogresscolor'); - } else if ($row->state == 'finished') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } else if ($row->state == 'abandoned') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } else if ($row->state == 'overdue') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } - - $content = \html_writer::span('', 'local-assessfreq-status-icon', ['style' => $color]); - $content .= get_string($row->state, 'local_assessfreq'); - - return $content; - } - - /** - * Return an array of headers common across dashboard tables. - * - * @return array - */ - protected function get_common_headers(): array { - return [ - get_string('quiztimeopen', 'local_assessfreq'), - get_string('quiztimeclose', 'local_assessfreq'), - get_string('quiztimelimit', 'local_assessfreq'), - get_string('quiztimestart', 'local_assessfreq'), - get_string('quiztimefinish', 'local_assessfreq'), - get_string('status', 'local_assessfreq'), - get_string('actions', 'local_assessfreq'), - ]; - } - - /** - * Return an array of columns common across dashboard tables. - * - * @return array - */ - protected function get_common_columns(): array { - return [ - 'timeopen', - 'timeclose', - 'timelimit', - 'timestart', - 'timefinish', - 'state', - 'actions', - ]; - } - - /** - * Return HTML for common column actions. - * - * @param \stdClass $row - * @return string - */ - protected function get_common_column_actions(\stdClass $row): string { - global $OUTPUT; - $actions = ''; - if ( - $row->state == 'finished' - || $row->state == 'inprogress' - || $row->state == 'uploadpending' - || $row->state == 'abandoned' - || $row->state == 'overdue' - ) { - $classes = 'action-icon'; - $attempturl = new \moodle_url('/mod/quiz/review.php', ['attempt' => $row->attemptid]); - $attributes = [ - 'class' => $classes, - 'id' => 'tool-assessfreq-attempt-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userattempt', 'local_assessfreq'), - ]; - } else { - $classes = 'action-icon disabled'; - $attempturl = '#'; - $attributes = [ - 'class' => $classes, - 'id' => 'tool-assessfreq-attempt-' . $row->id, - ]; - } - $icon = $OUTPUT->render(new \pix_icon('i/search', '')); - $actions .= \html_writer::link($attempturl, $icon, $attributes); - - $profileurl = new \moodle_url('/user/profile.php', ['id' => $row->id]); - $icon = $OUTPUT->render(new \pix_icon('i/completion_self', '')); - $actions .= \html_writer::link($profileurl, $icon, [ - 'class' => 'action-icon', - 'id' => 'tool-assessfreq-profile-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userprofile', 'local_assessfreq'), - ]); - - $logurl = new \moodle_url('/report/log/user.php', ['id' => $row->id, 'course' => 1, 'mode' => 'all']); - $icon = $OUTPUT->render(new \pix_icon('i/report', '')); - $actions .= \html_writer::link($logurl, $icon, [ - 'class' => 'action-icon', - 'id' => 'tool-assessfreq-log-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userlogs', 'local_assessfreq'), - ]); - return $actions; - } -} diff --git a/classes/output/inprogress_participant_summary.php b/classes/output/inprogress_participant_summary.php deleted file mode 100644 index c27ec8b1..00000000 --- a/classes/output/inprogress_participant_summary.php +++ /dev/null @@ -1,76 +0,0 @@ -. - -/** - * Renderable for participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -/** - * Renderable for participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class inprogress_participant_summary { - /** - * Generate the markup for the summary chart, - * used in the quiz dashboard. - * - * @param \stdClass $participants The particpiant data. - * @return \core\chart_pie $chart Generated chart object and chart data status. - */ - public function get_inprogress_participant_summary_chart(\stdClass $participants): \core\chart_pie { - - $seriesdata = [ - $participants->notloggedin, - $participants->loggedin, - $participants->inprogress, - $participants->finished, - ]; - - $labels = [ - get_string('notloggedin', 'local_assessfreq'), - get_string('loggedin', 'local_assessfreq'), - get_string('inprogress', 'local_assessfreq'), - get_string('finished', 'local_assessfreq'), - ]; - - $colors = [ - get_config('local_assessfreq', 'notloggedincolor'), - get_config('local_assessfreq', 'loggedincolor'), - get_config('local_assessfreq', 'inprogresscolor'), - get_config('local_assessfreq', 'finishedcolor'), - ]; - - // Create chart object. - $chart = new \core\chart_pie(); - $chart->set_doughnut(true); - $participants = new \core\chart_series(get_string('participants', 'local_assessfreq'), $seriesdata); - $participants->set_colors($colors); - $chart->add_series($participants); - $chart->set_labels($labels); - $chart->set_legend_options(['display' => false]); - - return $chart; - } -} diff --git a/classes/output/quiz_user_table.php b/classes/output/quiz_user_table.php deleted file mode 100644 index 9c8f44b5..00000000 --- a/classes/output/quiz_user_table.php +++ /dev/null @@ -1,329 +0,0 @@ -. - -/** - * Renderable table for quiz dashboard users. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->libdir . '/tablelib.php'); - -use table_sql; -use renderable; - -/** - * Renderable table for quiz dashboard users. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_user_table extends table_sql implements renderable { - use dashboard_table; - - /** - * @var integer $quizid The ID of the braodcast to get the acknowledgements for. - */ - private $quizid; - - /** - * - * @var integer $contextid The context id. - */ - private $contextid; - - /** - * - * @var string $search The string to search for in the table data. - */ - private $search; - - /** - * @var string[] Extra fields to display. - */ - protected $extrafields; - - /** - * report_table constructor. - * - * @param string $baseurl Base URL of the page that contains the table. - * @param int $quizid The id from the quiz table to get data for. - * @param int $contextid The context id for the context the table is being displayed in. - * @param string $search The string to search for in the table. - * @param int $page the page number for pagination. - * - * @throws \coding_exception - */ - public function __construct(string $baseurl, int $quizid, int $contextid, string $search, int $page = 0) { - parent::__construct('local_assessfreq_student_table'); - global $DB; - - $this->quizid = $quizid; - $this->contextid = $contextid; - $this->search = $search; - $this->set_attribute('id', 'local_assessfreq_ackreport_table'); - $this->set_attribute('class', 'generaltable generalbox'); - $this->downloadable = false; - $this->define_baseurl($baseurl); - - $quizrecord = $DB->get_record('quiz', ['id' => $this->quizid], 'timeopen, timeclose, timelimit'); - $this->timeopen = $quizrecord->timeopen; - $this->timeclose = $quizrecord->timeclose; - $this->timelimit = $quizrecord->timelimit; - - $context = \context::instance_by_id($contextid); - - // Define the headers and columns. - $headers = []; - $columns = []; - - $headers[] = get_string('fullname'); - $columns[] = 'fullname'; - - $extrafields = \core_user\fields::get_identity_fields($context, false); - foreach ($extrafields as $field) { - $headers[] = \core_user\fields::get_display_name($field); - $columns[] = $field; - } - - $this->define_columns(array_merge($columns, $this->get_common_columns())); - $this->define_headers(array_merge($headers, $this->get_common_headers())); - $this->extrafields = $extrafields; - - // Setup pagination. - $this->currpage = $page; - $this->sortable(true); - $this->column_nosort = ['actions']; - } - - /** - * This function is used for the extra user fields. - * - * These are being dynamically added to the table so there are no functions 'col_' as - * the list has the potential to increase in the future and we don't want to have to remember to add - * a new method to this class. We also don't want to pollute this class with unnecessary methods. - * - * @param string $colname The column name - * @param \stdClass $data - * @return string - */ - public function other_cols($colname, $data) { - // Do not process if it is not a part of the extra fields. - if (!in_array($colname, $this->extrafields)) { - return ''; - } - - return s($data->{$colname}); - } - - /** - * Get content for time open column. - * Displays when the user attempt opens. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timeopen($row) { - $datetime = userdate($row->timeopen, get_string('trenddatetime', 'local_assessfreq')); - - if ($row->timeopen != $this->timeopen) { - $content = \html_writer::span($datetime, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time close column. - * Displays when the user attempt closes. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timeclose($row) { - $datetime = userdate($row->timeclose, get_string('trenddatetime', 'local_assessfreq')); - - if ($row->timeclose != $this->timeclose) { - $content = \html_writer::span($datetime, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time limit column. - * Displays the time the user has to finsih the quiz. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timelimit($row) { - $timelimit = format_time($row->timelimit); - - if ($row->timelimit != $this->timelimit) { - $content = \html_writer::span($timelimit, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($timelimit); - } - - return $content; - } - - /** - * Get content for actions column. - * Displays the actions for the user. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_actions($row) { - global $OUTPUT; - - $manage = ''; - - $icon = $OUTPUT->render(new \pix_icon('i/duration', '')); - $manage .= \html_writer::link('#', $icon, [ - 'class' => 'action-icon override', - 'id' => 'tool-assessfreq-override-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('useroverride', 'local_assessfreq'), - ]); - - $manage .= $this->get_common_column_actions($row); - - return $manage; - } - - - /** - * Query the database for results to display in the table. - * - * @param int $pagesize size of page for paginated displayed table. - * @param bool $useinitialsbar do you want to use the initials bar. - */ - public function query_db($pagesize, $useinitialsbar = false) { - global $CFG, $DB; - - $maxlifetime = $CFG->sessiontimeout; - $timedout = time() - $maxlifetime; - $sort = $this->get_sql_sort(); - - // We never want initial bars. We are using a custom search. - $this->initialbars(false); - - $frequency = new \local_assessfreq\frequency(); - $quiz = new \local_assessfreq\quiz(); - $capabilities = $frequency->get_module_capabilities('quiz'); - $context = $quiz->get_quiz_context($this->quizid); - - [$joins, $wheres, $params] = $frequency->generate_enrolled_wheres_joins_params($context, $capabilities); - $attemptsql = 'SELECT qa_a.userid, qa_a.state, qa_a.quiz, qa_a.id as attemptid, - qa_a.timestart as timestart, qa_a.timefinish as timefinish - FROM {quiz_attempts} qa_a - INNER JOIN (SELECT userid, MAX(timestart) as timestart - FROM {quiz_attempts} - GROUP BY userid) qa_b ON qa_a.userid = qa_b.userid - AND qa_a.timestart = qa_b.timestart - WHERE qa_a.quiz = :qaquiz'; - - $sessionsql = 'SELECT DISTINCT (userid) - FROM {sessions} - WHERE timemodified >= :stm'; - - $joins .= ' LEFT JOIN {quiz_overrides} qo ON u.id = qo.userid AND qo.quiz = :qoquiz'; - $joins .= " LEFT JOIN ($attemptsql) qa ON u.id = qa.userid"; - $joins .= " LEFT JOIN ($sessionsql) us ON u.id = us.userid"; - - $params['qaquiz'] = $this->quizid; - $params['qoquiz'] = $this->quizid; - $params['stm'] = $timedout; - - $finaljoin = new \core\dml\sql_join($joins, $wheres, $params); - $params = $finaljoin->params; - - $sql = "SELECT u.*, - COALESCE(qo.timeopen, $this->timeopen) AS timeopen, - COALESCE(qo.timeclose, $this->timeclose) AS timeclose, - COALESCE(qo.timelimit, $this->timelimit) AS timelimit, - COALESCE(qa.state, (CASE - WHEN us.userid > 0 THEN 'loggedin' - ELSE 'notloggedin' - END)) AS state, - qa.attemptid, - qa.timestart, - qa.timefinish - FROM {user} u - $finaljoin->joins - WHERE $finaljoin->wheres"; - - $pagesize = get_user_preferences('local_assessfreq_quiz_table_rows_preference', 20); - - if (!empty($sort)) { - $sql .= " ORDER BY $sort"; - } - - $records = $DB->get_recordset_sql($sql, $params); - $data = []; - $offset = $this->currpage * $pagesize; - $offsetcount = 0; - $recordcount = 0; - - foreach ($records as $record) { - $searchcount = 0; - if ($this->search != '') { - // Because we are using COALESE and CASE for state we can't use SQL WHERE so we need to filter in PHP land. - // Also because we need to do some filtering in PHP land, we'll do it all here. - $searchcount = -1; - $searchfields = array_merge($this->extrafields, ['firstname', 'lastname', 'state']); - - foreach ($searchfields as $searchfield) { - if (stripos($record->{$searchfield}, $this->search) !== false) { - $searchcount++; - } - } - } - - if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { - $data[$record->id] = $record; - } - - if ($searchcount > -1 && $offsetcount >= $offset) { - $recordcount++; - } - - if ($searchcount > -1) { - $offsetcount++; - } - } - - $records->close(); - - $this->pagesize($pagesize, $offsetcount); - $this->rawdata = $data; - } -} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index a89d0365..d661d17b 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -15,463 +15,50 @@ // along with Moodle. If not, see . /** - * Assessment Frequency block rendrer. + * Renderer. * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace local_assessfreq\output; -use local_assessfreq\quiz; +use local_assessfreq\form\course_search; +use local_assessfreq\report_base; use plugin_renderer_base; -use local_assessfreq\frequency; -/** - * Assessment Frequency block rendrer. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ class renderer extends plugin_renderer_base { /** - * Render the html for the report cards. - * Most content is loaded by ajax + * Render each of the assessfreqreport subplugins as tabs to display. * - * @return string html to display. + * @return void */ - public function render_report_cards(): string { - $currentyear = date('Y'); - $preferenceyear = get_user_preferences('local_assessfreq_overview_year_preference', $currentyear); - $frequency = new frequency(); - - // Get years that have events and load into context. - $years = $frequency->get_years_has_events(); - - if (empty($years)) { - $years = [$currentyear]; - } - - // Add current year to the selection of years if missing. - if (!in_array($currentyear, $years)) { - $years[] = $currentyear; + public function render_reports() : void { + $reports = get_reports(); + $reportoutputs = []; + foreach ($reports as $report) { + /* @var $report report_base */ + $reportoutputs[] = [ + 'tablink' => $report->get_tablink(), // Plugin name. + 'tabname' => $report->get_name(), // Display name. + 'report' => $report->get_contents(), + 'weight' => $report->get_tab_weight(), + ]; } - - $context = ['years' => [], 'currentyear' => $preferenceyear]; - - if (!empty($years)) { - foreach ($years as $year) { - if ($year == $preferenceyear) { - $context['years'][] = ['year' => ['val' => $year, 'active' => 'true']]; - } else { - $context['years'][] = ['year' => ['val' => $year]]; - } - } - } else { - $context['years'][] = ['year' => ['val' => $preferenceyear, 'active' => 'true']]; - } - - return $this->render_from_template('local_assessfreq/report-cards', $context); - } - - /** - * Render the HTML for the student quiz table. - * - * @param string $baseurl the base url to render the table on. - * @param int $quizid the id of the quiz in the quiz table. - * @param int $contextid the id of the context the table is being called in. - * @param string $search The string to search for. - * @param int $page the page number for pagination. - * @return string $output HTML for the table. - */ - public function render_student_table(string $baseurl, int $quizid, int $contextid, string $search = '', int $page = 0): string { - $renderable = new quiz_user_table($baseurl, $quizid, $contextid, $search, $page); - $perpage = 50; - ob_start(); - $renderable->out($perpage, true); - $output = ob_get_contents(); - ob_end_clean(); - - return $output; - } - - /** - * Render the HTML for the student search table. - * - * @param string $baseurl the base url to render the table on. - * @param int $contextid the id of the context the table is being called in. - * @param string $search The string to search for. - * @param int $hoursahead Ammount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Ammount of time in hours to look behind for quizzes starting. - * @param int $now The timestamp to use for the current time. - * @param int $page the page number for pagination. - * @return string $output HTML for the table. - */ - public function render_student_search_table( - string $baseurl, - int $contextid, - string $search, - int $hoursahead, - int $hoursbehind, - int $now, - int $page = 0 - ): string { - - $renderable = new student_search_table($baseurl, $contextid, $search, $hoursahead, $hoursbehind, $now, $page); - $perpage = 50; - - ob_start(); - $renderable->out($perpage, true); - $output = ob_get_contents(); - ob_end_clean(); - - return $output; - } - - /** - * Renders the quizzes in progress "table" on the quiz dashboard screen. - * We update the table via ajax. - * The table isn't a real table it's a collection of divs. - * - * @param string $search The search string for the table. - * @param int $page The page number of results. - * @param string $sorton The value to sort the quizzes by. - * @param string $direction The direction to sort the quizzes. - * @param int $hoursahead Amount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Amount of time in hours to look behind for quizzes starting. - * @return string $output HTML for the table. - */ - public function render_quizzes_inprogress_table( - string $search, - int $page, - string $sorton, - string $direction, - int $hoursahead = 0, - int $hoursbehind = 0 - ): string { - $context = \context_system::instance(); // TODO: pass the actual context in from the caller. - $now = time(); - $quiz = new quiz($hoursahead, $hoursbehind); - $quizzes = $quiz->get_quiz_summaries($now); - $pagesize = get_user_preferences('local_assessfreq_quiz_table_inprogress_preference', 5); - - $inprogressquizzes = $quizzes['inprogress']; - $upcommingquizzes = $quizzes['upcomming']; - $finishedquizzes = $quizzes['finished']; - - foreach ($upcommingquizzes as $key => $upcommingquiz) { - foreach ($upcommingquiz as $keyupcomming => $upcomming) { - $inprogressquizzes[$keyupcomming] = $upcomming; - } - } - - foreach ($finishedquizzes as $key => $finishedquiz) { - foreach ($finishedquiz as $keyfinished => $finished) { - $inprogressquizzes[$keyfinished] = $finished; - } - } - - [$filtered, $totalrows] = $quiz->filter_quizzes($inprogressquizzes, $search, $page, $pagesize); - $sortedquizzes = \local_assessfreq\utils::sort($filtered, $sorton, $direction); - - $pagingbar = new \paging_bar($totalrows, $page, $pagesize, '/'); - $pagingoutput = $this->render($pagingbar); - - $context = [ - 'quizzes' => array_values($sortedquizzes), - 'quizids' => json_encode(array_keys($sortedquizzes)), - 'context' => $context->id, - 'pagingbar' => $pagingoutput, - ]; - - $output = $this->render_from_template('local_assessfreq/quiz-inprogress-summary', $context); - - return $output; - } - - /** - * Return heatmap HTML. - * - * @return string The heatmap HTML. - */ - public function render_report_heatmap(): string { - $currentyear = date('Y'); - $preferenceyear = get_user_preferences('local_assessfreq_heatmap_year_preference', $currentyear); - $preferencemetric = get_user_preferences('local_assessfreq_heatmap_metric_preference', 'assess'); - $preferencemodules = json_decode(get_user_preferences('local_assessfreq_heatmap_modules_preference', '["all"]'), true); - - $frequency = new frequency(); - - // Initial context setup. - $context = [ - 'years' => [], - 'currentyear' => $preferenceyear, - 'modules' => [], - 'metrics' => [], - 'sesskey' => sesskey(), - 'downloadmetric' => $preferencemetric, - ]; - - // Get years that have events and load into context. - $years = $frequency->get_years_has_events(); - - if (empty($years)) { - $years = [$currentyear]; - } - - // Add current year to the selection of years if missing. - if (!in_array($currentyear, $years)) { - $years[] = $currentyear; - } - - if (!empty($years)) { - foreach ($years as $year) { - if ($year == $preferenceyear) { - $context['years'][] = ['year' => ['val' => $year, 'active' => 'true']]; - $context['downloadyear'] = $year; - } else { - $context['years'][] = ['year' => ['val' => $year]]; - } - } - } else { - $context['years'][] = ['year' => ['val' => $preferenceyear, 'active' => 'true']]; - $context['downloadyear'] = $preferenceyear; - } - - // Get modules for filters and load into context. - $modules = $frequency->get_process_modules(); - if (empty($preferencemodules) || $preferencemodules === ['all']) { - $context['modules'][] = ['module' => ['val' => 'all', 'name' => get_string('all'), 'active' => 'true']]; - } else { - $context['modules'][] = ['module' => ['val' => 'all', 'name' => get_string('all')]]; - } - - if (!empty($modules[0])) { - foreach ($modules as $module) { - $modulename = get_string('modulename', $module); - if (in_array($module, $preferencemodules)) { - $context['modules'][] = ['module' => ['val' => $module, 'name' => $modulename, 'active' => 'true']]; - } else { - $context['modules'][] = ['module' => ['val' => $module, 'name' => $modulename]]; - } - } - } - - // Get metric details and load into context. - $context['metrics'] = [$preferencemetric => 'true']; - - return $this->render_from_template('local_assessfreq/report-heatmap', $context); - } - - /** - * Get the html to render the assessment dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_assessment(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_report_cards(); - $html .= $this->render_report_heatmap(); - $html .= $this->footer(); - - return $html; - } - - /** - * Add HTML for quiz selection and quiz refresh buttons. - * - * @return string html for the button. - */ - private function render_quiz_select_refresh_button(): string { - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $context = [ - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - 'hide' => true, - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-controls', $context); - } - - /** - * Add HTML for quiz refresh button. - * - * @return string html for the button. - */ - private function render_quiz_refresh_button(): string { - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $preferencehoursahead = get_user_preferences('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', 0); - $preferencehoursbehind = get_user_preferences('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', 0); - - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $hours = [ - 0 => 'hours0', - 1 => 'hours1', - 4 => 'hours4', - 8 => 'hours8', - ]; - - $context = [ - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], - 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-inprogress-controls', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_quiz_dashboard_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_quiz_table_rows_preference', 20); - $rows = [ - 20 => 'rows20', - 50 => 'rows50', - 100 => 'rows100', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-cards', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_quiz_dashboard_inprogress_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_quiz_table_inprogress_preference', 10); - $preferencesort = get_user_preferences('local_assessfreq_quiz_table_inprogress_sort_preference', 'name_asc'); - $rows = [ - 5 => 'rows5', - 10 => 'rows10', - 20 => 'rows20', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - 'sort' => [$preferencesort => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-inprogress-cards', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_student_table_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_student_search_table_rows_preference', 20); - $preferencehoursahead = get_user_preferences('local_assessfreq_student_search_table_hoursahead_preference', 4); - $preferencehoursbehind = get_user_preferences('local_assessfreq_student_search_table_hoursbehind_preference', 1); - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $rows = [ - 20 => 'rows20', - 50 => 'rows50', - 100 => 'rows100', - ]; - - $hours = [ - 0 => 'hours0', - 1 => 'hours1', - 4 => 'hours4', - 8 => 'hours8', - ]; - - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], - 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/student-search', $context); - } - - /** - * Get the html to render the quiz dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_quiz(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_quiz_select_refresh_button(); - $html .= $this->render_quiz_dashboard_cards(); - $html .= $this->footer(); - - return $html; - } - - /** - * Get the html to render the quizzes in porgress dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_quiz_inprogress(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_quiz_refresh_button(); - $html .= $this->render_quiz_dashboard_inprogress_cards(); - $html .= $this->footer(); - - return $html; - } - - /** - * Get the html to render the student search. - * - * @return string $html the html to display. - */ - public function render_student_search(): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_student_table_cards(); - $html .= $this->footer(); - - return $html; + usort($reportoutputs, function($a, $b) { + return $a['weight'] <=> $b['weight']; + }); + + $output = $this->output->header(); + $output .= $this->render_from_template( + 'local_assessfreq/index', + [ + 'reports' => $reportoutputs + ] + ); + $output .= $this->output->footer(); + echo $output; } } diff --git a/classes/output/upcomming_quizzes.php b/classes/output/upcomming_quizzes.php deleted file mode 100644 index d8925917..00000000 --- a/classes/output/upcomming_quizzes.php +++ /dev/null @@ -1,93 +0,0 @@ -. - -/** - * Renderable for upcomming quizzes card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -use local_assessfreq\quiz; - -/** - * Renderable for upcomming quizzes card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class upcomming_quizzes { - /** - * Generate the markup for the upcomming quizzes chart, - * used in the in progress quizzes dashboard. - * - * @param int $now Timestamp to get chart data for. - * @return array With Generated chart object and chart data status. - */ - public function get_upcomming_quizzes_chart(int $now): array { - - // Get quizzes for the supplied timestamp. - $quiz = new quiz(); - $quizzes = $quiz->get_quiz_summaries($now); - - $labels = []; - $quizseriestitle = get_string('quizzes', 'local_assessfreq'); - $participantseries = get_string('students', 'local_assessfreq'); - $result = []; - $result['hasdata'] = true; - - $quizseriesdata = []; - $participantseriesdata = []; - - foreach ($quizzes['upcomming'] as $timestamp => $upcomming) { - $quizcount = 0; - $participantcount = 0; - - foreach ($upcomming as $quiz) { - $quizcount++; - $participantcount += $quiz->participants; - } - - // Check if inprogress quizzes are upcomming quizzes with overrides. - foreach ($quizzes['inprogress'] as $inprogress) { - if ($inprogress->timestampopen >= $timestamp && $inprogress->timestampopen < $timestamp + HOURSECS) { - $quizcount++; - $participantcount += $inprogress->participants; - } - } - - $quizseriesdata[] = $quizcount; - $participantseriesdata[] = $participantcount; - $labels[] = userdate($timestamp + HOURSECS, get_string('inprogressdatetime', 'local_assessfreq')); - } - - // Create chart object. - $quizseries = new \core\chart_series($quizseriestitle, $quizseriesdata); - $participantseries = new \core\chart_series($participantseries, $participantseriesdata); - - $chart = new \core\chart_bar(); - $chart->add_series($quizseries); - $chart->add_series($participantseries); - $chart->set_labels($labels); - $result['chart'] = $chart; - - return $result; - } -} diff --git a/classes/plugininfo/assessfreqreport.php b/classes/plugininfo/assessfreqreport.php new file mode 100644 index 00000000..f61aaf31 --- /dev/null +++ b/classes/plugininfo/assessfreqreport.php @@ -0,0 +1,100 @@ +. + +/** + * Report plugininfo. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq\plugininfo; + +use admin_settingpage; +use core\plugininfo\base; +use moodle_url; +use part_of_admin_tree; + +class assessfreqreport extends base { + + /** + * Finds all enabled plugin names, the result may include missing plugins. + * @return array of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() : array { + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_plugins_of_type('assessfreqreport'); + + if (empty($plugins)) { + return array(); + } + + $enabled = []; + foreach ($plugins as $name => $plugin) { + if ($plugin->is_enabled()) { + $enabled[$name] = $name; + } + } + return $enabled; + } + + /** + * Whether the subplugin is enabled. + * + * @return bool Whether enabled. + */ + public function is_enabled() : bool { + return get_config('assessfreqreport_' . $this->name, 'enabled'); + } + + /** + * Returns the node name used in admin settings menu for this plugin settings (if applicable) + * + * @return string node name or null if plugin does not create settings node (default) + */ + public function get_settings_section_name(): string { + return 'assessfreqreport_' . $this->name; + } + + /** + * Include the settings.php file from sub plugins if they provide it. + * This is a copy of very similar implementations from various other subplugin areas. + */ + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} + diff --git a/classes/plugininfo/assessfreqsource.php b/classes/plugininfo/assessfreqsource.php new file mode 100644 index 00000000..f1c4e448 --- /dev/null +++ b/classes/plugininfo/assessfreqsource.php @@ -0,0 +1,99 @@ +. + +/** + * Source plugininfo. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq\plugininfo; + +use admin_settingpage; +use core\plugininfo\base; +use part_of_admin_tree; + +class assessfreqsource extends base { + + /** + * Finds all enabled plugin names, the result may include missing plugins. + * @return array of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() : array { + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_plugins_of_type('assessfreqsource'); + + if (empty($plugins)) { + return array(); + } + + $enabled = []; + foreach ($plugins as $name => $plugin) { + if ($plugin->is_enabled()) { + $enabled[$name] = $name; + } + } + return $enabled; + } + + /** + * Whether the subplugin is enabled. + * + * @return bool Whether enabled. + */ + public function is_enabled() : bool { + return get_config('assessfreqsource_' . $this->name, 'enabled'); + } + + /** + * Returns the node name used in admin settings menu for this plugin settings (if applicable) + * + * @return string node name or null if plugin does not create settings node (default) + */ + public function get_settings_section_name() : string { + return 'assessfreqsource_' . $this->name; + } + + /** + * Include the settings.php file from sub plugins if they provide it. + * This is a copy of very similar implementations from various other subplugin areas. + */ + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} + diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 3d44b2cd..917da0ec 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -24,10 +24,12 @@ namespace local_assessfreq\privacy; +use context; use core_privacy\local\metadata\collection; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; +use core_privacy\local\request\data_provider; use core_privacy\local\request\writer; use core_privacy\local\request\userlist; @@ -38,7 +40,7 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\request\data_provider, \core_privacy\local\metadata\provider { +class provider implements data_provider, \core_privacy\local\metadata\provider { /** * Returns metadata about this plugin's privacy policy. * @@ -75,7 +77,7 @@ public static function get_metadata(collection $collection): collection { * @return contextlist the contexts in which data is contained. */ public static function get_contexts_for_userid(int $userid): contextlist { - $contextlist = new \core_privacy\local\request\contextlist(); + $contextlist = new contextlist(); $contextlist->add_user_context($userid); $contextlist->add_system_context(); return $contextlist; @@ -118,8 +120,8 @@ public static function export_user_data(approved_contextlist $contextlist) { // Get records for user ID. $rows = $DB->get_records('local_assessfreq_user', ['userid' => $userid]); + $i = 0; if (count($rows) > 0) { - $i = 0; foreach ($rows as $row) { $parentclass[$i]['userid'] = $row->userid; $parentclass[$i]['eventid'] = $row->eventid; @@ -150,7 +152,7 @@ public static function export_user_data(approved_contextlist $contextlist) { * * @param context $context The context to delete for. */ - public static function delete_data_for_all_users_in_context(\context $context) { + public static function delete_data_for_all_users_in_context(context $context) { global $DB; // All data contained in system context. if ($context->contextlevel == CONTEXT_SYSTEM) { diff --git a/classes/quiz.php b/classes/quiz.php deleted file mode 100644 index b85488a8..00000000 --- a/classes/quiz.php +++ /dev/null @@ -1,773 +0,0 @@ -. - -/** - * Quiz data class. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq; - -use mod_quiz\question\bank\qbank_helper; - -/** - * Quiz data class. - * - * This class handles data processing to get quiz data. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz { - /** - * Ammount of time in hours for lookahead values. - * Defaults to 12. - * - * @var int $hoursahead. - */ - private $hoursahead = 12; - - /** - * Ammount of time in hours for lookbehind values. - * Defaults to 1. - * - * @var int $hoursahead. - */ - private $hoursbehind = 1; - - /** - * The direction used in sorting. - * - * @var string $sortdirection - */ - private $sortdirection; - - /** - * The quiz details to sort by. - * - * @var string $sorton - */ - private $sorton; - - /** - * Class constructor. - * - * @param int $hoursahead - * @param int $hoursbehind - */ - public function __construct(int $hoursahead = 12, int $hoursbehind = 1) { - $this->hoursahead = $hoursahead; - $this->hoursbehind = $hoursbehind; - } - - /** - * Given a quiz id get the module context. - * - * @param int $quizid The quiz ID of the context to get. - * @return \context_module $context The quiz module context. - */ - public function get_quiz_context(int $quizid): \context_module { - global $DB; - - $params = ['module' => 'quiz', 'quiz' => $quizid]; - $sql = 'SELECT cm.id - FROM {course_modules} cm - INNER JOIN {modules} m ON cm.module = m.id - INNER JOIN {quiz} q ON cm.instance = q.id AND cm.course = q.course - WHERE m.name = :module - AND q.id = :quiz'; - $cmid = $DB->get_field_sql($sql, $params); - $context = \context_module::instance($cmid); - - return $context; - } - - /** - * Get override info for a paricular quiz. - * Data returned is: - * Number of users with overrides in Quiz, - * Ealiest override start, - * Latest override end. - * - * @param int $quizid The ID of the quiz to get override data for. - * @param \context_module $context The context object of the quiz. - * @return \stdClass $overrideinfo Information about quiz overrides. - */ - private function get_quiz_override_info(int $quizid, \context_module $context): \stdClass { - global $DB; - - $capabilities = ['mod/quiz:attempt', 'mod/quiz:view']; - $overrideinfo = new \stdClass(); - $users = []; - $start = 0; - $end = 0; - - $sql = 'SELECT id, userid, COALESCE(timeopen, 0) AS timeopen, COALESCE(timeclose, 0) AS timeclose - FROM {quiz_overrides} - WHERE quiz = ?'; - $params = [$quizid]; - $overrides = $DB->get_records_sql($sql, $params); - - foreach ($overrides as $override) { - if (!has_all_capabilities($capabilities, $context, $override->userid)) { - continue; // Don't count users who can't access the quiz. - } - - $users[] = $override->userid; - - if ($override->timeclose > $end) { - $end = $override->timeclose; - } - - if ($start == 0) { - $start = $override->timeopen; - } else if ($override->timeopen < $start) { - $start = $override->timeopen; - } - } - - $users = count(array_unique($users)); - - $overrideinfo->start = $start; - $overrideinfo->end = $end; - $overrideinfo->users = $users; - - return $overrideinfo; - } - - /** - * Get quiz question infromation. - * Data returned is: - * List of individual question types, - * Count of questions in quiz, - * Count of question types. - * - * @param int $quizid The ID of the quiz to get override data for. - * @return \stdClass $questions The question data for the quiz. - */ - private function get_quiz_questions(int $quizid): \stdClass { - global $DB; - $questions = new \stdClass(); - $types = []; - $questioncount = 0; - $context = $this->get_quiz_context($quizid); - - $questionsrecords = qbank_helper::get_question_structure($quizid, $context); - - foreach ($questionsrecords as $questionrecord) { - $types[] = get_string('pluginname', 'qtype_' . $questionrecord->qtype); - $questioncount++; - } - - $typeswithcounts = []; - foreach (array_count_values($types) as $type => $count) { - $typeswithcounts[] = ['type' => $type, 'count' => $count]; - } - - $questions->types = $typeswithcounts; - $questions->typecount = count($typeswithcounts); - $questions->questioncount = $questioncount; - - return $questions; - } - - /** - * Method returns data about a quiz. - * Data returned is: - * Quiz name, - * Quiz start time, - * Quiz end time, - * Earliest participant start time (override), - * Latest participant end time (override), - * Total participants taking the quiz, - * Number participants with overrides in quiz, - * Quiz link, - * Number of questions, - * Number of question types, - * List of question types. - * - * @param int $quizid ID of the quiz to get data for. - * @return \stdClass $quizdata The retrieved quiz data. - */ - public function get_quiz_data(int $quizid): \stdClass { - global $DB; - $quizdata = new \stdClass(); - $context = $this->get_quiz_context($quizid); - - $quizrecord = $DB->get_record('quiz', ['id' => $quizid], 'name, timeopen, timeclose, timelimit, course'); - $course = get_course($quizrecord->course); - $courseurl = new \moodle_url('/course/view.php', ['id' => $quizrecord->course]); - - $overrideinfo = $this->get_quiz_override_info($quizid, $context); - $questions = $this->get_quiz_questions($quizid); - $frequency = new frequency(); - if (!empty($quizrecord->timeopen)) { - $timesopen = userdate($quizrecord->timeopen, get_string('strftimedatetime', 'langconfig')); - } else { - $timesopen = get_string('na', 'local_assessfreq'); - } - if (!empty($quizrecord->timeclose)) { - $timeclose = userdate($quizrecord->timeclose, get_string('strftimedatetime', 'langconfig')); - } else { - $timeclose = get_string('na', 'local_assessfreq'); - } - if (!empty($overrideinfo->start)) { - $overrideinfostart = userdate($overrideinfo->start, get_string('strftimedatetime', 'langconfig')); - } else { - $overrideinfostart = get_string('na', 'local_assessfreq'); - } - if (!empty($overrideinfo->end)) { - $overrideinfoend = userdate($overrideinfo->end, get_string('strftimedatetime', 'langconfig')); - } else { - $overrideinfoend = get_string('na', 'local_assessfreq'); - } - - // Handle override start. - if ($overrideinfo->start != 0 && $overrideinfo->start < $quizrecord->timeopen) { - $earlyopen = $overrideinfostart; - $earlyopenstamp = $overrideinfo->start; - } else { - $earlyopen = $timesopen; - $earlyopenstamp = $quizrecord->timeopen; - } - - // Handle override end. - if ($overrideinfo->end != 0 && $overrideinfo->end > $quizrecord->timeclose) { - $lateclose = $overrideinfoend; - $lateclosestamp = $overrideinfo->end; - } else { - $lateclose = $timeclose; - $lateclosestamp = $quizrecord->timeclose; - } - - // Quiz result link. - $resultlink = new \moodle_url('/mod/quiz/report.php', ['id' => $context->instanceid, 'mode' => 'overview']); - // Override link. - $overrridelink = new \moodle_url('/mod/quiz/overrides.php', ['cmid' => $context->instanceid, 'mode' => 'user']); - // Participant link. - $participantlink = new \moodle_url('/user/index.php', ['id' => $quizrecord->course]); - // Dashboard link. - $dashboardlink = new \moodle_url('/local/assessfreq/dashboard_quiz.php', ['id' => $quizid]); - - $quizdata->name = format_string($quizrecord->name, true, ["context" => $context, "escape" => true]); - $quizdata->timeopen = $timesopen; - $quizdata->timeclose = $timeclose; - $quizdata->timelimit = format_time($quizrecord->timelimit); - $quizdata->earlyopen = $earlyopen; - $quizdata->earlyopenstamp = $earlyopenstamp; - $quizdata->lateclose = $lateclose; - $quizdata->lateclosestamp = $lateclosestamp; - $quizdata->participants = count($frequency->get_event_users_raw($context->id, 'quiz')); - $quizdata->overrideparticipants = $overrideinfo->users; - $quizdata->url = $context->get_url()->out(false); - $quizdata->types = $questions->types; - $quizdata->typecount = $questions->typecount; - $quizdata->questioncount = $questions->questioncount; - $quizdata->resultlink = $resultlink->out(false); - $quizdata->overridelink = $overrridelink->out(false); - $quizdata->coursefullname = format_string($course->fullname, true, ["context" => $context, "escape" => true]); - $quizdata->courseshortname = $course->shortname; - $quizdata->courselink = $courseurl->out(false); - $quizdata->participantlink = $participantlink->out(false); - $quizdata->dashboardlink = $dashboardlink->out(false); - $quizdata->assessid = $quizid; - - return $quizdata; - } - - /** - * Get a list of all quiz overrides that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting overrides. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting overrides. - * @return array $quizzes The quizzes with applicable overrides. - */ - private function get_tracked_overrides(int $now, int $lookahead, int $lookbehind): array { - global $DB; - - $starttime = $now + $lookahead; - $endtime = $now - $lookbehind; - - $sql = 'SELECT id, quiz, userid, timeopen, timeclose - FROM {quiz_overrides} - WHERE (timeopen > 0 AND timeopen < :starttime) - AND (timeclose > :endtime OR timeclose > :now)'; - $params = [ - 'starttime' => $starttime, - 'endtime' => $endtime, - 'now' => $now, - ]; - - $quizzes = $DB->get_records_sql($sql, $params); - - return $quizzes; - } - - /** - * Get a list of all quizzes that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting quizzes. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting quizzes. - * @return array $quizzes The quizzes. - */ - private function get_tracked_quizzes(int $now, int $lookahead, int $lookbehind): array { - global $DB; - - $starttime = $now + $lookahead; - $endtime = $now - $lookbehind; - - $sql = 'SELECT id, timeopen, timeclose, timelimit, 0 AS isoverride - FROM {quiz} - WHERE (timeopen > 0 AND timeopen < :starttime) - AND (timeclose > :endtime OR timeclose > :now)'; - $params = [ - 'starttime' => $starttime, - 'endtime' => $endtime, - 'now' => $now, - ]; - - $quizzes = $DB->get_records_sql($sql, $params); - - return $quizzes; - } - - /** - * Get a list of all quizzes that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. With quiz start and end times adjusted to take into account users with overrides. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting quizzes. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting quizzes. - * @return array $quizzes The quizzes. - */ - private function get_tracked_quizzes_with_overrides(int $now, int $lookahead = HOURSECS, int $lookbehind = HOURSECS): array { - global $DB; - - $quizzes = $this->get_tracked_quizzes($now, $lookahead, $lookbehind); - $overrides = $this->get_tracked_overrides($now, $lookahead, $lookbehind); - - // Add override data to each quiz in the array. - foreach ($overrides as $override) { - $sql = 'SELECT id, timeopen, timeclose, timelimit - FROM {quiz} - WHERE id = :id'; - $params = [ - 'id' => $override->quiz, - ]; - - $quizzesoverride = $DB->get_record_sql($sql, $params); - - if ($quizzesoverride) { - if (array_key_exists($quizzesoverride->id, $quizzes)) { - $quizzesoverride->isoverride = $quizzes[$quizzesoverride->id]->isoverride; - if (isset($quizzes[$quizzesoverride->id]->overrides)) { - $quizzesoverride->overrides = $quizzes[$quizzesoverride->id]->overrides; - } - $quizzesoverride->overrides[] = $override; - $quizzes[$quizzesoverride->id] = $quizzesoverride; - } else { - $quizzesoverride->isoverride = 1; - $quizzesoverride->overrides[] = $override; - $quizzes[$quizzesoverride->id] = $quizzesoverride; - } - } - } - - return $quizzes; - } - - /** - * Get counts for inprogress assessments, both total in progress quiz activities - * and total participants in progress. - * - * @param int $now Timestamp to use for reference for time. - * @return array $quizzes Array of counts of inprogress assessments and participants. - */ - public function get_inprogress_counts(int $now): array { - // Get tracked quizzes. - $trackedquizzes = $this->get_tracked_quizzes_with_overrides($now, 0, 0); - - $counts = [ - 'assessments' => 0, - 'participants' => 0, - ]; - - foreach ($trackedquizzes as $quiz) { - $counts['assessments']++; - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - if (!empty($trackedrecords)) { - $tracking = array_pop($trackedrecords); - $counts['participants'] += $tracking->inprogress; - } - } - - return $counts; - } - - /** - * Get finished, in progress and upcomming quizzes and their associated data. - * - * @param int $now Timestamp to use for reference for time. - * @return array $quizzes Array of finished, inprogress and upcomming quizzes with associated data. - */ - public function get_quiz_summaries(int $now): array { - // Get tracked quizzes. - $lookahead = HOURSECS * $this->hoursahead; - $lookbehind = HOURSECS * $this->hoursbehind; - $trackedquizzes = $this->get_tracked_quizzes_with_overrides($now, $lookahead, $lookbehind); - - // Set up array to hold quizzes and data. - $quizzes = [ - 'finished' => [], - 'inprogress' => [], - 'upcomming' => [], - ]; - - // Itterate through the hours, processing in progress and upcomming quizzes. - for ($hour = 0; $hour <= $this->hoursahead; $hour++) { - $time = $now + (HOURSECS * $hour); - - if ($hour == 0) { - $quizzes['inprogress'] = []; - } - - $quizzes['upcomming'][$time] = []; - - // Seperate out inprogress and upcomming quizzes, then get data for each quiz. - foreach ($trackedquizzes as $quiz) { - if ($quiz->timeopen < $time && $quiz->timeclose > $time && $hour === 0) { // Get inprogress quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['inprogress'][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); // Remove quiz from array to help with performance. - } else if (($quiz->timeopen >= $time) && ($quiz->timeopen < ($time + HOURSECS))) { // Get upcomming quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['upcomming'][$time][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } else { - if (isset($quiz->overrides)) { - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['inprogress'][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } - } - } - } - - // Iterate through the hours, processing finished quizzes. - for ($hour = 1; $hour <= $this->hoursbehind; $hour++) { - $time = $now - (HOURSECS * $hour); - - $quizzes['finished'][$time] = []; - - // Get data for each finished quiz. - foreach ($trackedquizzes as $quiz) { - if (($quiz->timeclose >= $time) && ($quiz->timeclose < ($time + HOURSECS))) { // Get finished quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['finished'][$time][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } - } - } - - return $quizzes; - } - - /** - * Given a list of user ids, check if the user is logged in our not - * and return summary counts of logged in and not logged in users. - * - * @param array $userids User ids to get logged in status. - * @return \stdClass $usercounts Object with coutns of users logged in and not logged in. - */ - private function get_loggedin_users(array $userids): \stdClass { - global $CFG, $DB; - - $maxlifetime = $CFG->sessiontimeout; - $timedout = time() - $maxlifetime; - $userchunks = array_chunk($userids, 250); // Break list of users into chunks so we don't exceed DB IN limits. - - $loggedin = 0; // Count of logged in users. - $loggedout = 0; // Count of not loggedin users. - $loggedinusers = []; - $loggedoutusers = []; - - foreach ($userchunks as $userchunk) { - [$insql, $inparams] = $DB->get_in_or_equal($userchunk); - $inparams[] = $timedout; - - $sql = "SELECT DISTINCT(userid) - FROM {sessions} - WHERE userid $insql - AND timemodified >= ?"; - $users = $DB->get_fieldset_sql($sql, $inparams); - $loggedinusers = array_merge($loggedinusers, $users); - } - - $loggedoutusers = array_diff($userids, $loggedinusers); - - $loggedin = count($loggedinusers); - $loggedout = count($loggedoutusers); - - $usercounts = new \stdClass(); - $usercounts->loggedin = $loggedin; - $usercounts->loggedout = $loggedout; - $usercounts->loggedinusers = $loggedinusers; - $usercounts->loggedoutusers = $loggedoutusers; - - return $usercounts; - } - - /** - * Get count of in porgress and finished attempts for a quiz. - * - * @param int $quizid The id of the quiz to get the counts for. - * @return \stdClass $attemptcounts The found counts. - */ - private function get_quiz_attempts(int $quizid): \stdClass { - global $DB; - - $inprogress = 0; - $finished = 0; - $inprogressusers = []; - $finishedusers = []; - - $sql = 'SELECT userid, state - FROM {quiz_attempts} qa - JOIN ( - SELECT MAX(id) id - FROM {quiz_attempts} - WHERE quiz = ? - GROUP BY userid) - AS qb - ON qa.id = qb.id'; - - $params = [$quizid]; - - $usersattempts = $DB->get_records_sql($sql, $params); - - foreach ($usersattempts as $usersattempt) { - if ($usersattempt->state == 'inprogress' || $usersattempt->state == 'overdue') { - $inprogress++; - $inprogressusers[] = $usersattempt->userid; - } else if ($usersattempt->state == 'finished' || $usersattempt->state == 'abandoned') { - $finished++; - $finishedusers[] = $usersattempt->userid; - } - } - - $attemptcounts = new \stdClass(); - $attemptcounts->inprogress = $inprogress; - $attemptcounts->finished = $finished; - $attemptcounts->inprogressusers = $inprogressusers; - $attemptcounts->finishedusers = $finishedusers; - - return $attemptcounts; - } - - /** - * Process and store user tracking information for a quiz. - * - * @param int $now Timestamp to use for reference for time. - * @return int $count Count of processed quizzes - */ - public function process_quiz_tracking(int $now): int { - global $DB; - - $frequency = new frequency(); - $quizzes = $this->get_tracked_quizzes_with_overrides($now); - $quizusersbyquizid = []; - $contextsbyquizid = []; - $count = 0; - - foreach ($quizzes as $quiz) { - $contextid = $this->get_quiz_context($quiz->id)->id; - $quizusersbyquizid[$quiz->id] = array_column($frequency->get_event_users_raw( - $contextid, - 'quiz' - ), 'id'); - - $contextsbyquizid[$quiz->id] = $contextid; - } - - $loggedinusers = $this->get_loggedin_users( - array_unique(array_reduce($quizusersbyquizid, 'array_merge', [])) - ); - - // For each quiz get the list of users who are elligble to do the quiz. - foreach ($quizzes as $quiz) { - $context = $contextsbyquizid[$quiz->id]; - $quizusers = $quizusersbyquizid[$quiz->id]; - $attemptusers = $this->get_quiz_attempts($quiz->id); - $loggedout = 0; - $loggedin = 0; - $inprogress = 0; - $finished = 0; - - foreach ($quizusers as $user) { - if (in_array($user, $attemptusers->finishedusers)) { - $finished++; - continue; - } else if (in_array($user, $attemptusers->inprogressusers)) { - $inprogress++; - continue; - } else if (in_array($user, $loggedinusers->loggedinusers)) { - $loggedin++; - continue; - } else if (in_array($user, $loggedinusers->loggedoutusers)) { - $loggedout++; - continue; - } - } - - $record = new \stdClass(); - $record->assessid = $quiz->id; - $record->notloggedin = $loggedout; - $record->loggedin = $loggedin; - $record->inprogress = $inprogress; - $record->finished = $finished; - $record->timecreated = time(); - - $DB->insert_record('local_assessfreq_trend', $record); - $count++; - } - - return $count; - } - - /** - * Given a quiz ID get its tracking information. - * - * @param int $quizid The ID of the quiz. - * @return array $tracking Tracking reocrds for the quiz. - */ - public function get_quiz_tracking(int $quizid): array { - global $DB; - - $tracking = $DB->get_records('local_assessfreq_trend', ['assessid' => $quizid], 'timecreated ASC'); - - return $tracking; - } - - /** - * Given an array of quizzes, filter based on a provided search string and apply pagination. - * - * @param array $quizzes Array of quizzes to search. - * @param string $search The string to search by. - * @param int $page The page number of results. - * @param int $pagesize The page size for results. - * @return array $result Array containing list of filtered quizzes and total of how many quizzes matched the filter. - */ - public function filter_quizzes(array $quizzes, string $search, int $page, int $pagesize): array { - $filtered = []; - $searchfields = ['name', 'coursefullname']; - $offset = $page * $pagesize; - $offsetcount = 0; - $recordcount = 0; - - foreach ($quizzes as $id => $quiz) { - $searchcount = 0; - if ($search != '') { - $searchcount = -1; - foreach ($searchfields as $searchfield) { - if (stripos($quiz->{$searchfield}, $search) !== false) { - $searchcount++; - } - } - } - - if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { - $filtered[$id] = $quiz; - } - - if ($searchcount > -1 && $offsetcount >= $offset) { - $recordcount++; - } - - if ($searchcount > -1) { - $offsetcount++; - } - } - - $result = [$filtered, $offsetcount]; - - return $result; - } -} diff --git a/classes/report_base.php b/classes/report_base.php new file mode 100644 index 00000000..c7327818 --- /dev/null +++ b/classes/report_base.php @@ -0,0 +1,103 @@ +. + +/** + * Base report class. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq; + +/** + * Abstract class that each report subplugin primary class will extend from to determine consistent factors. + */ +abstract class report_base { + + private static array $instances = []; + + public function __construct() { + $this->get_required_js(); + $this->get_required_css(); + } + + /** + * Get the instance of the report class. + * + * @return report_base + */ + public static function get_instance() : report_base { + $class = static::class; + if (!isset(self::$instances[$class])) { + self::$instances[$class] = new static(); + } + + return self::$instances[$class]; + } + + /** + * Return the name of the tab being rendered. + * @return string + */ + abstract public function get_name() : string; + + /** + * Return the weight of the tab which is used to determine the loading order with the highest first. + * @return int + */ + abstract public function get_tab_weight() : int; + + /** + * Get the contents of the page as a string of HTML (template). + * + * @return object + */ + + abstract public function get_contents() : string; + + /** + * Get the anchor link to use for the tabs. + * + * @return string + */ + abstract public function get_tablink() : string; + + /** + * Check if the report is visible to the user. + * + * @return bool + */ + public function has_access() : bool { + return false; + } + + /** + * Set up the required JS in the global $PAGE object. + * @return void + */ + protected function get_required_js() { + } + + /** + * Set up the required CSS in the global $PAGE object. + * @return void + */ + protected function get_required_css() { + } +} diff --git a/classes/source_base.php b/classes/source_base.php new file mode 100644 index 00000000..6bfdccf5 --- /dev/null +++ b/classes/source_base.php @@ -0,0 +1,197 @@ +. + +/** + * Base source class. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq; + +/** + * Abstract class that each source subplugin primary class will extend from to determine consistent factors. + */ +abstract class source_base { + + private static array $instances = []; + + /** + * Static array cache multiple calls to the same functions. + * + * @var array + */ + private static array $cache = []; + + public function __construct() { + $this->get_required_js(); + $this->get_required_css(); + } + + /** + * Get the instance of the source class. + * + * @return source_base + */ + public static function get_instance() : source_base { + $class = static::class; + if (!isset(self::$instances[$class])) { + self::$instances[$class] = new static(); + } + + return self::$instances[$class]; + } + + /** + * Return the name of the module the source refers to. + * @return string + */ + abstract public function get_module() : string; + + /** + * Return the module table. By default, this is the module name, however some mods use a different table. + * @return string + */ + public function get_module_table() : string { + return $this->get_module(); + } + + /** + * Return the timelimit field used in the module table. + * @return string + */ + public function get_timelimit_field() : string { + return ''; + } + + /** + * Return the available/timeopen field used in the module table. + * @return string + */ + public function get_open_field() : string { + return ''; + } + + /** + * Return the duedate/timeclose field used in the module table. + * @return string + */ + public function get_close_field() : string { + return ''; + } + + /** + * Return the capability map for the module that users must have before the activity applies to them. + * @return array + */ + public function get_user_capabilities() : array { + return []; + } + + /** + * Return the name of the source being rendered. + * @return string + */ + abstract public function get_name() : string; + + /** + * Set up the required JS in the global $PAGE object. + * @return void + */ + protected function get_required_js() { + } + + /** + * Set up the required CSS in the global $PAGE object. + * @return void + */ + protected function get_required_css() { + } + + /** + * Given an assess ID and module get its tracking information. + * + * @param int $assessid The ID of the assessment. + * @param bool $limited If limited, only return a subset of data. Otherwise reports can try and render thousands of data points. + * @return array $tracking Tracking reocrds for the quiz. + */ + protected function get_tracking(int $assessid, bool $limited = false) : array { + global $DB; + + $module = $this->get_module(); + + $cachekey = "$assessid-$module"; + + if (isset(self::$cache[$cachekey])) { + return self::$cache[$cachekey]; + } + + $trendcount = get_config('assessfreqreport_activity_dashboard', 'trendcount'); + $trendlimit = get_config('assessfreqreport_activity_dashboard', 'trendlimit'); + $return = []; + + $trends = $DB->get_records( + 'local_assessfreq_trend', + ['assessid' => $assessid, 'module' => $module], + 'id DESC', + '*', + 0, + $trendlimit + ); + if (!$limited) { + return $trends; + } + $modulus = round(count($trends) / $trendcount); + $i = 0; + if (count($trends) < $trendcount) { + return $trends; + } + foreach ($trends as $trend) { + if ($i % $modulus == 0) { + $return[] = $trend; + } + $i++; + } + + self::$cache[$cachekey] = $return; + + return $return; + } + + /** + * Given an assess ID and module get its most recent tracking information. + * + * @param int $assessid The ID of the assessment. + * @return mixed $tracking Tracking record. + */ + protected function get_recent_tracking(int $assessid) { + global $DB; + + return $DB->get_record_sql(" + SELECT * + FROM {local_assessfreq_trend} + WHERE assessid = ? + AND module = ? + ORDER BY id DESC + LIMIT 1 + ", + [$assessid, $this->get_module()] + ); + } +} diff --git a/classes/task/data_process.php b/classes/task/data_process.php index 73eb05ab..0ac5fc71 100644 --- a/classes/task/data_process.php +++ b/classes/task/data_process.php @@ -21,9 +21,14 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace local_assessfreq\task; +use context_system; +use core\task\manager; use core\task\scheduled_task; +use local_assessfreq\event\event_processed; +use local_assessfreq\frequency; /** * A scheduled task to generate data used in plugin reports. @@ -38,7 +43,7 @@ class data_process extends scheduled_task { * * @return string */ - public function get_name() { + public function get_name() : string { return get_string('task:dataprocess', 'local_assessfreq'); } @@ -49,11 +54,11 @@ public function get_name() { public function execute() { mtrace('local_assessfreq: Processing event data'); $now = time(); - $frequency = new \local_assessfreq\frequency(); - $context = \context_system::instance(); + $frequency = new frequency(); + $context = context_system::instance(); // Only run scheduled task if there is not an ad-hoc task pending or processing historic data. - $adhoctask = \core\task\manager::get_adhoc_tasks(\local_assessfreq\task\history_process::class); + $adhoctask = manager::get_adhoc_tasks(history_process::class); if (!empty($adhoctask)) { mtrace('local_assessfreq: Stopping early historic processing task pending'); return; @@ -62,9 +67,9 @@ public function execute() { // Due dates may have changed since we last ran report. So delete all events in DB later than now and replace them. mtrace('local_assessfreq: Deleting old event data'); $actionstart = time(); - $frequency->delete_events($now); // Delete event records greaer than now. + $frequency->delete_events($now); // Delete event records greater than now. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'delete', 'duration' => $actionduration], ]); @@ -75,7 +80,7 @@ public function execute() { $actionstart = time(); $frequency->process_site_events($now); // Process records in the future. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'site', 'duration' => $actionduration], ]); @@ -86,11 +91,21 @@ public function execute() { $actionstart = time(); $frequency->process_user_events($now); // Process user events. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'user', 'duration' => $actionduration], ]); $event->trigger(); mtrace('local_assessfreq: Processing user events finished in: ' . $actionduration . ' seconds'); + + //mtrace('local_assessfreq: Clearing legacy tracking data'); + //$actionstart = time(); + //$actionduration = time() - $actionstart; + //$event = event_processed::create([ + // 'context' => $context, + // 'other' => ['action' => 'user', 'duration' => $actionduration], + //]); + //$event->trigger(); + //mtrace('local_assessfreq: Processing user events finished in: ' . $actionduration . ' seconds'); } } diff --git a/classes/task/history_process.php b/classes/task/history_process.php index 5cbcb513..7e2b797f 100644 --- a/classes/task/history_process.php +++ b/classes/task/history_process.php @@ -23,7 +23,12 @@ */ namespace local_assessfreq\task; +use context_system; use core\task\adhoc_task; +use core\task\manager; +use local_assessfreq\event\event_processed; +use local_assessfreq\frequency; +use moodle_exception; /** * Adhoc task to process historical data used in plugin. @@ -43,18 +48,18 @@ public function execute() { // Only run if scheduled task is not running. // Throw an error if it is and this task will be retried after a delay. // The scheduled task won't start while this job is pending. - $schedtask = \core\task\manager::get_scheduled_task(\local_assessfreq\task\data_process::class); + $schedtask = manager::get_scheduled_task(data_process::class); if ($schedtask->get_lock()) { - throw new \moodle_exception('local_assessfreq_scheduled_task_running'); + throw new moodle_exception('local_assessfreq_scheduled_task_running'); } - $frequency = new \local_assessfreq\frequency(); - $context = \context_system::instance(); + $frequency = new frequency(); + $context = context_system::instance(); $actionstart = time(); $frequency->delete_events(0); // Delete ALL event records. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'delete', 'duration' => $actionduration], ]); @@ -65,7 +70,7 @@ public function execute() { $actionstart = time(); $frequency->process_site_events(1); // Process ALL records. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'site', 'duration' => $actionduration], ]); @@ -76,7 +81,7 @@ public function execute() { $actionstart = time(); $frequency->process_user_events(1); // Process ALL user events. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'user', 'duration' => $actionduration], ]); diff --git a/classes/task/quiz_tracking.php b/classes/task/quiz_tracking.php deleted file mode 100644 index 0c7a21f3..00000000 --- a/classes/task/quiz_tracking.php +++ /dev/null @@ -1,59 +0,0 @@ -. - -/** - * A scheduled task to track the process of quizzes in the system. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace local_assessfreq\task; - -use core\task\scheduled_task; - -/** - * A scheduled task to track the process of quizzes in the system. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_tracking extends scheduled_task { - /** - * Get a descriptive name for this task (shown to admins). - * - * @return string - */ - public function get_name() { - return get_string('task:quiztracking', 'local_assessfreq'); - } - - /** - * Do the job. - * Throw exceptions on errors (the job will be retried). - */ - public function execute() { - mtrace('local_assessfreq: Processing quiz trcking'); - $quiz = new \local_assessfreq\quiz(); - - $actionstart = time(); - $quiz->process_quiz_tracking($actionstart); // Process user events. - $actionduration = time() - $actionstart; - - mtrace('local_assessfreq: Processing quiz tracking finished in: ' . $actionduration . ' seconds'); - } -} diff --git a/classes/utils.php b/classes/utils.php index 11d096ae..461f072a 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -70,7 +70,7 @@ public static function sort(array $inputarray, string $sorton, string $direction /** * Sort an array of arrays/objects by multiple values. * - * @param array $inputarray Array of quizzes to sort. + * @param array $inputarray Array of activities to sort. * @param array $sorton Associative array to sort by in the format field => direction. * @return array $inputarray the sorted array. */ diff --git a/dashboard_assessment.php b/dashboard_assessment.php deleted file mode 100644 index 7c211437..00000000 --- a/dashboard_assessment.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -/** - * Assessment dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_assessment.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_assessment', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:assessment', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_assessment', 'init', [$context->id]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_assessment($baseurl); diff --git a/dashboard_quiz.php b/dashboard_quiz.php deleted file mode 100644 index 66a62d2a..00000000 --- a/dashboard_quiz.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -/** - * Quiz dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$quizid = optional_param('id', 0, PARAM_INT); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_quiz.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_quiz', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:quiz', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_quiz', 'init', [$context->id, $quizid]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_quiz($baseurl); diff --git a/dashboard_quiz_inprogress.php b/dashboard_quiz_inprogress.php deleted file mode 100644 index 18cdacd4..00000000 --- a/dashboard_quiz_inprogress.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -/** - * Quiz dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_quiz_inprogress.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_quiz', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:quiz_inprogress', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_quiz_inprogress', 'init', [$context->id]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_quiz_inprogress($baseurl); diff --git a/db/access.php b/db/access.php new file mode 100644 index 00000000..2359261f --- /dev/null +++ b/db/access.php @@ -0,0 +1,34 @@ +. + +/** + * Access file. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'local/assessfreq:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [], + ], +]; diff --git a/db/install.php b/db/install.php index a81c96ab..2ccdcc66 100644 --- a/db/install.php +++ b/db/install.php @@ -22,13 +22,16 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\task\manager; +use local_assessfreq\task\history_process; + /** * Generate ad-hoc task on install. */ function xmldb_local_assessfreq_install() { if (!PHPUNIT_TEST) { // I hate this anti-pattern. // Create an adhoc task that will process all historical event data. - $task = new \local_assessfreq\task\history_process(); - \core\task\manager::queue_adhoc_task($task, true); + $task = new history_process(); + manager::queue_adhoc_task($task, true); } } diff --git a/db/install.xml b/db/install.xml index ca69cca6..a660c3ae 100644 --- a/db/install.xml +++ b/db/install.xml @@ -69,6 +69,7 @@ + @@ -81,6 +82,7 @@ +
diff --git a/db/services.php b/db/services.php index afac4f83..ce50c597 100644 --- a/db/services.php +++ b/db/services.php @@ -26,40 +26,6 @@ // Define the web service functions to install. $functions = [ - 'local_assessfreq_get_frequency' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_frequency', - 'classpath' => '', - 'description' => 'Returns event frequency map.', - 'type' => 'read', - 'ajax' => true, - ], - 'local_assessfreq_get_heat_colors' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_heat_colors', - 'classpath' => '', - 'description' => 'Returns event heat map colors.', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_process_modules' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_process_modules', - 'classpath' => '', - 'description' => 'Returns modules we are processing .', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_day_events' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_day_events', - 'classpath' => '', - 'description' => 'Gets day event info for use in heatmap.', - 'type' => 'read', - 'ajax' => true, - ], 'local_assessfreq_get_courses' => [ 'classname' => 'local_assessfreq_external', 'methodname' => 'get_courses', @@ -68,19 +34,11 @@ 'type' => 'read', 'ajax' => true, ], - 'local_assessfreq_get_quizzes' => [ + 'local_assessfreq_get_activities' => [ 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_quizzes', + 'methodname' => 'get_activities', 'classpath' => '', - 'description' => 'Gets quizzes.', - 'type' => 'read', - 'ajax' => true, - ], - 'local_assessfreq_get_quiz_data' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_quiz_data', - 'classpath' => '', - 'description' => 'Gets quiz data.', + 'description' => 'Gets activities.', 'type' => 'read', 'ajax' => true, ], @@ -100,21 +58,4 @@ 'type' => 'write', 'ajax' => true, ], - 'local_assessfreq_get_system_timezone' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_system_timezone', - 'classpath' => '', - 'description' => 'Returns system (not user) timezone.', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_inprogress_counts' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_inprogress_counts', - 'classpath' => '', - 'description' => 'Get counts for inprogress assessments.', - 'type' => 'read', - 'ajax' => true, - ], ]; diff --git a/db/subplugins.json b/db/subplugins.json new file mode 100644 index 00000000..e37298eb --- /dev/null +++ b/db/subplugins.json @@ -0,0 +1,6 @@ +{ + "plugintypes": { + "assessfreqreport": "local/assessfreq/report", + "assessfreqsource": "local/assessfreq/source" + } +} \ No newline at end of file diff --git a/db/tasks.php b/db/tasks.php index 6e9893c3..3648ed85 100644 --- a/db/tasks.php +++ b/db/tasks.php @@ -36,14 +36,5 @@ 'day' => '*', 'dayofweek' => '*', 'month' => '*', - ], - [ - 'classname' => 'local_assessfreq\task\quiz_tracking', - 'blocking' => 0, - 'minute' => '*', - 'hour' => '*', - 'day' => '*', - 'dayofweek' => '*', - 'month' => '*', - ], + ] ]; diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 00000000..daa35493 --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,57 @@ +. + +/** + * Upgrade file. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Function to upgrade local_assessfreq. + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_local_assessfreq_upgrade($oldversion) { + global $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2024040302) { + + $table = new xmldb_table('local_assessfreq_trend'); + /* + * Previously we only used this table for quiz, so all existing modules will be quiz modules, hence the default. + */ + $field = new xmldb_field('module', XMLDB_TYPE_CHAR, '20', true, true, null, 'quiz'); + $index = new xmldb_index('module', XMLDB_INDEX_NOTUNIQUE, ['assessid', 'module']); + + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + upgrade_plugin_savepoint(true, 2024040302, 'local', 'assessfreq'); + } + + return true; +} diff --git a/history.php b/history.php index d5f241a4..cb3cd9fb 100644 --- a/history.php +++ b/history.php @@ -34,22 +34,22 @@ // Build the page output. echo $OUTPUT->header(); -echo $OUTPUT->heading(get_string('clearhistory', 'local_assessfreq')); +echo $OUTPUT->heading(get_string('settings:clearhistory', 'local_assessfreq')); // Page content. (This feels like the lazy way to do things). $url = new \moodle_url('/local/assessfreq/history.php', ['action' => 'deleteall']); if ($action === null) { echo $OUTPUT->box_start(); - echo $OUTPUT->container(get_string('reprocessall_desc', 'local_assessfreq')); - echo $OUTPUT->single_button($url, get_string('reprocessall', 'local_assessfreq'), 'get'); + echo $OUTPUT->container(get_string('history:reprocessall_desc', 'local_assessfreq')); + echo $OUTPUT->single_button($url, get_string('history:reprocessall', 'local_assessfreq'), 'get'); echo $OUTPUT->box_end(); } else if ($action == 'deleteall') { $actionurl = new moodle_url('/local/assessfreq/history.php', ['action' => 'confirmed']); $cancelurl = new moodle_url('/local/assessfreq/history.php'); echo $OUTPUT->confirm( - get_string('confirmreprocess', 'local_assessfreq'), - new single_button($actionurl, get_string('continue'), 'post', single_button::BUTTON_SECONDARY), + get_string('history:confirmreprocess', 'local_assessfreq'), + new single_button($actionurl, get_string('continue'), 'post', true), new single_button($cancelurl, get_string('cancel'), 'get') ); } else if ($action == 'confirmed') { @@ -57,8 +57,8 @@ $task = new \local_assessfreq\task\history_process(); \core\task\manager::queue_adhoc_task($task, true); echo $OUTPUT->box_start(); - echo $OUTPUT->container(get_string('reprocessall_desc', 'local_assessfreq')); - echo $OUTPUT->single_button($url, get_string('reprocessall', 'local_assessfreq'), 'get'); + echo $OUTPUT->container(get_string('history:reprocessall_desc', 'local_assessfreq')); + echo $OUTPUT->single_button($url, get_string('history:reprocessall', 'local_assessfreq'), 'get'); echo $OUTPUT->box_end(); } diff --git a/index.php b/index.php new file mode 100644 index 00000000..1cc018e3 --- /dev/null +++ b/index.php @@ -0,0 +1,64 @@ +. + +/** + * Main landing page for the reports + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__, 3) . '/config.php'); + +require_login(); + +require_once('lib.php'); + +// Capability requirements. +$context = context_system::instance(); +$course = get_course(SITEID); + +// If we have a course selected, update the PAGE object accordinging. +if ($courseid = optional_param('courseid', 0, PARAM_INT)) { + // If we've been given the side id redirect without the param. + if ($courseid == SITEID) { + redirect('/local/assessfreq/'); + } + $context = context_course::instance($courseid); + $PAGE->set_pagelayout('incourse'); + $course = get_course($courseid); +} + +// Capability check. +require_capability('local/assessfreq:view', $context); + +$PAGE->set_url('/local/assessfreq'); +$PAGE->set_context($context); +// Set the course to use in subsequent checks. +$PAGE->set_course($course); + +if ($course->id != SITEID) { + $PAGE->set_heading($course->fullname); +} +$PAGE->set_title(get_string('pluginname', 'local_assessfreq')); + +$output = $PAGE->get_renderer('local_assessfreq'); +$PAGE->requires->js_call_amd('local_assessfreq/dashboard', 'init'); + +/* @var $output local_assessfreq\output\renderer */ +$output->render_reports(); diff --git a/lang/en/local_assessfreq.php b/lang/en/local_assessfreq.php index 4ada491d..76ef1426 100644 --- a/lang/en/local_assessfreq.php +++ b/lang/en/local_assessfreq.php @@ -15,207 +15,50 @@ // along with Moodle. If not, see . /** - * Plugin strings are defined here. + * Lang file. * - * @package local_assessfreq - * @category string - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -defined('MOODLE_INTERNAL') || die(); +$string['pluginname'] = 'Assessment Frequency Report'; +$string['subplugintype_assessfreqreport_plural'] = 'Assessment Frequency Reports'; +$string['subplugintype_assessfreqsource_plural'] = 'Assessment Frequency Sources'; -$string['pluginname'] = 'Assessment Frequency'; -$string['title'] = 'Assessment Frequency'; +$string['privacy:metadata'] = 'The assessment frequency reports only display data'; + +$string['assessfreq:view'] = 'Ability to load the inital view. Report subplugins will also need to be allowed.'; -$string['abandoned'] = 'Abandoned'; -$string['activity'] = 'Activity'; -$string['actions'] = 'Actions'; -$string['assessbyactivity'] = 'Assessments by activity'; -$string['assessbymonth'] = 'Assessments due by month'; -$string['assessbymonthstudents'] = 'Students with assessments due by month'; -$string['assessheatmap'] = 'Assessment heatmap for year:'; -$string['assessoverview'] = 'Assessment overviews for year:'; -$string['cachedef_eventsdueactivity'] = 'Events due by activity cache'; -$string['cachedef_eventsduemonth'] = 'Events due by month cache'; -$string['cachedef_eventusers'] = 'Users for month cache'; -$string['cachedef_monthlyuser'] = 'User events due by month cache'; -$string['cachedef_courseevents'] = 'Assessment frequency course event cache'; -$string['cachedef_siteevents'] = 'Assessment frequency site event cache'; -$string['cachedef_userevents'] = 'Assessment frequency user event cache'; -$string['cachedef_usereventsallfrequencyarray'] = 'Assessment frequency all user event cache'; -$string['cachedef_yearevents'] = 'Years that have events'; -$string['clearhistory'] = 'Clear history'; -$string['close'] = 'Close'; -$string['closeapply'] = 'Close and apply'; -$string['confirmreprocess'] = 'Delete ALL history and reprocess?'; -$string['course'] = 'Course'; -$string['courseasc'] = 'Course Asc'; -$string['coursedesc'] = 'Course Desc'; -$string['dashboard'] = 'View activity dashboard'; -$string['dashboard:assessment'] = 'Assessment dashboard'; -$string['dashboard:quiz'] = 'Quiz dashboard'; -$string['dashboard:quiz_inprogress'] = 'Quizzes in progress dashboard'; -$string['dashboard:quiztitle'] = '{$a->quiz} - {$a->course} - Dashboard'; -$string['duedate'] = 'Due date'; -$string['eventeventprocessed'] = 'event_processed'; -$string['eventeven_processed_desc'] = 'local assessfreq task event processing'; -$string['entercourse'] = 'Enter course name'; -$string['entersearch'] = 'Enter search text'; -$string['entersearchquiz'] = 'Search by quiz or course name'; -$string['findcourse'] = 'Find course'; -$string['finished'] = 'Finished'; -$string['hours0'] = 'Now'; -$string['hours1'] = '1 Hour'; -$string['hours4'] = '4 Hours'; -$string['hours8'] = '8 Hours'; -$string['hoursahead'] = 'Hours ahead'; -$string['hoursbehind'] = 'Hours behind'; -$string['inprogress'] = 'In progress'; -$string['inprogressdatetime'] = '%H:00'; -$string['inprogressparticpants'] = 'Participants in progress: {$a}'; -$string['inprogressquiz'] = 'Quizzes in progress: {$a}'; -$string['loading'] = 'Loading...'; -$string['loadingquiz'] = 'Loading quizzes'; -$string['loadingquiztitle'] = 'Loading quiz'; -$string['loggedin'] = 'Logged in'; -$string['na'] = 'N/A'; -$string['minuteone'] = '1 Minute'; -$string['minutetwo'] = '2 Minutes'; -$string['minutefive'] = '5 Minutes'; -$string['minuteten'] = '10 Minutes'; -$string['nocourse'] = 'No course selected'; -$string['nodata'] = 'No data found'; -$string['noquiz'] = 'No quiz selected...'; -$string['noquizselected'] = 'No quiz selected. Select quiz or cancel'; -$string['notloggedin'] = 'Not logged in'; -$string['numberassessments'] = 'By number of assessments'; -$string['numberevents'] = 'Event Count'; -$string['numberstudents'] = 'By number of students with assessments'; -$string['open'] = 'Open'; -$string['overdue'] = 'Overdue'; -$string['overrides'] = 'Overrides'; -$string['participantsummary'] = 'Participant summary'; -$string['participanttrend'] = 'Participant trend'; -$string['participants'] = 'Participants'; -$string['period'] = 'Period'; -$string['privacy:metadata:local_assessfreq'] = 'Data relating users for the local assessfreq plugin'; -$string['privacy:metadata:local_assessfreq_user'] = 'Data relating users with assessment events'; -$string['privacy:metadata:local_assessfreq_user:id'] = 'Record ID'; -$string['privacy:metadata:local_assessfreq_user:userid'] = 'The ID of the user that is effected by the assessment event'; -$string['privacy:metadata:local_assessfreq_user:eventid'] = 'The ID that relates to the assessment event'; -$string['privacy:metadata:local_assessfreq_conf_user'] = 'Data relating users with assessment conflicts'; -$string['privacy:metadata:local_assessfreq_conf_user:id'] = 'Record ID'; -$string['privacy:metadata:local_assessfreq_conf_user:userid'] = 'The ID of the user that is effected by the assessment conflict'; -$string['privacy:metadata:local_assessfreq_conf_user:conflictid'] = 'The ID that relates to the assessment conflict'; -$string['pluginsettings'] = 'Plugin settings'; -$string['quiz'] = 'Quiz'; -$string['quizasc'] = 'Quiz Asc'; -$string['quizdesc'] = 'Quiz Desc'; -$string['quizdetails'] = 'Quiz details'; -$string['quiztparticipantsoverride'] = 'Participants with an override:'; -$string['quiztquestionnumber'] = 'Questions in quiz:'; -$string['quizquestiontypes'] = 'Question types in quiz:'; -$string['quiztimeclose'] = 'Close time'; -$string['quiztimeearlyopen'] = 'First participant starts:'; -$string['quiztimefinish'] = 'Finish'; -$string['quiztimelateclose'] = 'Last participant finishes:'; -$string['quiztimelimit'] = 'Time limit'; -$string['quiztimeopen'] = 'Open time'; -$string['quiztimestart'] = 'Start'; -$string['quizparticipants'] = 'Participant count:'; -$string['quizresults'] = 'Quiz results:'; -$string['quizresultsview'] = 'View quiz results'; -$string['quizzes'] = 'Quizzes'; -$string['quizzesinprogress'] = 'Quizzes in progress'; -$string['reports'] = 'Assessment reports'; -$string['reset'] = 'Clear search'; -$string['reprocessall'] = 'Reprocess all events'; -$string['reprocessall_desc'] = 'This will delete ALL existing event records from the database and start a process to reprocess all events. This will happen in the background.'; -$string['rows5'] = '5 Rows'; -$string['rows10'] = '10 Rows'; -$string['rows20'] = '20 Rows'; -$string['rows50'] = '50 Rows'; -$string['rows100'] = '100 Rows'; -$string['scale'] = 'Scale:'; -$string['schedule'] = 'Daily schedule'; -$string['selectassessment'] = 'Select assessment type'; -$string['selectcourse'] = 'Select course first'; -$string['selectquiz'] = 'Select quiz'; -$string['searchquiz'] = 'Search for quiz'; -$string['searchquizform'] = 'Search and select the quiz to display on the dashboard'; -$string['selectmetric'] = 'Select metric'; -$string['selectyear'] = 'Select year'; -$string['settings:chartheading'] = 'Chart colors'; -$string['settings:chartheading_desc'] = 'These settings allow you to configure the colors used in the charts and graphs'; -$string['settings:finishedcolor'] = 'Finished color'; -$string['settings:finishedcolor_desc'] = 'Select color to display for finished users in charts'; -$string['settings:heat1'] = 'First heat color'; -$string['settings:heat1_desc'] = 'Select color for the first level of the frequency heatmap'; -$string['settings:heat1'] = 'First heat color'; -$string['settings:heat1_desc'] = 'Select color for the first level of the frequency heatmap'; -$string['settings:heat2'] = 'Second heat color'; -$string['settings:heat2_desc'] = 'Select color for the second level of the frequency heatmap'; -$string['settings:heat3'] = 'Third heat color'; -$string['settings:heat3_desc'] = 'Select color for the third level of the frequency heatmap'; -$string['settings:heat4'] = 'Fourth heat color'; -$string['settings:heat4_desc'] = 'Select color for the fourth level of the frequency heatmap'; -$string['settings:heat5'] = 'Fifth heat color'; -$string['settings:heat5_desc'] = 'Select color for the fifth level of the frequency heatmap'; -$string['settings:heat6'] = 'Sixth heat color'; -$string['settings:heat6_desc'] = 'Select color for the sixth level of the frequency heatmap'; -$string['settings:heatheading'] = 'Heatmap colors'; -$string['settings:heatheading_desc'] = 'These settings allow you to configure the colors used in the heatmap'; -$string['settings:hiddencourses'] = 'Include hidden courses'; -$string['settings:hiddencourses_desc'] = 'Included hidden courses in the heatmap calculations'; -$string['settings:inprogresscolor'] = 'In progress color'; -$string['settings:inprogresscolor_desc'] = 'Select color to display for in progress users in charts'; -$string['settings:loggedincolor'] = 'Logged in color'; -$string['settings:loggedincolor_desc'] = 'Select color to display for logged in users in charts'; -$string['settings:modules'] = 'Enabled modules'; -$string['settings:modules_desc'] = 'Select the modules that you want to appear in the heatmap calculations'; -$string['settings:moduleheading'] = 'Modules and courses'; -$string['settings:moduleheading_desc'] = 'These settings control how modules and courses are used in processing'; -$string['settings:notloggedincolor'] = 'Not logged in color'; -$string['settings:notloggedincolor_desc'] = 'Select color to display for not logged in users in charts'; -$string['settings:disabledmodules'] = 'Include disabled modules'; -$string['settings:disabledmodules_desc'] = 'Include modules that have been disabled in calculations'; -$string['showrows'] = 'Show rows'; -$string['sorttable'] = 'Sort table'; -$string['status'] = 'Status'; -$string['student_search'] = 'Student Search'; -$string['students'] = 'Students'; -$string['studenttable'] = 'Student attempt status'; -$string['submitoverridefail'] = 'Ajax override form submission failed'; -$string['systemdisabled'] = ' (module disabled)'; $string['task:dataprocess'] = 'Data collection task'; $string['task:quiztracking'] = 'Quiz tracking task'; -$string['time'] = 'Time'; -$string['timelimit'] = 'Time limit (minutes)'; -$string['timeendasc'] = 'End time Asc'; -$string['timeenddesc'] = 'End time Desc'; -$string['timestartasc'] = 'Start time Asc'; -$string['timestartdesc'] = 'Start time Desc'; -$string['title'] = 'Title'; -$string['toggleoverview'] = 'Toggle overview graphs'; -$string['trenddatetime'] = '%H:%M, %d-%m-%y'; -$string['userattempt'] = 'View user attempt'; -$string['upcommingquizes'] = 'Upcomming quizzes starting'; -$string['uploadpending'] = 'Upload pending'; -$string['userlogs'] = 'View user logs'; -$string['useroverride'] = 'Add user override'; -$string['userprofile'] = 'View user profile'; -$string['url'] = 'URL'; -$string['zoom'] = 'Zoom in'; -$string['jan'] = 'January'; -$string['feb'] = 'February'; -$string['mar'] = 'March'; -$string['apr'] = 'April'; -$string['may'] = 'May'; -$string['jun'] = 'June'; -$string['jul'] = 'July'; -$string['aug'] = 'August'; -$string['sep'] = 'September'; -$string['oct'] = 'October'; -$string['nov'] = 'November'; -$string['dec'] = 'December'; + +$string['courseselect'] = 'Select course...'; +$string['noreports'] = 'No reports have been configured for you. +If you believe this is an error please contact your site administrator.'; + +$string['history:confirmreprocess'] = 'Delete ALL history and reprocess?'; +$string['history:reprocessall'] = 'Reprocess all events'; +$string['history:reprocessall_desc'] = 'This will delete ALL existing event records from the database and start a process to reprocess all events. This will happen in the background.'; + +$string['settings:clearhistory'] = 'Assessment Frequency Clear History'; +$string['settings:head'] = 'Assessment Frequency Reports'; +$string['settings:local_assessfreq'] = 'Global Settings'; +$string['settings:start_month'] = 'Start month'; +$string['settings:start_month_desc'] = 'Specify the month that the heatmap year should start from.'; +$string['settings:hiddencourses'] = 'Include hidden courses'; +$string['settings:hiddencourses_desc'] = 'Included hidden courses in the reports'; +$string['settings:enablesource'] = 'Enable: {$a}'; +$string['settings:enablesource_help'] = 'Check this control to allow the source to be used for the dashboard.'; +$string['settings:enablereport'] = 'Enable: {$a}'; +$string['settings:enablereport_help'] = 'Check this control to allow the report to be used for the dashboard.'; + +$string['filter:entersearch'] = 'Enter search'; +$string['filter:reset'] = 'Reset'; +$string['filter:showrows'] = 'Show rows'; +$string['filter:rows20'] = '20 rows'; +$string['filter:rows50'] = '50 rows'; +$string['filter:rows100'] = '100 rows'; + +$string['modal:useroverride'] = 'User override'; diff --git a/lib.php b/lib.php index 1258ad3a..484ef139 100644 --- a/lib.php +++ b/lib.php @@ -13,6 +13,9 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +use local_assessfreq\frequency; +use local_assessfreq\source_base; +use local_assessfreq\report_base; /** * This page contains callbacks. @@ -23,297 +26,214 @@ */ /** - * Returns the name of the user preferences as well as the details this plugin uses. + * This function extends the navigation with the report link. * - * @return array + * @param navigation_node $navigation The navigation node to extend + * @param stdClass $course The course to object for the report + * @param context $context The context of the course */ -function local_assessfreq_user_preferences() { - - $preferences['local_assessfreq_overview_year_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => date('Y'), - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_heatmap_year_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => date('Y'), - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_heatmap_metric_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 'assess', - 'type' => PARAM_ALPHA, - ]; - - $preferences['local_assessfreq_heatmap_modules_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => '[]', - 'type' => PARAM_RAW, - ]; - - $preferences['local_assessfreq_quiz_refresh_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 60, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_rows_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_rows_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_hoursahead_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 4, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_hoursbehind_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 1, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quizzes_inprogress_table_hoursahead_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 0, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quizzes_inprogress_table_hoursbehind_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 0, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_inprogress_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_inprogress_sort_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 'name_asc', - 'type' => PARAM_ALPHAEXT, - ]; - - return $preferences; +function local_assessfreq_extend_navigation_course(navigation_node $navigation, stdClass $course, context $context) { + if (has_capability('local/assessfreq:view', $context)) { + $url = new moodle_url('/local/assessfreq/', ['courseid' => $course->id]); + $settingsnode = navigation_node::create(get_string('pluginname', 'local_assessfreq'), $url); + $reportnode = $navigation->get('coursereports'); + if (isset($settingsnode) && !empty($reportnode)) { + $reportnode->add_node($settingsnode); + } + } } /** - * Return the HTML for the given chart. + * Get all of the subplugin reports that are enabled and instantiate the class. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @param $ignoreenabled + * @return array */ -function local_assessfreq_output_fragment_get_chart($args): string { - $allowedcalls = [ - 'assess_by_month', - 'assess_by_activity', - 'assess_by_month_student', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); +function get_reports($ignoreenabled = false) : array { + $reports = []; + $pluginmanager = core_plugin_manager::instance(); + foreach ($pluginmanager->get_plugins_of_type('assessfreqreport') as $subplugin) { + /* @var $class report_base */ + if ($subplugin->is_enabled() || $ignoreenabled) { + $class = "assessfreqreport_{$subplugin->name}\\report"; + $report = $class::get_instance(); + if ($report->has_access()) { + $reports[$subplugin->name] = $report; + } + } } - - $assesschart = new $classname(); - $chart = $assesschart->$methodname($data->year); - - $chartdata = json_encode($chart); - return $chartdata; + return $reports; } /** - * Return the HTML for the given chart. + * Get all of the subplugin sources that are enabled and instantiate the class. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @param $ignoreenabled + * @return array */ -function local_assessfreq_output_fragment_get_quiz_chart($args): string { - $allowedcalls = [ - 'participant_summary', - 'participant_trend', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); +function get_sources($ignoreenabled = false, $requiredmethod = '') : array { + $sources = []; + $pluginmanager = core_plugin_manager::instance(); + foreach ($pluginmanager->get_plugins_of_type('assessfreqsource') as $subplugin) { + if ($subplugin->is_enabled() || $ignoreenabled) { + /* @var $class source_base */ + $class = "assessfreqsource_{$subplugin->name}\\source"; + $source = $class::get_instance(); + if (!empty($requiredmethod)) { + if (!method_exists($source, $requiredmethod)) { + continue; + } + } + $sources[$subplugin->name] = $source; + } } - - $assesschart = new $classname(); - $chart = $assesschart->$methodname($data->quiz); - - $chartdata = json_encode($chart); - return $chartdata; + return $sources; } /** - * Return the HTML for the given chart. + * Using the start month defined in config get an ordered year of month names. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @return array */ -function local_assessfreq_output_fragment_get_quiz_inprogress_chart($args): string { - $allowedcalls = [ - 'upcomming_quizzes', - 'all_participants_inprogress', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); - } +function get_months_ordered() : array { - $assesschart = new $classname(); - $now = time(); + $months = []; + $startmonth = get_config('local_assessfreq', 'start_month'); - if ($methodname == 'get_all_participants_inprogress_chart') { - $chart = $assesschart->$methodname($now, $data->hoursahead, $data->hoursbehind); - } else { - $chart = $assesschart->$methodname($now); + for ($i = $startmonth; $i < $startmonth + 12; $i++) { + $month = $i - 12 > 0 ? $i - 12 : $i; + + $date = DateTime::createFromFormat('!m', $month); + $monthname = $date->format('F'); + + $months[$month] = $monthname; } - $chartdata = json_encode($chart); - return $chartdata; + return $months; } /** - * Renders the quiz search form for the modal on the quiz dashboard. + * Get the years that have events with the preferred year active. * - * @param array $args - * @return string $o Form HTML. + * @param $preference + * @return array */ -function local_assessfreq_output_fragment_new_base_form($args): string { +function get_years($preference) : array { - $context = $args['context']; - has_capability('moodle/site:config', $context); + $currentyear = date('Y'); - $mform = new \local_assessfreq\form\quiz_search_form(null, null, 'post', '', ['class' => 'ignoredirty']); + // Get years that have events and load into context. + $frequency = new frequency(); + $yearlist = $frequency->get_years_has_events(); - ob_start(); - $mform->display(); - $o = ob_get_contents(); - ob_end_clean(); + if (empty($yearlist)) { + $yearlist = [$currentyear]; + } - return $o; -} + // Add current year to the selection of years if missing. + if (!in_array($currentyear, $yearlist)) { + $yearlist[] = $currentyear; + } -/** - * Renders the student table on the quiz dashboard screen. - * We update the table via ajax. - * - * @param array $args - * @return string $o Form HTML. - */ -function local_assessfreq_output_fragment_get_student_table($args): string { - global $CFG, $PAGE; + $years = []; - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); + foreach ($yearlist as $year) { + $years[$year] = ['year' => ['val' => $year]]; + } - $baseurl = $CFG->wwwroot . '/local/assessfreq/dashboard_quiz.php'; - $output = $PAGE->get_renderer('local_assessfreq'); + if (!$preference) { + $preference = date('Y'); + } - $o = $output->render_student_table($baseurl, $data->quiz, $context->id, $data->search, $data->page); + $years[$preference]['year']['active'] = true; - return $o; + return array_values($years); } /** - * Renders the student table on the student search screen. - * We update the table via ajax. + * Get the modules to use in data collection. + * This is based on which sources have been enabled. * - * @param array $args - * @return string $o Form HTML. + * @return array $modules The enabled modules. */ -function local_assessfreq_output_fragment_get_student_search_table($args): string { - global $CFG, $PAGE; +function get_modules($preferences, $requiredmethod= '') : array { - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - $search = is_null($data->search) ? '' : $data->search; - $now = time(); - $hoursahead = (int)$data->hoursahead; - $hoursbehind = (int)$data->hoursbehind; + $sources = get_sources(false, $requiredmethod); - $baseurl = $CFG->wwwroot . '/local/assessfreq/student_search.php'; - $output = $PAGE->get_renderer('local_assessfreq'); + // Get modules for filters and load into context. + $modules = []; + $modules['all'] = ['module' => ['val' => 'all', 'name' => get_string('all')]]; - $o = $output->render_student_search_table($baseurl, $context->id, $search, $hoursahead, $hoursbehind, $now, $data->page); + foreach ($sources as $source) { + $modulename = get_string('modulename', $source->get_module()); + $modules[$source->get_module()] = ['module' => ['val' => $source->get_module(), 'name' => $modulename]]; + } - return $o; + if (!$preferences) { + $preferences = ["all"]; + } + + foreach ($preferences as $preference) { + if (isset($modules[$preference])) { + $modules[$preference]['module']['active'] = true; + } + } + + return array_values($modules); } /** - * Renders the quizzes in progress "table" on the quiz dashboard screen. - * We update the table via ajax. - * The table isn't a real table it's a collection of divs. + * Given a list of user ids, check if the user is logged in our not + * and return summary counts of logged in and not logged in users. * - * @param array $args - * @return string $o Form HTML. + * @param array $userids User ids to get logged in status. + * @return stdClass $usercounts Object with coutns of users logged in and not logged in. */ -function local_assessfreq_output_fragment_get_quizzes_inprogress_table($args): string { - global $PAGE; +function get_loggedin_users(array $userids): stdClass { + global $CFG, $DB; - $context = $args['context']; - has_capability('moodle/site:config', $context); + $maxlifetime = $CFG->sessiontimeout; + $timedout = time() - $maxlifetime; + $userchunks = array_chunk($userids, 250); // Break list of users into chunks so we don't exceed DB IN limits. - $data = json_decode($args['data']); - $search = is_null($data->search) ? '' : $data->search; - $sorton = is_null($data->sorton) ? 'name' : $data->sorton; - $direction = is_null($data->direction) ? 'asc' : $data->direction; - $hoursahead = (int)$data->hoursahead; - $hoursbehind = (int)$data->hoursbehind; + $loggedinusers = []; - $output = $PAGE->get_renderer('local_assessfreq'); - $o = $output->render_quizzes_inprogress_table($search, $data->page, $sorton, $direction, $hoursahead, $hoursbehind); + foreach ($userchunks as $userchunk) { + [$insql, $inparams] = $DB->get_in_or_equal($userchunk); + $inparams[] = $timedout; - return $o; + $sql = "SELECT DISTINCT(userid) + FROM {sessions} + WHERE userid $insql + AND timemodified >= ?"; + $users = $DB->get_fieldset_sql($sql, $inparams); + $loggedinusers = array_merge($loggedinusers, $users); + } + + $loggedoutusers = array_diff($userids, $loggedinusers); + + $loggedin = count($loggedinusers); + $loggedout = count($loggedoutusers); + + $usercounts = new stdClass(); + $usercounts->loggedin = $loggedin; + $usercounts->loggedout = $loggedout; + $usercounts->loggedinusers = $loggedinusers; + $usercounts->loggedoutusers = $loggedoutusers; + + return $usercounts; } /** - * Renders the quiz user override form for the modal on the quiz dashboard. + * Renders the user override form for the modal. * * @param array $args * @return string $o Form HTML. */ function local_assessfreq_output_fragment_new_override_form($args): string { - global $DB; + global $DB, $CFG; - $context = $args['context']; - has_capability('mod/quiz:manageoverrides', $context); + $module = $args['activitytype']; $serialiseddata = json_decode($args['jsonformdata'], true); @@ -323,36 +243,17 @@ function local_assessfreq_output_fragment_new_override_form($args): string { parse_str($serialiseddata, $formdata); } - // Get some data needed to generate the form. - $quizid = $args['quizid']; - $quizdata = new \local_assessfreq\quiz(); - $quizcontext = $quizdata->get_quiz_context($quizid); - $quiz = $DB->get_record('quiz', ['id' => $quizid], '*', MUST_EXIST); - - $cm = get_course_and_cm_from_cmid($quizcontext->instanceid, 'quiz')[1]; - - // Check if we have an existing override for this user. - $override = $DB->get_record('quiz_overrides', ['quiz' => $quiz->id, 'userid' => $args['userid']]); - - if ($override) { - $data = clone $override; - } else { - $data = new \stdClass(); - $data->userid = $args['userid']; - } - - $mform = new \local_assessfreq\form\quiz_override_form($cm, $quiz, $quizcontext, $override, $formdata); - $mform->set_data($data); - - if (!empty($serialiseddata)) { - // If we were passed non-empty form data we want the mform to call validation functions and show errors. - $mform->is_validated(); + $sources = get_sources(); + $source = $sources[$module]; + $o = ''; + /* @var $source source_base */ + if (method_exists($source, 'get_override_form')) { + $mform = $source->get_override_form($args['activityid'], $args['context'], $args['userid'], $serialiseddata); + ob_start(); + $mform->display(); + $o = ob_get_contents(); + ob_end_clean(); } - ob_start(); - $mform->display(); - $o = ob_get_contents(); - ob_end_clean(); - return $o; } diff --git a/report/activities_in_progress/amd/build/activities_in_progress.min.js b/report/activities_in_progress/amd/build/activities_in_progress.min.js new file mode 100644 index 00000000..4bbce616 --- /dev/null +++ b/report/activities_in_progress/amd/build/activities_in_progress.min.js @@ -0,0 +1,11 @@ +define("assessfreqreport_activities_in_progress/activities_in_progress",["exports","local_assessfreq/table_handler","local_assessfreq/user_preferences"],(function(_exports,_table_handler,UserPreference){var obj; +/** + * Chart data JS module. + * + * @module assessfreqreport/activities_in_progress + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_table_handler=(obj=_table_handler)&&obj.__esModule?obj:{default:obj},UserPreference=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(UserPreference);_exports.init=context=>{moduleDropdown();let table=new _table_handler.default(0,context,"assessfreqreport-activities-in-progress-table","assessfreqreport_activities_in_progress","get_in_progress_table","assessfreqreport_activities_in_progress_table_rows_preference","assessfreqreport_activities_in_progress_table_sort_preference","assessfreqreport-activities-in-progress-table-search","assessfreqreport-activities-in-progress-table","local_assessfreq_set_table_preference");table.getTable();let tableSearchInputElement=document.getElementById("assessfreqreport-activities-in-progress-table-search"),tableSearchResetElement=document.getElementById("assessfreqreport-activities-in-progress-table-search-reset"),tableSearchRowsElement=document.getElementById("assessfreqreport-activities-in-progress-table-rows"),tableSearchAheadElement=document.getElementById("assessfreqreport-activities-in-progress-hoursahead"),tableSearchBehindElement=document.getElementById("assessfreqreport-activities-in-progress-hoursbehind");tableSearchInputElement.addEventListener("keyup",table.tableSearch),tableSearchInputElement.addEventListener("paste",table.tableSearch),tableSearchResetElement.addEventListener("click",table.tableSearchReset),tableSearchRowsElement.addEventListener("click",table.tableSearchRowSet),tableSearchAheadElement.addEventListener("click",tableSearchAheadSet),tableSearchBehindElement.addEventListener("click",tableSearchBehindSet)};const moduleDropdown=()=>{let links=document.getElementById("local-assessfreq-report-activities-in-progress-filter-type").getElementsByTagName("a"),all=links[0],modules=[];for(let i=0;i{event.preventDefault(),event.stopPropagation();for(let j=0;j{event.preventDefault(),event.stopPropagation();document.getElementById("local-assessfreq-report-activities-in-progress-filter-type-filters").classList.remove("show");for(let i=0;i{event.preventDefault(),event.stopPropagation(),all.classList.remove("active"),event.target.classList.toggle("active")}))}},tableSearchAheadSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("assessfreqreport_activities_in_progress_hoursahead_preference",hours),location.reload()}},tableSearchBehindSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let hours=event.target.dataset.metric;UserPreference.setUserPreference("assessfreqreport_activities_in_progress_hoursbehind_preference",hours),location.reload()}}})); + +//# sourceMappingURL=activities_in_progress.min.js.map \ No newline at end of file diff --git a/report/activities_in_progress/amd/build/activities_in_progress.min.js.map b/report/activities_in_progress/amd/build/activities_in_progress.min.js.map new file mode 100644 index 00000000..e325c844 --- /dev/null +++ b/report/activities_in_progress/amd/build/activities_in_progress.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"activities_in_progress.min.js","sources":["../src/activities_in_progress.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module assessfreqreport/activities_in_progress\n * @package\n * @copyright Simon Thornett \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Init function.\n * @param {Integer} context\n */\nexport const init = (context) => {\n\n // Set up event listener and related actions for module dropdown on heatmp.\n moduleDropdown();\n\n let table = new TableHandler(\n 0,\n context,\n 'assessfreqreport-activities-in-progress-table',\n 'assessfreqreport_activities_in_progress',\n 'get_in_progress_table',\n 'assessfreqreport_activities_in_progress_table_rows_preference',\n 'assessfreqreport_activities_in_progress_table_sort_preference',\n 'assessfreqreport-activities-in-progress-table-search',\n 'assessfreqreport-activities-in-progress-table',\n 'local_assessfreq_set_table_preference'\n );\n\n table.getTable();\n\n let tableSearchInputElement = document.getElementById('assessfreqreport-activities-in-progress-table-search');\n let tableSearchResetElement = document.getElementById('assessfreqreport-activities-in-progress-table-search-reset');\n let tableSearchRowsElement = document.getElementById('assessfreqreport-activities-in-progress-table-rows');\n let tableSearchAheadElement = document.getElementById('assessfreqreport-activities-in-progress-hoursahead');\n let tableSearchBehindElement = document.getElementById('assessfreqreport-activities-in-progress-hoursbehind');\n\n tableSearchInputElement.addEventListener('keyup', table.tableSearch);\n tableSearchInputElement.addEventListener('paste', table.tableSearch);\n tableSearchResetElement.addEventListener('click', table.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', table.tableSearchRowSet);\n tableSearchAheadElement.addEventListener('click', tableSearchAheadSet);\n tableSearchBehindElement.addEventListener('click', tableSearchBehindSet);\n\n};\n\n/**\n * Add the event listeners to the modules in the module select dropdown.\n */\nconst moduleDropdown = () => {\n let links = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type').getElementsByTagName('a');\n let all = links[0];\n let modules = [];\n\n for (let i = 0; i < links.length; i++) {\n let module = links[i].dataset.module;\n\n if (module.toLowerCase() === 'all') {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n // Remove active class from all other links.\n for (let j = 0; j < links.length; j++) {\n links[j].classList.remove('active');\n }\n event.target.classList.toggle('active');\n });\n } else if (module.toLowerCase() === 'close') {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n\n const dropdownmenu = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type-filters');\n dropdownmenu.classList.remove('show');\n\n for (let i = 0; i < links.length; i++) {\n if (links[i].classList.contains('active')) {\n let module = links[i].dataset.module;\n modules.push(module);\n }\n }\n\n // Save selection as a user preference.\n UserPreference.setUserPreference(\n 'assessfreqreport_activities_in_progress_modules_preference',\n JSON.stringify(modules)\n );\n\n // Reload based on selected year.\n location.reload();\n });\n } else {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n\n all.classList.remove('active');\n\n event.target.classList.toggle('active');\n });\n }\n }\n};\n\n/**\n * Process the hours ahead event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursahead_preference', hours);\n // Reload based on selected year.\n location.reload();\n }\n};\n\n/**\n * Process the hours behind event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursbehind_preference', hours);\n // Reload based on selected year.\n location.reload();\n }\n};\n"],"names":["context","moduleDropdown","table","TableHandler","getTable","tableSearchInputElement","document","getElementById","tableSearchResetElement","tableSearchRowsElement","tableSearchAheadElement","tableSearchBehindElement","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","tableSearchAheadSet","tableSearchBehindSet","links","getElementsByTagName","all","modules","i","length","module","dataset","toLowerCase","event","preventDefault","stopPropagation","j","classList","remove","target","toggle","contains","push","UserPreference","setUserPreference","JSON","stringify","location","reload","tagName","hours","metric"],"mappings":";;;;;;;;qmCA+BqBA,UAGjBC,qBAEIC,MAAQ,IAAIC,uBACZ,EACAH,QACA,gDACA,0CACA,wBACA,gEACA,gEACA,uDACA,gDACA,yCAGJE,MAAME,eAEFC,wBAA0BC,SAASC,eAAe,wDAClDC,wBAA0BF,SAASC,eAAe,8DAClDE,uBAAyBH,SAASC,eAAe,sDACjDG,wBAA0BJ,SAASC,eAAe,sDAClDI,yBAA2BL,SAASC,eAAe,uDAEvDF,wBAAwBO,iBAAiB,QAASV,MAAMW,aACxDR,wBAAwBO,iBAAiB,QAASV,MAAMW,aACxDL,wBAAwBI,iBAAiB,QAASV,MAAMY,kBACxDL,uBAAuBG,iBAAiB,QAASV,MAAMa,mBACvDL,wBAAwBE,iBAAiB,QAASI,qBAClDL,yBAAyBC,iBAAiB,QAASK,6BAOjDhB,eAAiB,SACfiB,MAAQZ,SAASC,eAAe,8DAA8DY,qBAAqB,KACnHC,IAAMF,MAAM,GACZG,QAAU,OAET,IAAIC,EAAI,EAAGA,EAAIJ,MAAMK,OAAQD,IAAK,KAC/BE,OAASN,MAAMI,GAAGG,QAAQD,OAED,QAAzBA,OAAOE,cACPR,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,sBAED,IAAIC,EAAI,EAAGA,EAAIZ,MAAMK,OAAQO,IAC9BZ,MAAMY,GAAGC,UAAUC,OAAO,UAE9BL,MAAMM,OAAOF,UAAUG,OAAO,aAEF,UAAzBV,OAAOE,cACdR,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,kBAEevB,SAASC,eAAe,sEAChCwB,UAAUC,OAAO,YAEzB,IAAIV,EAAI,EAAGA,EAAIJ,MAAMK,OAAQD,OAC1BJ,MAAMI,GAAGS,UAAUI,SAAS,UAAW,KACnCX,OAASN,MAAMI,GAAGG,QAAQD,OAC9BH,QAAQe,KAAKZ,QAKrBa,eAAeC,kBACX,6DACAC,KAAKC,UAAUnB,UAInBoB,SAASC,YAGbxB,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,kBAENT,IAAIW,UAAUC,OAAO,UAErBL,MAAMM,OAAOF,UAAUG,OAAO,eAWxClB,oBAAuBW,WACzBA,MAAMC,iBACqC,MAAvCD,MAAMM,OAAOU,QAAQjB,cAAuB,KACxCkB,MAAQjB,MAAMM,OAAOR,QAAQoB,OACjCR,eAAeC,kBAAkB,gEAAiEM,OAElGH,SAASC,WASXzB,qBAAwBU,WAC1BA,MAAMC,iBACqC,MAAvCD,MAAMM,OAAOU,QAAQjB,cAAuB,KACxCkB,MAAQjB,MAAMM,OAAOR,QAAQoB,OACjCR,eAAeC,kBAAkB,iEAAkEM,OAEnGH,SAASC"} \ No newline at end of file diff --git a/report/activities_in_progress/amd/src/activities_in_progress.js b/report/activities_in_progress/amd/src/activities_in_progress.js new file mode 100644 index 00000000..cea58fd8 --- /dev/null +++ b/report/activities_in_progress/amd/src/activities_in_progress.js @@ -0,0 +1,153 @@ +// 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 . + +/** + * Chart data JS module. + * + * @module assessfreqreport/activities_in_progress + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import TableHandler from 'local_assessfreq/table_handler'; +import * as UserPreference from 'local_assessfreq/user_preferences'; + +/** + * Init function. + * @param {Integer} context + */ +export const init = (context) => { + + // Set up event listener and related actions for module dropdown on heatmp. + moduleDropdown(); + + let table = new TableHandler( + 0, + context, + 'assessfreqreport-activities-in-progress-table', + 'assessfreqreport_activities_in_progress', + 'get_in_progress_table', + 'assessfreqreport_activities_in_progress_table_rows_preference', + 'assessfreqreport_activities_in_progress_table_sort_preference', + 'assessfreqreport-activities-in-progress-table-search', + 'assessfreqreport-activities-in-progress-table', + 'local_assessfreq_set_table_preference' + ); + + table.getTable(); + + let tableSearchInputElement = document.getElementById('assessfreqreport-activities-in-progress-table-search'); + let tableSearchResetElement = document.getElementById('assessfreqreport-activities-in-progress-table-search-reset'); + let tableSearchRowsElement = document.getElementById('assessfreqreport-activities-in-progress-table-rows'); + let tableSearchAheadElement = document.getElementById('assessfreqreport-activities-in-progress-hoursahead'); + let tableSearchBehindElement = document.getElementById('assessfreqreport-activities-in-progress-hoursbehind'); + + tableSearchInputElement.addEventListener('keyup', table.tableSearch); + tableSearchInputElement.addEventListener('paste', table.tableSearch); + tableSearchResetElement.addEventListener('click', table.tableSearchReset); + tableSearchRowsElement.addEventListener('click', table.tableSearchRowSet); + tableSearchAheadElement.addEventListener('click', tableSearchAheadSet); + tableSearchBehindElement.addEventListener('click', tableSearchBehindSet); + +}; + +/** + * Add the event listeners to the modules in the module select dropdown. + */ +const moduleDropdown = () => { + let links = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type').getElementsByTagName('a'); + let all = links[0]; + let modules = []; + + for (let i = 0; i < links.length; i++) { + let module = links[i].dataset.module; + + if (module.toLowerCase() === 'all') { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + // Remove active class from all other links. + for (let j = 0; j < links.length; j++) { + links[j].classList.remove('active'); + } + event.target.classList.toggle('active'); + }); + } else if (module.toLowerCase() === 'close') { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + + const dropdownmenu = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type-filters'); + dropdownmenu.classList.remove('show'); + + for (let i = 0; i < links.length; i++) { + if (links[i].classList.contains('active')) { + let module = links[i].dataset.module; + modules.push(module); + } + } + + // Save selection as a user preference. + UserPreference.setUserPreference( + 'assessfreqreport_activities_in_progress_modules_preference', + JSON.stringify(modules) + ); + + // Reload based on selected year. + location.reload(); + }); + } else { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + + all.classList.remove('active'); + + event.target.classList.toggle('active'); + }); + } + } +}; + +/** + * Process the hours ahead event from the student table. + * + * @param {Event} event The triggered event for the element. + */ +const tableSearchAheadSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + let hours = event.target.dataset.metric; + UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursahead_preference', hours); + // Reload based on selected year. + location.reload(); + } +}; + +/** + * Process the hours behind event from the student table. + * + * @param {Event} event The triggered event for the element. + */ +const tableSearchBehindSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + let hours = event.target.dataset.metric; + UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursbehind_preference', hours); + // Reload based on selected year. + location.reload(); + } +}; diff --git a/report/activities_in_progress/classes/output/renderer.php b/report/activities_in_progress/classes/output/renderer.php new file mode 100644 index 00000000..21e3ea0e --- /dev/null +++ b/report/activities_in_progress/classes/output/renderer.php @@ -0,0 +1,346 @@ +. + +/** + * Renderer. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assessfreqreport_activities_in_progress\output; + +use context_system; +use core\chart_bar; +use core\chart_pie; +use core\chart_series; +use html_writer; +use local_assessfreq\source_base; +use local_assessfreq\utils; +use paging_bar; +use plugin_renderer_base; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/local/assessfreq/lib.php'); + +class renderer extends plugin_renderer_base { + + public function render_report($data) { + + // In progress counts. + $contents = ''; + foreach ($data['inprogress'] as $count) { + $contents .= html_writer::div($count); + } + + $progresssummarycontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('inprogress:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents + ] + ); + + // Upcoming activities starting. + $labels = []; + $seriestitle = get_string('upcomingchart:activities', 'assessfreqreport_activities_in_progress'); + $participantseries = get_string('upcomingchart:participants', 'assessfreqreport_activities_in_progress'); + + $seriesdata = []; + $participantseriesdata = []; + + foreach ($data['upcoming'] as $sourceupcoming) { + foreach ($sourceupcoming['upcoming'] as $timestamp => $upcoming) { + $count = 0; + $participantcount = 0; + + foreach ($upcoming as $activity) { + $count++; + $participantcount += $activity->participants; + } + + foreach ($sourceupcoming['inprogress'] as $inprogress) { + if ($inprogress->timestampopen >= $timestamp && $inprogress->timestampopen < $timestamp + HOURSECS) { + $count++; + $participantcount += $inprogress->participants; + } + } + + if (!isset($seriesdata[$timestamp])) { + $seriesdata[$timestamp] = 0; + } + $seriesdata[$timestamp] += $count; + if (!isset($participantseriesdata[$timestamp])) { + $participantseriesdata[$timestamp] = 0; + } + $participantseriesdata[$timestamp] += $participantcount; + $labels[$timestamp] = userdate( + $timestamp + HOURSECS, + get_string('upcomingchart:inprogressdatetime', 'assessfreqreport_activities_in_progress') + ); + } + } + $seriesdata = array_values($seriesdata); + $participantseriesdata = array_values($participantseriesdata); + $labels = array_values($labels); + + if ($seriesdata) { + $series = new chart_series($seriestitle, $seriesdata); + $participantseries = new chart_series($participantseries, $participantseriesdata); + + $chart = new chart_bar(); + $chart->add_series($series); + $chart->add_series($participantseries); + $chart->set_labels($labels); + + $contents = $this->render($chart); + } else { + $contents = ''; + } + $upcomingcontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('upcomingchart:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents, + ] + ); + + // Participant summary container. + $seriesdata = [ + 'notloggedin' => 0, + 'loggedin' => 0, + 'inprogress' => 0, + 'finished' => 0, + ]; + + foreach ($data['participants'] as $sourceparticipants) { + foreach ($sourceparticipants as $status => $value) { + $seriesdata[$status] = $value + ($seriesdata[$status] ?? 0); + } + } + + $seriesdata = array_values($seriesdata); + + $labels = [ + get_string('summarychart:notloggedin', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:loggedin', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:inprogress', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:finished', 'assessfreqreport_activities_in_progress'), + ]; + + $colors = [ + get_config('assessfreqreport_activities_in_progress', 'notloggedincolor'), + get_config('assessfreqreport_activities_in_progress', 'loggedincolor'), + get_config('assessfreqreport_activities_in_progress', 'inprogresscolor'), + get_config('assessfreqreport_activities_in_progress', 'finishedcolor'), + ]; + + if ($participantseriesdata) { + $chart = new chart_pie(); + $chart->set_doughnut(true); + $participants = new chart_series( + get_string('summarychart:participants', 'assessfreqreport_activities_in_progress'), + $seriesdata + ); + $participants->set_colors($colors); + $chart->add_series($participants); + $chart->set_labels($labels); + + $contents = $this->render($chart); + } else { + $contents = ''; + } + + $summarycontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('summarychart:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents + ] + ); + + // Activies in progress container. + $progresscontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('inprogresstable:head', 'assessfreqreport_activities_in_progress'), + 'contents' => 'No data' + ] + ); + + $preferencerows = get_user_preferences('assessfreqreport_activities_in_progress_table_rows_preference', 20); + $rows = [ + 20 => 'rows20', + 50 => 'rows50', + 100 => 'rows100', + ]; + + $preferencehoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $preferencehoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + + $hours = [ + 0 => 'hours0', + 1 => 'hours1', + 4 => 'hours4', + 8 => 'hours8', + ]; + + $preferencemodule = json_decode( + get_user_preferences('assessfreqreport_activities_in_progress_modules_preference', '["all"]'), + true + ); + // Only get modules with the "get_inprogress_count" method as only these display on the report. + $modules = get_modules($preferencemodule, 'get_inprogress_count'); + + return $this->render_from_template( + 'assessfreqreport_activities_in_progress/activities-in-progress', + [ + 'filters' => [ + 'modules' => $modules, + 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], + 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], + ], + 'progresssummary' => $progresssummarycontainer, + 'upcoming' => $upcomingcontainer, + 'summary' => $summarycontainer, + 'progress' => $progresscontainer, + 'table' => [ + 'id' => 'assessfreqreport-activities-in-progress', + 'name' => get_string('inprogresstable:head', 'assessfreqreport_activities_in_progress'), + 'rows' => [$rows[$preferencerows] => 'true'], + ] + ] + ); + } + + /** + * Renders the activities in progress "table" on the dashboard screen. + * We update the table via ajax. + * The table isn't a real table it's a collection of divs. + * + * @param string $search The search string for the table. + * @param int $page The page number of results. + * @param string $sorton The value to sort by. + * @param string $direction The direction to sort. + * @param int $hoursahead Amount of time in hours to look ahead for activity starting. + * @param int $hoursbehind Amount of time in hours to look behind for activity starting. + * @return string $output HTML for the table. + */ + public function render_activities_inprogress_table( + string $search, + int $page, + string $sorton, + string $direction + ): string { + $now = time(); + $hoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $hoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + $sources = get_sources(); + $inprogress = []; + /* @var $source source_base */ + foreach ($sources as $source) { + if (method_exists($source, 'get_inprogress_data')) { + $inprogress[] = $source->get_inprogress_data($now, $hoursahead, $hoursbehind); + } + } + $pagesize = get_user_preferences('assessfreqreport_activities_in_progress_table_rows_preference', 20); + + $activities = []; + foreach ($inprogress as $activity) { + array_push($activities, ...$activity['inprogress']); + $upcomingactivities = $activity['upcoming']; + $finishedactivities = $activity['finished']; + + foreach ($upcomingactivities as $upcomingactivity) { + foreach ($upcomingactivity as $key => $upcoming) { + $activities[$key] = $upcoming; + } + } + + foreach ($finishedactivities as $finishedactivity) { + foreach ($finishedactivity as $key => $finished) { + $activities[$key] = $finished; + } + } + } + + if (empty($activities)) { + return ''; + } + + [$filtered, $totalrows] = $this->filter($activities, $search, $page, $pagesize); + $sortedactivities = utils::sort($filtered, $sorton, $direction); + + $pagingbar = new paging_bar($totalrows, $page, $pagesize, '/'); + $pagingoutput = $this->render($pagingbar); + + $context = [ + 'activities' => array_values($sortedactivities), + 'pagingbar' => $pagingoutput, + 'iscourse' => $this->page->course->id !== SITEID, + ]; + + return $this->render_from_template('assessfreqreport_activities_in_progress/activities-in-progress-table', $context); + } + + + /** + * Given an array of activities, filter based on a provided search string and apply pagination. + * + * @param array $activities Array of activities to search. + * @param string $search The string to search by. + * @param int $page The page number of results. + * @param int $pagesize The page size for results. + * @return array $result Array containing list of filtered activities and total of how many activities matched the filter. + */ + private function filter(array $activities, string $search, int $page, int $pagesize): array { + $filtered = []; + $searchfields = ['name', 'coursefullname']; + $offset = $page * $pagesize; + $offsetcount = 0; + $recordcount = 0; + + foreach ($activities as $id => $activity) { + $searchcount = 0; + if ($search != '') { + $searchcount = -1; + foreach ($searchfields as $searchfield) { + if (stripos($activity->{$searchfield}, $search) !== false) { + $searchcount++; + } + } + } + + if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { + $filtered[$id] = $activity; + } + + if ($searchcount > -1 && $offsetcount >= $offset) { + $recordcount++; + } + + if ($searchcount > -1) { + $offsetcount++; + } + } + + return [$filtered, $offsetcount]; + } +} diff --git a/report/activities_in_progress/classes/report.php b/report/activities_in_progress/classes/report.php new file mode 100644 index 00000000..74c330b6 --- /dev/null +++ b/report/activities_in_progress/classes/report.php @@ -0,0 +1,127 @@ +. + +/** + * Main report class. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assessfreqreport_activities_in_progress; + +use local_assessfreq\report_base; +use local_assessfreq\source_base; + +class report extends report_base { + const WEIGHT = 30; + + /** + * @inheritDoc + */ + public function get_name() : string { + return get_string("tab:name", "assessfreqreport_activities_in_progress"); + } + + /** + * @inheritDoc + */ + public function get_tab_weight() : int { + return self::WEIGHT; + } + + /** + * @inheritDoc + */ + public function get_tablink() : string { + return 'activities_in_progress'; + } + + /** + * @inheritDoc + */ + public function has_access() : bool { + global $PAGE; + + return has_capability('assessfreqreport/activities_in_progress:view', $PAGE->context); + } + + /** + * @inheritDoc + */ + public function get_contents() : string { + global $PAGE; + + $data = []; + $inprogress = []; + $upcoming = []; + $participants = []; + $now = time(); + $modulepreference = json_decode( + get_user_preferences('assessfreqreport_activities_in_progress_modules_preference', '["all"]') + ); + $sources = get_sources(); + $hoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $hoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + + foreach ($sources as $source) { + /* @var $source source_base */ + if (!in_array('all', $modulepreference) && !in_array($source->get_module(), $modulepreference)) { + continue; + } + if (method_exists($source, 'get_inprogress_count')) { + $inprogress[] = $source->get_inprogress_count($now, $hoursahead, $hoursbehind); + } + if (method_exists($source, 'get_upcoming_data')) { + $upcoming[] = $source->get_upcoming_data($now, $hoursahead, $hoursbehind); + } + if (method_exists($source, 'get_all_participants_inprogress_data')) { + $participants[] = $source->get_all_participants_inprogress_data($now, $hoursahead, $hoursbehind); + } + } + $data['inprogress'] = $inprogress; + $data['upcoming'] = $upcoming; + $data['participants'] = $participants; + + $renderer = $PAGE->get_renderer("assessfreqreport_activities_in_progress"); + + return $renderer->render_report($data); + } + + /** + * @inheritDoc + */ + protected function get_required_js() : void { + global $PAGE; + + $PAGE->requires->js_call_amd( + 'assessfreqreport_activities_in_progress/activities_in_progress', + 'init', + [$PAGE->context->id] + ); + } + + /** + * @inheritDoc + */ + protected function get_required_css(): void { + global $PAGE; + + $PAGE->requires->css('/local/assessfreq/report/activities_in_progress/styles.css'); + } +} diff --git a/report/activities_in_progress/db/access.php b/report/activities_in_progress/db/access.php new file mode 100644 index 00000000..7e3d2ed6 --- /dev/null +++ b/report/activities_in_progress/db/access.php @@ -0,0 +1,34 @@ +. + +/** + * Access file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'assessfreqreport/activities_in_progress:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [], + ], +]; diff --git a/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php b/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php new file mode 100644 index 00000000..4c5857bb --- /dev/null +++ b/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php @@ -0,0 +1,80 @@ +. + +/** + * Lang file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['pluginname'] = 'Report - Activities in Progress'; + +$string['tab:name'] = 'Activities in Progress'; + +$string['activities_in_progress:view'] = 'Ability to view the activities in progress report.'; + +$string['settings:chartheading'] = 'Chart settings'; +$string['settings:chartheading_desc'] = 'These settings allow you to configure the the settings used in the charts and graphs'; +$string['settings:notloggedincolor'] = 'Not logged in color'; +$string['settings:notloggedincolor_desc'] = 'Select color to display for not logged in users in charts'; +$string['settings:loggedincolor'] = 'Logged in color'; +$string['settings:loggedincolor_desc'] = 'Select color to display for logged in users in charts'; +$string['settings:inprogresscolor'] = 'In progress color'; +$string['settings:inprogresscolor_desc'] = 'Select color to display for in progress users in charts'; +$string['settings:finishedcolor'] = 'Finished color'; +$string['settings:finishedcolor_desc'] = 'Select color to display for finished users in charts'; +$string['settings:graphsheading'] = 'Graph settings'; +$string['settings:graphsheading_desc'] = 'Specify the graph settings for each graph report'; + +$string['filter:selectassessment'] = 'Select assessment type'; +$string['filter:closeapply'] = 'Close and apply'; +$string['filter:header'] = 'Filters'; +$string['filter:submit'] = 'Filter'; +$string['filter:hours0'] = 'Now'; +$string['filter:hours1'] = '1 Hour'; +$string['filter:hours4'] = '4 Hours'; +$string['filter:hours8'] = '8 Hours'; +$string['filter:hoursahead'] = 'Hours ahead'; +$string['filter:hoursbehind'] = 'Hours behind'; + +$string['inprogress'] = 'In progress'; +$string['inprogress:head'] = 'In progress'; + +$string['upcomingchart:head'] = 'Upcoming activities starting'; +$string['upcomingchart:inprogressdatetime'] = '%H:00'; +$string['upcomingchart:activities'] = 'Activities'; +$string['upcomingchart:participants'] = 'Students'; + +$string['summarychart:head'] = 'Participant summary'; +$string['summarychart:participants'] = 'Students'; +$string['summarychart:notloggedin'] = 'Not logged in'; +$string['summarychart:loggedin'] = 'Logged in'; +$string['summarychart:inprogress'] = 'In progress'; +$string['summarychart:finished'] = 'Finished'; + +$string['inprogresstable:head'] = 'Activies in progress'; +$string['inprogresstable:activity'] = 'Activity'; +$string['inprogresstable:course'] = 'Course'; +$string['inprogresstable:timelimit'] = 'Time limit'; +$string['inprogresstable:timeopen'] = 'Time open'; +$string['inprogresstable:timeclose'] = 'Time close'; +$string['inprogresstable:participants'] = 'Participants (Overrides)'; +$string['inprogresstable:dashboard'] = 'Dashboard'; + +$string['report:usage_guidlines'] = ''; diff --git a/report/activities_in_progress/lib.php b/report/activities_in_progress/lib.php new file mode 100644 index 00000000..373555de --- /dev/null +++ b/report/activities_in_progress/lib.php @@ -0,0 +1,86 @@ +. + +/** + * @package assessfreqreport_activities_in_progress + * @copyright 2024 Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Returns the name of the user preferences as well as the details this plugin uses. + * + * @return array + */ +function assessfreqreport_activities_in_progress_user_preferences() : array { + + $preferences['assessfreqreport_activities_in_progress_modules_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => '[]', + 'type' => PARAM_RAW, + ]; + + $preferences['assessfreqreport_activities_in_progress_table_rows_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 20, + 'type' => PARAM_INT, + ]; + + $preferences['assessfreqreport_activities_in_progress_table_sort_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 'name_asc', + 'type' => PARAM_ALPHAEXT, + ]; + + $preferences['assessfreqreport_activities_in_progress_hoursahead_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 8, + 'type' => PARAM_INT, + ]; + + $preferences['assessfreqreport_activities_in_progress_hoursbehind_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 1, + 'type' => PARAM_INT, + ]; + + return $preferences; +} + +/** + * Renders the user table on the dashboard screen. + * We update the table via ajax. + * + * @param array $args + * @return string $o Form HTML. + */ +function assessfreqreport_activities_in_progress_output_fragment_get_in_progress_table(array $args) : string { + global $PAGE; + + require_capability('assessfreqreport/activities_in_progress:view', $PAGE->context); + + $sortpreference = explode( + '_', + get_user_preferences('assessfreqreport_activities_in_progress_table_sort_preference', 'name_asc') + ); + $data = json_decode($args['data']); + $search = is_null($data->search) ? '' : $data->search; + $sorton = $sortpreference[0]; + $direction = $sortpreference[1]; + + $output = $PAGE->get_renderer('assessfreqreport_activities_in_progress'); + return $output->render_activities_inprogress_table($search, $data->page, $sorton, $direction); +} diff --git a/report/activities_in_progress/settings.php b/report/activities_in_progress/settings.php new file mode 100644 index 00000000..6d1e0359 --- /dev/null +++ b/report/activities_in_progress/settings.php @@ -0,0 +1,65 @@ +. + +/** + * Settings file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if (!$hassiteconfig) { + return; +} + +// Graph settings. +$settings->add(new admin_setting_heading( + 'assessfreqreport_activities_in_progress/graphsheading', + get_string('settings:graphsheading', 'assessfreqreport_activities_in_progress'), + get_string('settings:graphsheading_desc', 'assessfreqreport_activities_in_progress') +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/notloggedincolor', + get_string('settings:notloggedincolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:notloggedincolor_desc', 'assessfreqreport_activities_in_progress'), + '#8C0010' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/loggedincolor', + get_string('settings:loggedincolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:loggedincolor_desc', 'assessfreqreport_activities_in_progress'), + '#FA8900' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/inprogresscolor', + get_string('settings:inprogresscolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:inprogresscolor_desc', 'assessfreqreport_activities_in_progress'), + '#875692' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/finishedcolor', + get_string('settings:finishedcolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:finishedcolor_desc', 'assessfreqreport_activities_in_progress'), + '#1B8700' +)); diff --git a/report/activities_in_progress/styles.css b/report/activities_in_progress/styles.css new file mode 100644 index 00000000..dd9125ba --- /dev/null +++ b/report/activities_in_progress/styles.css @@ -0,0 +1,3 @@ +#local-assessfreq-report-activities-in-progress .chart-area .chart-image { + width: 100% !important; +} \ No newline at end of file diff --git a/report/activities_in_progress/templates/activities-in-progress-table.mustache b/report/activities_in_progress/templates/activities-in-progress-table.mustache new file mode 100644 index 00000000..34d7bd2f --- /dev/null +++ b/report/activities_in_progress/templates/activities-in-progress-table.mustache @@ -0,0 +1,78 @@ +{{! + 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 . +}} +{{! + @template assessfreqreport_activities_in_progress/activities-in-progress-table + + Report Summary template. + + Example context (json): + { + "activities": 1, + "context": 1 + } +}} +
+ {{{pagingbar}}} +
+
+
+
+ + + + {{^iscourse}} + + {{/iscourse}} + + + + + + + + {{#activities}} + + + {{^iscourse}} + + {{/iscourse}} + + + + + + + {{/activities}} + +
{{#str}} inprogresstable:activity, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:course, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timeopen, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timeclose, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timelimit, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:participants, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:dashboard, assessfreqreport_activities_in_progress {{/str}}
+ {{{name}}} + {{courseshortname}}{{earlyopen}}{{lateclose}}{{timelimit}} + {{participants}} ({{overrideparticipants}}) + + {{#dashboardlink}} + + {{#pix}} i/report, core{{/pix}} + + {{/dashboardlink}} +
+
+
+
+
+ {{{pagingbar}}} +
\ No newline at end of file diff --git a/templates/quiz-dashboard-cards.mustache b/report/activities_in_progress/templates/activities-in-progress.mustache similarity index 50% rename from templates/quiz-dashboard-cards.mustache rename to report/activities_in_progress/templates/activities-in-progress.mustache index 22da4c96..f2ce8b59 100644 --- a/templates/quiz-dashboard-cards.mustache +++ b/report/activities_in_progress/templates/activities-in-progress.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template local_assessfreq/quiz-dashboard-cards + @template assessfreqreport_activities_in_progress/activities-in-progress Report Summary template. @@ -24,22 +24,26 @@ } }} +
+ + {{> assessfreqreport_activities_in_progress/filters}} -
-
-
-

{{# str }} noquiz, local_assessfreq {{/ str }}

+
+
+
{{{progresssummary}}}
+
+
+
+ {{{upcoming}}} +
+
+ {{{summary}}} +
+
+
+
+ {{> local_assessfreq/table}} +
-
-
- {{> local_assessfreq/quiz-summary-card}} - {{> local_assessfreq/quiz-summary-graph}} -
-
- {{> local_assessfreq/quiz-summary-trend-graph}} -
-
- {{> local_assessfreq/quiz-summary-student-table}}
- diff --git a/report/activities_in_progress/templates/filter-hoursahead.mustache b/report/activities_in_progress/templates/filter-hoursahead.mustache new file mode 100644 index 00000000..4f6ae0e8 --- /dev/null +++ b/report/activities_in_progress/templates/filter-hoursahead.mustache @@ -0,0 +1,76 @@ +{{! + 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 . +}} +{{! + @template local_assessfreq/nav-quiz-table-hoursahead-filter + + This template renders the day range selector for the timeline view. + + Example context (json): + {} +}} +
+ + +
diff --git a/report/activities_in_progress/templates/filter-hoursbehind.mustache b/report/activities_in_progress/templates/filter-hoursbehind.mustache new file mode 100644 index 00000000..147f0295 --- /dev/null +++ b/report/activities_in_progress/templates/filter-hoursbehind.mustache @@ -0,0 +1,76 @@ +{{! + 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 . +}} +{{! + @template local_assessfreq/nav-quiz-table-hoursbehind-filter + + This template renders the day range selector for the timeline view. + + Example context (json): + {} +}} +
+ + +
diff --git a/templates/nav-assess-type-filter.mustache b/report/activities_in_progress/templates/filter-type.mustache similarity index 62% rename from templates/nav-assess-type-filter.mustache rename to report/activities_in_progress/templates/filter-type.mustache index 448e9455..5c60ae18 100644 --- a/templates/nav-assess-type-filter.mustache +++ b/report/activities_in_progress/templates/filter-type.mustache @@ -15,27 +15,27 @@ along with Moodle. If not, see . }} {{! - @template local_assessfreq/nav-assess-type-filter + @template assessfreqreport_activities_in_progress/filter-type - This template renders the day range selector for the timeline view. + This template renders the type filter. Example context (json): {} }} -