diff --git a/amd/build/dashboard.min.js b/amd/build/dashboard.min.js new file mode 100644 index 0000000..a370285 --- /dev/null +++ b/amd/build/dashboard.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/dashboard",["exports","./push_tasks","core/notification"],(function(_exports,_push_tasks,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};async function pushgrade(button){try{let assessmentmappingid=button.getAttribute("data-assessmentmappingid"),result=await(0,_push_tasks.schedulePushTask)(assessmentmappingid);if(result.success){button.parentNode.parentNode.querySelector("span:last-child").innerHTML='',button.disabled=!0;let tooltipid=button.getAttribute("aria-describedby");null!==tooltipid&&null!==document.getElementById(tooltipid)&&document.getElementById(tooltipid).remove()}else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=button.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success}catch(error){return window.console.error(error),!1}}_exports.init=()=>{let page=document.getElementById("page"),tableSelector=document.getElementById("module-delivery-selector");tableSelector.addEventListener("change",(function(){let selectedTable=document.getElementById(tableSelector.value);if(selectedTable){let offset=-100,tablePosition=selectedTable.getBoundingClientRect().top,scrollPosition=page.scrollTop+tablePosition+offset;page.scrollTo({top:scrollPosition,behavior:"smooth"})}}));let backToTopButton=document.getElementById("backToTopButton");page.addEventListener("scroll",(function(){page.scrollTop>=100?backToTopButton.style.display="block":backToTopButton.style.display="none"})),backToTopButton.addEventListener("click",(function(){page.scrollTo({top:0,behavior:"smooth"}),tableSelector.selectedIndex=0})),document.querySelectorAll(".push-mark-button:not([disabled])").forEach((function(button){button.addEventListener("click",(function(){pushgrade(this)}))})),document.getElementById("push-all-button").addEventListener("click",(async function(){let mabpushbuttons=document.querySelectorAll(".push-mark-button:not([disabled])"),total=mabpushbuttons.length,count=0,promises=[];mabpushbuttons.forEach((function(button){let promise=pushgrade(button).then((function(result){return result&&(count+=1),result})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),page.scrollTo({top:0,behavior:"instant"}),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); + +//# 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 0000000..45dae80 --- /dev/null +++ b/amd/build/dashboard.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["import {schedulePushTask} from \"./push_tasks\";\nimport notification from \"core/notification\";\n\nexport const init = () => {\n // Find the page element.\n let page = document.getElementById(\"page\");\n\n // Find the module delivery table selector.\n let tableSelector = document.getElementById(\"module-delivery-selector\");\n\n // Jump to the selected module delivery table when the user selects a module delivery.\n tableSelector.addEventListener(\"change\", function() {\n // Find the selected table by ID.\n let selectedTable = document.getElementById(tableSelector.value);\n\n // Calculate the scroll position to be 100 pixels above the table.\n if (selectedTable) {\n let offset = -100;\n let tablePosition = selectedTable.getBoundingClientRect().top;\n let scrollPosition = page.scrollTop + tablePosition + offset;\n\n // Scroll to the calculated position.\n page.scrollTo({\n top: scrollPosition,\n behavior: \"smooth\"\n });\n }\n });\n\n // Find the back to top button.\n let backToTopButton = document.getElementById(\"backToTopButton\");\n\n // Show the button when the user scrolls down 100 pixels from the top of the page.\n page.addEventListener(\"scroll\", function() {\n if (page.scrollTop >= 100) {\n backToTopButton.style.display = \"block\";\n } else {\n backToTopButton.style.display = \"none\";\n }\n });\n\n // Scroll to the top of the page when the button is clicked.\n backToTopButton.addEventListener(\"click\", function() {\n page.scrollTo({top: 0, behavior: \"smooth\"});\n tableSelector.selectedIndex = 0;\n });\n\n // Get all the push buttons that are not disabled.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n // Push grades when the user clicks on each enabled push button.\n mabpushbuttons.forEach(function(button) {\n button.addEventListener(\"click\", function() {\n pushgrade(this);\n });\n });\n\n // Get the push all button.\n let pushallbutton = document.getElementById(\"push-all-button\");\n\n // Push grades for all the not disabled push buttons when the user clicks on the push all button.\n pushallbutton.addEventListener(\"click\", async function() {\n // Get the updated not disabled push buttons.\n let mabpushbuttons = document.querySelectorAll(\".push-mark-button:not([disabled])\");\n\n // Number of not disabled push buttons.\n let total = mabpushbuttons.length;\n let count = 0;\n\n // Create an array to hold all the Promises.\n let promises = [];\n\n // Push grades to SITS for each MAB.\n mabpushbuttons.forEach(function(button) {\n // Create a Promise for each button and push it into the array.\n let promise = pushgrade(button)\n .then(function(result) {\n if (result) {\n count = count + 1;\n }\n return result;\n }).catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all Promises to resolve.\n await Promise.all(promises);\n\n // Scroll to the top of the page so that the user can see the notification.\n page.scrollTo({top: 0, behavior: \"instant\"});\n\n // Show the notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n\n/**\n * Schedule a push task when the user clicks on a push button.\n *\n * @param {HTMLElement} button The button element.\n * @return {Promise} Promise.\n */\nasync function pushgrade(button) {\n try {\n // Get the assessment mapping ID from the button.\n let assessmentmappingid = button.getAttribute(\"data-assessmentmappingid\");\n\n // Schedule a push task.\n let result = await schedulePushTask(assessmentmappingid);\n\n // Check if the push task is successfully scheduled.\n if (result.success) {\n // Update the icon.\n let icon = button.parentNode.parentNode.querySelector(\"span:last-child\");\n icon.innerHTML = '';\n\n // Disable the push button after scheduled push task successfully.\n button.disabled = true;\n\n // Remove the tooltip (for Firefox and Safari).\n let tooltipid = button.getAttribute(\"aria-describedby\");\n if (tooltipid !== null && document.getElementById(tooltipid) !== null) {\n document.getElementById(tooltipid).remove();\n }\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n\n // Set the class and content of the error message row.\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the button.\n let currentrow = button.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row after the current row.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n\n return result.success;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n}\n"],"names":["pushgrade","button","assessmentmappingid","getAttribute","result","success","parentNode","querySelector","innerHTML","status","disabled","tooltipid","document","getElementById","remove","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","insertAdjacentElement","error","window","console","page","tableSelector","addEventListener","selectedTable","value","offset","tablePosition","getBoundingClientRect","top","scrollPosition","scrollTop","scrollTo","behavior","backToTopButton","style","display","selectedIndex","querySelectorAll","forEach","this","async","mabpushbuttons","total","length","count","promises","promise","then","catch","push","Promise","all","addNotification","type"],"mappings":"4SA4GeA,UAAUC,gBAGbC,oBAAsBD,OAAOE,aAAa,4BAG1CC,aAAe,gCAAiBF,wBAGhCE,OAAOC,QAAS,CAELJ,OAAOK,WAAWA,WAAWC,cAAc,mBACjDC,UAAY,4FACDJ,OAAOK,OAAS,SAGhCR,OAAOS,UAAW,MAGdC,UAAYV,OAAOE,aAAa,oBAClB,OAAdQ,WAA6D,OAAvCC,SAASC,eAAeF,YAC9CC,SAASC,eAAeF,WAAWG,aAEpC,KAECC,gBAAkBH,SAASI,cAAc,MAG7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBP,UACZ,iEACkDJ,OAAOc,QADzD,kBAKAC,WAAalB,OAAOmB,QAAQ,MAGM,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBP,SAIlCK,WAAWK,sBAAsB,WAAYT,wBAG1CX,OAAOC,QAChB,MAAOoB,cACLC,OAAOC,QAAQF,MAAMA,QACd,iBA3JK,SAEZG,KAAOhB,SAASC,eAAe,QAG/BgB,cAAgBjB,SAASC,eAAe,4BAG5CgB,cAAcC,iBAAiB,UAAU,eAEjCC,cAAgBnB,SAASC,eAAegB,cAAcG,UAGtDD,cAAe,KACXE,QAAU,IACVC,cAAgBH,cAAcI,wBAAwBC,IACtDC,eAAiBT,KAAKU,UAAYJ,cAAgBD,OAGtDL,KAAKW,SAAS,CACVH,IAAKC,eACLG,SAAU,mBAMlBC,gBAAkB7B,SAASC,eAAe,mBAG9Ce,KAAKE,iBAAiB,UAAU,WACxBF,KAAKU,WAAa,IAClBG,gBAAgBC,MAAMC,QAAU,QAEhCF,gBAAgBC,MAAMC,QAAU,UAKxCF,gBAAgBX,iBAAiB,SAAS,WACtCF,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,WACjCX,cAAce,cAAgB,KAIbhC,SAASiC,iBAAiB,qCAGhCC,SAAQ,SAAS7C,QAC5BA,OAAO6B,iBAAiB,SAAS,WAC7B9B,UAAU+C,YAKEnC,SAASC,eAAe,mBAG9BiB,iBAAiB,SAASkB,qBAEhCC,eAAiBrC,SAASiC,iBAAiB,qCAG3CK,MAAQD,eAAeE,OACvBC,MAAQ,EAGRC,SAAW,GAGfJ,eAAeH,SAAQ,SAAS7C,YAExBqD,QAAUtD,UAAUC,QACnBsD,MAAK,SAASnD,eACPA,SACAgD,OAAgB,GAEbhD,UACRoD,OAAM,SAAS/B,OACdC,OAAOC,QAAQF,MAAMA,UAG7B4B,SAASI,KAAKH,kBAIZI,QAAQC,IAAIN,UAGlBzB,KAAKW,SAAS,CAACH,IAAK,EAAGI,SAAU,kCAGpBoB,gBAAgB,CACzB1C,QAASkC,MAAQ,OAASF,MAAQ,mCAClCW,KAAOT,QAAUF,MAAS,UAAY"} \ No newline at end of file diff --git a/amd/build/push_tasks.min.js b/amd/build/push_tasks.min.js new file mode 100644 index 0000000..db83ca9 --- /dev/null +++ b/amd/build/push_tasks.min.js @@ -0,0 +1,3 @@ +define("local_sitsgradepush/push_tasks",["exports","core/ajax"],(function(_exports,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.schedulePushTask=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.schedulePushTask=assessmentmappingid=>new Promise(((resolve,reject)=>{_ajax.default.call([{methodname:"local_sitsgradepush_schedule_push_task",args:{assessmentmappingid:assessmentmappingid}}])[0].done((function(response){resolve(response)})).fail((function(err){window.console.log(err),reject(err)}))}))})); + +//# sourceMappingURL=push_tasks.min.js.map \ No newline at end of file diff --git a/amd/build/push_tasks.min.js.map b/amd/build/push_tasks.min.js.map new file mode 100644 index 0000000..3a920c9 --- /dev/null +++ b/amd/build/push_tasks.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"push_tasks.min.js","sources":["../src/push_tasks.js"],"sourcesContent":["import Ajax from 'core/ajax';\n\n/**\n * Schedule a task to push grades to SITS.\n *\n * @param {string} assessmentmappingid The assessment mapping ID.\n * @return {Promise} Promise.\n */\nexport const schedulePushTask = (assessmentmappingid) => {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_sitsgradepush_schedule_push_task',\n args: {\n 'assessmentmappingid': assessmentmappingid,\n },\n }])[0].done(function(response) {\n resolve(response);\n }).fail(function(err) {\n window.console.log(err);\n reject(err);\n });\n });\n};\n"],"names":["assessmentmappingid","Promise","resolve","reject","call","methodname","args","done","response","fail","err","window","console","log"],"mappings":"yQAQiCA,qBACtB,IAAIC,SAAQ,CAACC,QAASC,wBACpBC,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,qBACqBN,wBAE3B,GAAGO,MAAK,SAASC,UACjBN,QAAQM,aACTC,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,KACnBP,OAAOO"} \ No newline at end of file diff --git a/amd/build/sitsgradepush.min.js b/amd/build/sitsgradepush.min.js index 6372bee..d55a125 100644 --- a/amd/build/sitsgradepush.min.js +++ b/amd/build/sitsgradepush.min.js @@ -1,3 +1,3 @@ -define("local_sitsgradepush/sitsgradepush",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.init=coursemoduleid=>{let pushbuton=document.getElementById("local_sitsgradepush_pushbutton_async");null!==pushbuton&&pushbuton.addEventListener("click",(e=>{e.preventDefault(),function(coursemoduleid){_ajax.default.call([{methodname:"local_sitsgradepush_schedule_push_task",args:{coursemoduleid:coursemoduleid}}])[0].done((function(response){if(_notification.default.addNotification({message:response.message,type:response.success?"success":"warning"}),response.success){let pushbuton=document.getElementById("local_sitsgradepush_pushbutton_async");pushbuton.innerHTML=response.status,pushbuton.disabled=!0}})).fail((function(err){window.console.log(err)}))}(coursemoduleid)}))}})); +define("local_sitsgradepush/sitsgradepush",["exports","./push_tasks","core/notification"],(function(_exports,_push_tasks,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};_exports.init=(coursemoduleid,mappingids)=>{let pushbuton=document.getElementById("local_sitsgradepush_pushbutton_async");if(null===pushbuton)return;let promises=[];pushbuton.addEventListener("click",(async e=>{e.preventDefault();let total=mappingids.length,count=0;mappingids.forEach((function(mappingid){let promise=(0,_push_tasks.schedulePushTask)(mappingid).then((function(result){let taskstatus=document.getElementById("taskstatus-"+mappingid);if(result.success)count+=1,taskstatus.innerHTML=' '+result.status;else{let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='";let currentrow=taskstatus.closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove(),currentrow.insertAdjacentElement("afterend",errormessagerow)}return result.success})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),_notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"})}))}})); //# sourceMappingURL=sitsgradepush.min.js.map \ No newline at end of file diff --git a/amd/build/sitsgradepush.min.js.map b/amd/build/sitsgradepush.min.js.map index 7dc88b0..bc89336 100644 --- a/amd/build/sitsgradepush.min.js.map +++ b/amd/build/sitsgradepush.min.js.map @@ -1 +1 @@ -{"version":3,"file":"sitsgradepush.min.js","sources":["../src/sitsgradepush.js"],"sourcesContent":["import Ajax from 'core/ajax';\nimport notification from 'core/notification';\n\nexport const init = (coursemoduleid) => {\n // Get the push button.\n let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async');\n\n // Exit if the push button is not found.\n if (pushbuton === null) {\n return;\n }\n\n // Add an event listener to the push button.\n pushbuton.addEventListener('click', (e) => {\n e.preventDefault();\n // Schedule a task to push grades to SITS.\n schedulePushTask(coursemoduleid);\n });\n};\n\n/**\n * Schedule a task to push grades to SITS.\n * @param {int} coursemoduleid\n */\nfunction schedulePushTask(coursemoduleid) {\n Ajax.call([{\n methodname: 'local_sitsgradepush_schedule_push_task',\n args: {\n 'coursemoduleid': coursemoduleid,\n },\n }])[0].done(function(response) {\n notification.addNotification({\n message: response.message,\n type: response.success ? 'success' : 'warning'\n });\n if (response.success) {\n // Get the push button.\n let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async');\n\n // Update the button text.\n pushbuton.innerHTML = response.status;\n\n // Disable the push button after scheduled push task successfully.\n pushbuton.disabled = true;\n }\n }).fail(function(err) {\n window.console.log(err);\n });\n}\n"],"names":["coursemoduleid","pushbuton","document","getElementById","addEventListener","e","preventDefault","call","methodname","args","done","response","addNotification","message","type","success","innerHTML","status","disabled","fail","err","window","console","log","schedulePushTask"],"mappings":"oYAGqBA,qBAEbC,UAAYC,SAASC,eAAe,wCAGtB,OAAdF,WAKJA,UAAUG,iBAAiB,SAAUC,IACjCA,EAAEC,0BAUgBN,8BACjBO,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,gBACgBT,mBAEtB,GAAGU,MAAK,SAASC,mCACJC,gBAAgB,CACzBC,QAASF,SAASE,QAClBC,KAAMH,SAASI,QAAU,UAAY,YAErCJ,SAASI,QAAS,KAEdd,UAAYC,SAASC,eAAe,wCAGxCF,UAAUe,UAAYL,SAASM,OAG/BhB,UAAUiB,UAAW,MAE1BC,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,QA9BnBI,CAAiBxB"} \ No newline at end of file +{"version":3,"file":"sitsgradepush.min.js","sources":["../src/sitsgradepush.js"],"sourcesContent":["import {schedulePushTask} from \"./push_tasks\";\nimport notification from 'core/notification';\n\nexport const init = (coursemoduleid, mappingids) => {\n // Get the push button.\n let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async');\n\n // Exit if the push button is not found.\n if (pushbuton === null) {\n return;\n }\n\n let promises = [];\n\n // Schedule a push task for each assessment mapping.\n pushbuton.addEventListener('click', async(e) => {\n e.preventDefault();\n\n // Number of assessment mappings.\n let total = mappingids.length;\n let count = 0;\n\n // Schedule a task to push grades to SITS for each assessment mapping.\n mappingids.forEach(function(mappingid) {\n let promise = schedulePushTask(mappingid)\n .then(function(result) {\n let taskstatus = document.getElementById('taskstatus-' + mappingid);\n if (result.success) {\n count = count + 1;\n taskstatus.innerHTML = ' ' + result.status;\n } else {\n // Create an error message row.\n let errormessagerow = document.createElement(\"tr\");\n errormessagerow.setAttribute(\"class\", \"error-message-row\");\n errormessagerow.innerHTML =\n '\">' +\n '
' + result.message + '
' +\n '';\n\n // Find the closest row to the assessment mapping.\n let currentrow = taskstatus.closest(\"tr\");\n\n // Remove the existing error message row if it exists.\n if (currentrow.nextElementSibling !== null &&\n currentrow.nextElementSibling.classList.contains(\"error-message-row\")) {\n currentrow.nextElementSibling.remove();\n }\n\n // Insert the error message row for the assessment mapping.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n return result.success;\n })\n .catch(function(error) {\n window.console.error(error);\n });\n\n promises.push(promise);\n });\n\n // Wait for all the push tasks to be scheduled.\n await Promise.all(promises);\n\n // Display a notification.\n notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n });\n};\n"],"names":["coursemoduleid","mappingids","pushbuton","document","getElementById","promises","addEventListener","async","e","preventDefault","total","length","count","forEach","mappingid","promise","then","result","taskstatus","success","innerHTML","status","errormessagerow","createElement","setAttribute","message","currentrow","closest","nextElementSibling","classList","contains","remove","insertAdjacentElement","catch","error","window","console","push","Promise","all","addNotification","type"],"mappings":"+SAGoB,CAACA,eAAgBC,kBAE7BC,UAAYC,SAASC,eAAe,2CAGtB,OAAdF,qBAIAG,SAAW,GAGfH,UAAUI,iBAAiB,SAASC,MAAAA,IAChCC,EAAEC,qBAGEC,MAAQT,WAAWU,OACnBC,MAAQ,EAGZX,WAAWY,SAAQ,SAASC,eACpBC,SAAU,gCAAiBD,WAC1BE,MAAK,SAASC,YACPC,WAAaf,SAASC,eAAe,cAAgBU,cACrDG,OAAOE,QACPP,OAAgB,EAChBM,WAAWE,UAAY,+CAAiDH,OAAOI,WAC5E,KAECC,gBAAkBnB,SAASoB,cAAc,MAC7CD,gBAAgBE,aAAa,QAAS,qBACtCF,gBAAgBF,UACZ,iEACkDH,OAAOQ,QADzD,kBAKAC,WAAaR,WAAWS,QAAQ,MAGE,OAAlCD,WAAWE,oBACXF,WAAWE,mBAAmBC,UAAUC,SAAS,sBACjDJ,WAAWE,mBAAmBG,SAIlCL,WAAWM,sBAAsB,WAAYV,wBAE1CL,OAAOE,WAEjBc,OAAM,SAASC,OACZC,OAAOC,QAAQF,MAAMA,UAG7B7B,SAASgC,KAAKtB,kBAIZuB,QAAQC,IAAIlC,gCAGLmC,gBAAgB,CACzBf,QAASb,MAAQ,OAASF,MAAQ,mCAClC+B,KAAO7B,QAAUF,MAAS,UAAY"} \ No newline at end of file diff --git a/amd/src/dashboard.js b/amd/src/dashboard.js new file mode 100644 index 0000000..3b96837 --- /dev/null +++ b/amd/src/dashboard.js @@ -0,0 +1,161 @@ +import {schedulePushTask} from "./push_tasks"; +import notification from "core/notification"; + +export const init = () => { + // Find the page element. + let page = document.getElementById("page"); + + // Find the module delivery table selector. + let tableSelector = document.getElementById("module-delivery-selector"); + + // Jump to the selected module delivery table when the user selects a module delivery. + tableSelector.addEventListener("change", function() { + // Find the selected table by ID. + let selectedTable = document.getElementById(tableSelector.value); + + // Calculate the scroll position to be 100 pixels above the table. + if (selectedTable) { + let offset = -100; + let tablePosition = selectedTable.getBoundingClientRect().top; + let scrollPosition = page.scrollTop + tablePosition + offset; + + // Scroll to the calculated position. + page.scrollTo({ + top: scrollPosition, + behavior: "smooth" + }); + } + }); + + // Find the back to top button. + let backToTopButton = document.getElementById("backToTopButton"); + + // Show the button when the user scrolls down 100 pixels from the top of the page. + page.addEventListener("scroll", function() { + if (page.scrollTop >= 100) { + backToTopButton.style.display = "block"; + } else { + backToTopButton.style.display = "none"; + } + }); + + // Scroll to the top of the page when the button is clicked. + backToTopButton.addEventListener("click", function() { + page.scrollTo({top: 0, behavior: "smooth"}); + tableSelector.selectedIndex = 0; + }); + + // Get all the push buttons that are not disabled. + let mabpushbuttons = document.querySelectorAll(".push-mark-button:not([disabled])"); + + // Push grades when the user clicks on each enabled push button. + mabpushbuttons.forEach(function(button) { + button.addEventListener("click", function() { + pushgrade(this); + }); + }); + + // Get the push all button. + let pushallbutton = document.getElementById("push-all-button"); + + // Push grades for all the not disabled push buttons when the user clicks on the push all button. + pushallbutton.addEventListener("click", async function() { + // Get the updated not disabled push buttons. + let mabpushbuttons = document.querySelectorAll(".push-mark-button:not([disabled])"); + + // Number of not disabled push buttons. + let total = mabpushbuttons.length; + let count = 0; + + // Create an array to hold all the Promises. + let promises = []; + + // Push grades to SITS for each MAB. + mabpushbuttons.forEach(function(button) { + // Create a Promise for each button and push it into the array. + let promise = pushgrade(button) + .then(function(result) { + if (result) { + count = count + 1; + } + return result; + }).catch(function(error) { + window.console.error(error); + }); + + promises.push(promise); + }); + + // Wait for all Promises to resolve. + await Promise.all(promises); + + // Scroll to the top of the page so that the user can see the notification. + page.scrollTo({top: 0, behavior: "instant"}); + + // Show the notification. + notification.addNotification({ + message: count + ' of ' + total + ' push tasks have been scheduled.', + type: (count === total) ? 'success' : 'warning' + }); + }); +}; + +/** + * Schedule a push task when the user clicks on a push button. + * + * @param {HTMLElement} button The button element. + * @return {Promise} Promise. + */ +async function pushgrade(button) { + try { + // Get the assessment mapping ID from the button. + let assessmentmappingid = button.getAttribute("data-assessmentmappingid"); + + // Schedule a push task. + let result = await schedulePushTask(assessmentmappingid); + + // Check if the push task is successfully scheduled. + if (result.success) { + // Update the icon. + let icon = button.parentNode.parentNode.querySelector("span:last-child"); + icon.innerHTML = ''; + + // Disable the push button after scheduled push task successfully. + button.disabled = true; + + // Remove the tooltip (for Firefox and Safari). + let tooltipid = button.getAttribute("aria-describedby"); + if (tooltipid !== null && document.getElementById(tooltipid) !== null) { + document.getElementById(tooltipid).remove(); + } + } else { + // Create an error message row. + let errormessagerow = document.createElement("tr"); + + // Set the class and content of the error message row. + errormessagerow.setAttribute("class", "error-message-row"); + errormessagerow.innerHTML = + '' + + '' + + ''; + + // Find the closest row to the button. + let currentrow = button.closest("tr"); + + // Remove the existing error message row if it exists. + if (currentrow.nextElementSibling !== null && + currentrow.nextElementSibling.classList.contains("error-message-row")) { + currentrow.nextElementSibling.remove(); + } + + // Insert the error message row after the current row. + currentrow.insertAdjacentElement("afterend", errormessagerow); + } + + return result.success; + } catch (error) { + window.console.error(error); + return false; + } +} diff --git a/amd/src/push_tasks.js b/amd/src/push_tasks.js new file mode 100644 index 0000000..cc336b9 --- /dev/null +++ b/amd/src/push_tasks.js @@ -0,0 +1,23 @@ +import Ajax from 'core/ajax'; + +/** + * Schedule a task to push grades to SITS. + * + * @param {string} assessmentmappingid The assessment mapping ID. + * @return {Promise} Promise. + */ +export const schedulePushTask = (assessmentmappingid) => { + return new Promise((resolve, reject) => { + Ajax.call([{ + methodname: 'local_sitsgradepush_schedule_push_task', + args: { + 'assessmentmappingid': assessmentmappingid, + }, + }])[0].done(function(response) { + resolve(response); + }).fail(function(err) { + window.console.log(err); + reject(err); + }); + }); +}; diff --git a/amd/src/sitsgradepush.js b/amd/src/sitsgradepush.js index 13370b1..08e666e 100644 --- a/amd/src/sitsgradepush.js +++ b/amd/src/sitsgradepush.js @@ -1,7 +1,7 @@ -import Ajax from 'core/ajax'; +import {schedulePushTask} from "./push_tasks"; import notification from 'core/notification'; -export const init = (coursemoduleid) => { +export const init = (coursemoduleid, mappingids) => { // Get the push button. let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async'); @@ -10,40 +10,61 @@ export const init = (coursemoduleid) => { return; } - // Add an event listener to the push button. - pushbuton.addEventListener('click', (e) => { + let promises = []; + + // Schedule a push task for each assessment mapping. + pushbuton.addEventListener('click', async(e) => { e.preventDefault(); - // Schedule a task to push grades to SITS. - schedulePushTask(coursemoduleid); - }); -}; -/** - * Schedule a task to push grades to SITS. - * @param {int} coursemoduleid - */ -function schedulePushTask(coursemoduleid) { - Ajax.call([{ - methodname: 'local_sitsgradepush_schedule_push_task', - args: { - 'coursemoduleid': coursemoduleid, - }, - }])[0].done(function(response) { + // Number of assessment mappings. + let total = mappingids.length; + let count = 0; + + // Schedule a task to push grades to SITS for each assessment mapping. + mappingids.forEach(function(mappingid) { + let promise = schedulePushTask(mappingid) + .then(function(result) { + let taskstatus = document.getElementById('taskstatus-' + mappingid); + if (result.success) { + count = count + 1; + taskstatus.innerHTML = ' ' + result.status; + } else { + // Create an error message row. + let errormessagerow = document.createElement("tr"); + errormessagerow.setAttribute("class", "error-message-row"); + errormessagerow.innerHTML = + '' + + '' + + ''; + + // Find the closest row to the assessment mapping. + let currentrow = taskstatus.closest("tr"); + + // Remove the existing error message row if it exists. + if (currentrow.nextElementSibling !== null && + currentrow.nextElementSibling.classList.contains("error-message-row")) { + currentrow.nextElementSibling.remove(); + } + + // Insert the error message row for the assessment mapping. + currentrow.insertAdjacentElement("afterend", errormessagerow); + } + return result.success; + }) + .catch(function(error) { + window.console.error(error); + }); + + promises.push(promise); + }); + + // Wait for all the push tasks to be scheduled. + await Promise.all(promises); + + // Display a notification. notification.addNotification({ - message: response.message, - type: response.success ? 'success' : 'warning' + message: count + ' of ' + total + ' push tasks have been scheduled.', + type: (count === total) ? 'success' : 'warning' }); - if (response.success) { - // Get the push button. - let pushbuton = document.getElementById('local_sitsgradepush_pushbutton_async'); - - // Update the button text. - pushbuton.innerHTML = response.status; - - // Disable the push button after scheduled push task successfully. - pushbuton.disabled = true; - } - }).fail(function(err) { - window.console.log(err); }); -} +}; diff --git a/classes/external/schedule_push_task.php b/classes/external/schedule_push_task.php index 1760e4c..3e9e40e 100644 --- a/classes/external/schedule_push_task.php +++ b/classes/external/schedule_push_task.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . namespace local_sitsgradepush\external; +use context_course; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; @@ -37,7 +38,7 @@ class schedule_push_task extends external_api { */ public static function execute_parameters() { return new external_function_parameters([ - 'coursemoduleid' => new external_value(PARAM_INT, 'Course module ID', VALUE_REQUIRED), + 'assessmentmappingid' => new external_value(PARAM_INT, 'Assessment mapping ID', VALUE_REQUIRED), ]); } @@ -57,14 +58,19 @@ public static function execute_returns() { /** * Schedule a push task. * - * @param int $coursemoduleid + * @param int $assessmentmappingid * @return array */ - public static function execute(int $coursemoduleid) { + public static function execute(int $assessmentmappingid) { + global $USER; try { - $params = self::validate_parameters(self::execute_parameters(), ['coursemoduleid' => $coursemoduleid]); + $params = self::validate_parameters( + self::execute_parameters(), + ['assessmentmappingid' => $assessmentmappingid] + ); $manager = manager::get_manager(); - $manager->schedule_push_task($params['coursemoduleid']); + $manager->schedule_push_task($params['assessmentmappingid']); + return [ 'success' => true, 'status' => get_string('task:status:requested', 'local_sitsgradepush'), diff --git a/classes/manager.php b/classes/manager.php index 8c9504a..d896e3e 100644 --- a/classes/manager.php +++ b/classes/manager.php @@ -235,61 +235,83 @@ public function get_component_grade_options(int $courseid, mixed $coursemoduleid // Get module occurrences from portico enrolments block. $modocc = \block_portico_enrolments\manager::get_modocc_mappings($courseid); - // Get local component grades records. - $records = $this->get_local_component_grades($modocc); - // Fetch component grades from SITS. // TODO: Could do some caching here. $this->fetch_component_grades_from_sits($modocc); - $records = $this->get_local_component_grades($modocc); + $modocccomponentgrades = $this->get_local_component_grades($modocc); - // Loop through records and build options. - foreach ($records as $record) { - $option = new \stdClass(); - $option->selected = ''; + // Loop through each component grade for each module occurrence and build the options. + foreach ($modocccomponentgrades as $occ) { + foreach ($occ->componentgrades as $mab) { + $mab->selected = ''; - // This component grade is mapped to this activity, so set selected. - if (!empty($record->coursemoduleid) && $record->coursemoduleid == $coursemoduleid) { - $option->selected = 'selected'; + // This component grade is mapped to this activity, so set selected. + if (!empty($mab->coursemoduleid) && $mab->coursemoduleid == $coursemoduleid) { + $mab->selected = 'selected'; + } + $mab->text = sprintf( + '%s-%s-%s-%s-%s %s', + $mab->modcode, + $mab->academicyear, + $mab->periodslotcode, + $mab->modocc, + $mab->mabseq, + $mab->mabname + ); + $mab->value = $mab->id; } - $option->text = sprintf( - '%s-%s-%s-%s-%s %s', - $record->modcode, - $record->academicyear, - $record->periodslotcode, - $record->modocc, - $record->mabseq, - $record->mabname - ); - $option->value = $record->id; - $options[] = $option; } - return $options; + return $modocccomponentgrades; } /** - * Get component grades from local DB. + * Decode the module occurrence's module availability code, e.g. A6U, A7P. * - * @param array $modocc + * @param string $mav * @return array - * @throws \dml_exception + */ + public function get_decoded_mod_occ_mav(string $mav): array { + $decoded = []; + if (!empty($mav)) { + // Get level. + $decoded['level'] = substr($mav, 1, 1); + // Get graduate type. + $graduatetype = substr($mav, 2, 1); + $decoded['graduatetype'] = match ($graduatetype) { + 'U' => 'UNDERGRADUATE', + 'P' => 'POSTGRADUATE', + default => 'UNKNOWN', + }; + } + + return $decoded; + } + + /** + * Get component grades from local DB. + * + * @param array $modocc module occurrences of the current course. + * @return array Array of module occurrences with component grades. + * @throws \dml_exception|\coding_exception */ public function get_local_component_grades(array $modocc): array { global $DB; - $componentgrades = []; + $moduledeliveries = []; foreach ($modocc as $occ) { - $sql = "SELECT cg.*, am.coursemoduleid AS 'coursemoduleid' + $sql = "SELECT cg.*, am.coursemoduleid AS 'coursemoduleid', am.id AS 'assessmentmappingid' FROM {" . self::TABLE_COMPONENT_GRADE . "} cg LEFT JOIN {" . self::TABLE_ASSESSMENT_MAPPING . "} am ON cg.id = am.componentgradeid WHERE cg.modcode = :modcode AND cg.modocc = :modocc AND cg.academicyear = :academicyear AND cg.periodslotcode = :periodslotcode"; $params = [ - 'modcode' => $occ->mod_code, 'modocc' => $occ->mod_occ_mav, + 'modcode' => $occ->mod_code, + 'modocc' => $occ->mod_occ_mav, 'academicyear' => $occ->mod_occ_year_code, - 'periodslotcode' => $occ->mod_occ_psl_code, ]; + 'periodslotcode' => $occ->mod_occ_psl_code, + ]; // Get AST codes. if ($astcodes = self::get_moodle_ast_codes()) { @@ -316,11 +338,31 @@ public function get_local_component_grades(array $modocc): array { } } - // Merge results for multiple module occurrences. - $componentgrades = array_merge($componentgrades, $records); + // Create module delivery object with component grades data. + $moduledelivery = new \stdClass(); + $moduledelivery->modcode = $occ->mod_code; + $moduledelivery->modocc = $occ->mod_occ_mav; + $moduledelivery->academicyear = $occ->mod_occ_year_code; + $moduledelivery->periodslotcode = $occ->mod_occ_psl_code; + $moduledelivery->modoccname = $occ->mod_occ_name; + $moduledelivery->componentgrades = $records; + + // Get MAP code from the first record. + if (!empty($records)) { + $moduledelivery->mapcode = $records[array_key_first($records)]->mapcode; + } else { + $moduledelivery->mapcode = ''; + } + + // Get decoded module occurrence's MAV. + $decodedmav = $this->get_decoded_mod_occ_mav($occ->mod_occ_mav); + $moduledelivery->level = $decodedmav['level']; + $moduledelivery->graduatetype = $decodedmav['graduatetype']; + $moduledeliveries[] = $moduledelivery; } - return $componentgrades; + // Return module deliveries with component grades info. + return $moduledeliveries; } /** @@ -928,32 +970,39 @@ public function get_moodle_ast_codes_work_with_exam_room_code(): bool|array { /** * Schedule push task. * - * @param int $coursemoduleid + * @param int $assessmentmappingid Assessment mapping id * @return bool|int * @throws \dml_exception */ - public function schedule_push_task(int $coursemoduleid): bool|int { + public function schedule_push_task(int $assessmentmappingid): bool|int { global $DB, $USER; - // Check course module exists. - if (!$DB->record_exists('course_modules', ['id' => $coursemoduleid])) { - throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush'); - } - - // Check if the course module has been mapped to an assessment component. - if (!$DB->record_exists('local_sitsgradepush_mapping', ['coursemoduleid' => $coursemoduleid])) { - throw new \moodle_exception('error:assessmentisnotmapped', 'local_sitsgradepush'); + // Check if the assessment mapping exists. + $mapping = $DB->get_record(self::TABLE_ASSESSMENT_MAPPING, ['id' => $assessmentmappingid]); + if (!$mapping) { + throw new \moodle_exception('error:assessmentmapping', 'local_sitsgradepush', '', $assessmentmappingid); } // Check if there is already in one of the following status: added, queued, processing. - if (self::get_pending_task_in_queue($coursemoduleid)) { + if (self::get_pending_task_in_queue($mapping->id)) { throw new \moodle_exception('error:duplicatedtask', 'local_sitsgradepush'); } + // Check course module exists. + if (!$DB->record_exists('course_modules', ['id' => $mapping->coursemoduleid])) { + throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush'); + } + + // Check if the assessment component exists. + if (!$DB->record_exists('local_sitsgradepush_mab', ['id' => $mapping->componentgradeid])) { + throw new \moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $mapping->componentgradeid); + } + + // Create and insert the task. $task = new \stdClass(); $task->userid = $USER->id; $task->timescheduled = time(); - $task->coursemoduleid = $coursemoduleid; + $task->assessmentmappingid = $assessmentmappingid; return $DB->insert_record('local_sitsgradepush_tasks', $task); } @@ -961,24 +1010,25 @@ public function schedule_push_task(int $coursemoduleid): bool|int { /** * Get push task in status requested, queued or processing for a course module. * - * @param int $coursemoduleid - * @return \stdClass|bool - * @throws \coding_exception - * @throws \dml_exception + * @param int $assessmentmappingid Assessment mapping id + * @return \stdClass|bool false if no task found, otherwise return the task object with button label. + * @throws \coding_exception|\dml_exception */ - public function get_pending_task_in_queue(int $coursemoduleid): bool|\stdClass { + public function get_pending_task_in_queue(int $assessmentmappingid): bool|\stdClass { global $DB; + $sql = 'SELECT * FROM {local_sitsgradepush_tasks} - WHERE coursemoduleid = :coursemoduleid AND status IN (:status1, :status2, :status3) + WHERE assessmentmappingid = :assessmentmappingid AND status IN (:status1, :status2, :status3) ORDER BY id DESC'; $params = [ - 'coursemoduleid' => $coursemoduleid, + 'assessmentmappingid' => $assessmentmappingid, 'status1' => self::PUSH_TASK_STATUS_REQUESTED, 'status2' => self::PUSH_TASK_STATUS_QUEUED, 'status3' => self::PUSH_TASK_STATUS_PROCESSING, ]; + // Add button label to the task object. if ($result = $DB->get_record_sql($sql, $params)) { switch ($result->status) { case self::PUSH_TASK_STATUS_REQUESTED: @@ -1001,25 +1051,27 @@ public function get_pending_task_in_queue(int $coursemoduleid): bool|\stdClass { /** * Get last finished push task for a course module. * - * @param int $coursemoduleid - * @return false|mixed - * @throws \dml_exception + * @param int $assessmentmappingid Assessment mapping id + * @return false|mixed Returns false if no task found, otherwise return the task object with status text. + * @throws \dml_exception|\coding_exception */ - public function get_last_finished_push_task(int $coursemoduleid): mixed { + public function get_last_finished_push_task(int $assessmentmappingid): mixed { global $DB; + // Get the last task for the course module. $sql = 'SELECT * FROM {local_sitsgradepush_tasks} - WHERE coursemoduleid = :coursemoduleid AND status IN (:status1, :status2) + WHERE assessmentmappingid = :assessmentmappingid AND status IN (:status1, :status2) ORDER BY id DESC LIMIT 1'; $params = [ - 'coursemoduleid' => $coursemoduleid, + 'assessmentmappingid' => $assessmentmappingid, 'status1' => self::PUSH_TASK_STATUS_COMPLETED, 'status2' => self::PUSH_TASK_STATUS_FAILED, ]; + // Add status text to the task object. if ($task = $DB->get_record_sql($sql, $params)) { switch ($task->status) { case self::PUSH_TASK_STATUS_COMPLETED: diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 5be44de..a4c6411 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -17,6 +17,8 @@ namespace local_sitsgradepush\output; use local_sitsgradepush\errormanager; +use local_sitsgradepush\manager; +use moodle_page; use plugin_renderer_base; /** @@ -28,19 +30,50 @@ * @author Alex Yeung */ class renderer extends plugin_renderer_base { + + /** @var string Push task status - requested */ + const PUSH_STATUS_ICON_REQUESTED = 'requested'; + + /** @var string Push task status - queued */ + const PUSH_STATUS_ICON_QUEUED = 'queued'; + + /** @var string Push task status - processing */ + const PUSH_STATUS_ICON_PROCESSING = 'processing'; + + /** @var string Push task status - has push records */ + const PUSH_STATUS_ICON_HAS_PUSH_RECORDS = 'has_push_records'; + + /** @var string Push task status - no push records */ + const PUSH_STATUS_ICON_NO_PUSH_RECORDS = 'no_push_records'; + + /** @var manager|null Manager */ + private ?manager $manager; + + /** + * Constructor. + * + * @param moodle_page $page + * @param string $target + */ + public function __construct(moodle_page $page, $target) { + parent::__construct($page, $target); + $this->manager = manager::get_manager(); + } + /** * Render a simple button. * - * @param string $id - * @param string $name - * @param string $disabled - * @return string + * @param string $id Button ID + * @param string $name Button name + * @param string $disabled Disabled attribute + * @param string $class Class attribute + * @return string Rendered HTML * @throws \moodle_exception */ - public function render_button(string $id, string $name, string $disabled = '') : string { + public function render_button(string $id, string $name, string $disabled = '', $class = '') : string { return $this->output->render_from_template( 'local_sitsgradepush/button', - ['id' => $id, 'name' => $name, 'disabled' => $disabled] + ['id' => $id, 'name' => $name, 'disabled' => $disabled, 'class' => $class] ); } @@ -60,8 +93,8 @@ public function render_link(string $id, string $name, string $url) : string { /** * Render the assessment push status table. * - * @param \stdClass $mapping - * @return string + * @param \stdClass $mapping Assessment mapping + * @return string Rendered HTML * @throws \moodle_exception */ public function render_assessment_push_status_table(\stdClass $mapping) : string { @@ -81,12 +114,145 @@ public function render_assessment_push_status_table(\stdClass $mapping) : string $students = $mapping->students; } + $lasttasktext = null; + $taskstatustext = null; + $mappingid = null; + + // Add last task details and push task status to the mapping object if any. + if (!empty($mapping->id)) { + $mappingid = $mapping->id; + $lasttasktext = $this->get_last_push_task_time($mapping->id); + if ($taskstatus = $this->get_assessment_mapping_status_icon($mapping->id)) { + if ($taskstatus->status !== self::PUSH_STATUS_ICON_HAS_PUSH_RECORDS && + $taskstatus->status !== self::PUSH_STATUS_ICON_NO_PUSH_RECORDS) { + $taskstatustext = $taskstatus->statusicon . ' ' . $taskstatus->statustext; + } + } + } + + // Check if there is any task info to display. + $additionalinfo = $lasttasktext || $taskstatustext; + + // Render the table. return $this->output->render_from_template('local_sitsgradepush/assessmentgrades', [ + 'mappingid' => $mappingid, 'tabletitle' => $mapping->formattedname, 'students' => $students, + 'lasttask' => $lasttasktext, + 'taskstatus' => $taskstatustext, + 'additionalinfo' => $additionalinfo, ]); } + /** + * Render the sits grade push dashboard. + * + * @param array $moduledeliveries Module deliveries + * @param int $courseid Course ID + * @return string Rendered HTML + * @throws \coding_exception + * @throws \dml_exception + * @throws \moodle_exception + */ + public function render_dashboard(array $moduledeliveries, int $courseid) : string { + // Set default value for the select module delivery dropdown list. + $options[] = (object) ['value' => 'none', 'name' => 'NONE']; + + $moduledeliverytables = ''; + // Prepare the content for each module delivery table. + foreach ($moduledeliveries as $moduledelivery) { + // Set options for the select module delivery dropdown list. + $tableid = sprintf( + '%s-%s-%s-%s', + $moduledelivery->modcode, + $moduledelivery->modocc, + $moduledelivery->periodslotcode, + $moduledelivery->academicyear + ); + $option = new \stdClass(); + $option->value = $option->name = $tableid; + $options[] = $option; + + // Add assessment mapping info if any. + $componentgrades = []; + if (!empty($moduledelivery->componentgrades) && is_array($moduledelivery->componentgrades)) { + $componentgrades = array_values($moduledelivery->componentgrades); + foreach ($componentgrades as $componentgrade) { + // No course module ID means the MAB is not mapped to any activity. + if (empty($componentgrade->coursemoduleid)) { + // Disable the change source button and push grade button if the MAB is not mapped to any activity. + $componentgrade->disablechangesourcebutton = ' disabled'; + $componentgrade->disablepushgradebutton = ' disabled'; + continue; + } + + // Get the assessment mapping status. + if ($coursemodule = get_coursemodule_from_id('', $componentgrade->coursemoduleid)) { + $assessmentmapping = new \stdClass(); + $assessmentmapping->info = + $assessmentmapping->id = $componentgrade->assessmentmappingid; + $assessmentmapping->type = get_module_types_names()[$coursemodule->modname]; + $assessmentmapping->name = $coursemodule->name; + $assessmentmapping->url = new \moodle_url( + '/mod/' . $coursemodule->modname . '/view.php', + ['id' => $coursemodule->id] + ); + $assessmentmapping->status = + $this->get_assessment_mapping_status_icon($componentgrade->assessmentmappingid); + $assessmentmapping->statusicon = $assessmentmapping->status->statusicon; + $componentgrade->assessmentmapping = $assessmentmapping; + $componentgrade->disablechangesourcebutton = + $this->disable_change_source_button($componentgrade->assessmentmappingid) ? ' disabled' : ''; + $componentgrade->disablepushgradebutton = + $this->disable_push_grade_button($assessmentmapping->status->status, $courseid) ? ' disabled' : ''; + } else { + throw new \moodle_exception('error:invalidcoursemoduleid', 'local_sitsgradepush'); + } + } + } + + // Render the module delivery table. + $moduledeliverytables .= $this->output->render_from_template( + 'local_sitsgradepush/module_delivery_table', + [ + 'tableid' => $tableid, + 'modcode' => $moduledelivery->modcode, + 'academicyear' => $moduledelivery->academicyear, + 'level' => $moduledelivery->level, + 'graduatetype' => $moduledelivery->graduatetype, + 'mapcode' => $moduledelivery->mapcode, + 'componentgrades' => $componentgrades, + ]); + } + + // Render the module delivery selector. + $moduledeliveryselector = $this->output->render_from_template( + 'local_sitsgradepush/selectelement', + [ + 'selectid' => 'module-delivery-selector', + 'label' => get_string('label:jumpto', 'local_sitsgradepush'), + 'options' => $options, + ] + ); + + // Render the push all button. + $pushallbutton = $this->output->render_from_template( + 'local_sitsgradepush/button', + [ + 'id' => 'push-all-button', + 'name' => get_string('label:pushall', 'local_sitsgradepush'), + 'disabled' => '', + 'class' => 'sitgradepush-btn-center', + ] + ); + + // Render the back to top button. + $backtotopbutton = $this->output->render_from_template('local_sitsgradepush/back_to_top_button', []); + + // Return the combined HTML. + return $moduledeliveryselector . $moduledeliverytables . $pushallbutton . $backtotopbutton; + } + /** * Get the last push result label. * @@ -112,4 +278,112 @@ private function get_label_html(int $errortype = null) : string { return ''.errormanager::get_error_label($errortype).' '; } } + + /** + * Get the last push task time. + * + * @param int $assessmentmappingid Assessment mapping ID + * @return string|null Last push task time + * @throws \coding_exception + * @throws \dml_exception + */ + private function get_last_push_task_time(int $assessmentmappingid) { + // Add last task details to the mapping if any. + $time = null; + if ($lasttask = $this->manager->get_last_finished_push_task($assessmentmappingid)) { + $time = get_string( + 'label:lastpushtext', + 'local_sitsgradepush', [ + 'statustext' => $lasttask->statustext, + 'date' => date('d/m/Y', $lasttask->timeupdated), + 'time' => date('g:i:s a', $lasttask->timeupdated), ]); + } + + return $time; + } + + /** + * Get the assessment mapping status icon. + * @param int $assessmentmappingid Assessment mapping ID + * @return \stdClass Assessment mapping status icon + * @throws \coding_exception + * @throws \dml_exception + */ + private function get_assessment_mapping_status_icon(int $assessmentmappingid) { + $manager = manager::get_manager(); + + // Work out the status of this assessment mapping. + if ($task = $manager->get_pending_task_in_queue($assessmentmappingid)) { + $status = match (intval($task->status)) { + manager::PUSH_TASK_STATUS_REQUESTED => self::PUSH_STATUS_ICON_REQUESTED, + manager::PUSH_TASK_STATUS_QUEUED => self::PUSH_STATUS_ICON_QUEUED, + manager::PUSH_TASK_STATUS_PROCESSING => self::PUSH_STATUS_ICON_PROCESSING, + }; + } else { + $status = $manager->has_grades_pushed($assessmentmappingid) ? + self::PUSH_STATUS_ICON_HAS_PUSH_RECORDS : self::PUSH_STATUS_ICON_NO_PUSH_RECORDS; + } + + $result = new \stdClass(); + $result->status = $status; + switch ($status) { + case self::PUSH_STATUS_ICON_REQUESTED: + $result->statusicon = ''; + $result->statustext = get_string('task:status:requested', 'local_sitsgradepush'); + break; + case self::PUSH_STATUS_ICON_QUEUED: + $result->statusicon = ''; + $result->statustext = get_string('task:status:queued', 'local_sitsgradepush'); + break; + case self::PUSH_STATUS_ICON_PROCESSING: + $result->statusicon = ''; + $result->statustext = get_string('task:status:processing', 'local_sitsgradepush'); + break; + case self::PUSH_STATUS_ICON_HAS_PUSH_RECORDS: + $result->statusicon = ''; + $result->statustext = get_string('pushrecordsexist', 'local_sitsgradepush'); + break; + default: + $result->statusicon = ''; + $result->statustext = get_string('pushrecordsnotexist', 'local_sitsgradepush'); + break; + } + + return $result; + } + + /** + * Disable the change source button if the grades have been pushed. + * + * @param int $assessmentmappingid Assessment mapping ID + * @return bool + * @throws \dml_exception + */ + private function disable_change_source_button(int $assessmentmappingid) : bool { + return $this->manager->has_grades_pushed($assessmentmappingid); + } + + /** + * Disable the push grade button if the push task is in progress. + * + * @param string $pushstatus Push task status + * @param int $courseid Course ID + * @return bool + */ + private function disable_push_grade_button(string $pushstatus, int $courseid) : bool { + // Disable the push grade button if the user does not have the capability. + if (!has_capability('local/sitsgradepush:pushgrade', \context_course::instance($courseid))) { + return true; + } + + return match($pushstatus) { + self::PUSH_STATUS_ICON_REQUESTED, self::PUSH_STATUS_ICON_QUEUED, self::PUSH_STATUS_ICON_PROCESSING => true, + default => false, + }; + } } diff --git a/classes/task/adhoctask.php b/classes/task/adhoctask.php index 723c090..d59fc6d 100644 --- a/classes/task/adhoctask.php +++ b/classes/task/adhoctask.php @@ -58,11 +58,21 @@ public function execute() { throw new \moodle_exception('error:tasknotfound', 'local_sitsgradepush'); } + // Check assessment mapping exists. + if (!$assessmentmapping = $DB->get_record('local_sitsgradepush_mapping', ['id' => $task->assessmentmappingid])) { + throw new \moodle_exception('error:mappingnotfound', 'local_sitsgradepush'); + } + // Get the course module. - if (!$coursemodule = get_coursemodule_from_id(null, $task->coursemoduleid)) { + if (!$coursemodule = get_coursemodule_from_id(null, $assessmentmapping->coursemoduleid)) { throw new \moodle_exception('error:coursemodulenotfound', 'local_sitsgradepush'); } + // Check the MAB exists. + if (!$DB->get_record('local_sitsgradepush_mab', ['id' => $assessmentmapping->componentgradeid])) { + throw new \moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $assessmentmapping->componentgradeid); + } + // Log start. mtrace(date('Y-m-d H:i:s', time()) . ' : ' . 'Processing push task [#' . $data->taskid . ']'); @@ -73,10 +83,16 @@ public function execute() { $assessment = assessmentfactory::get_assessment($coursemodule); if ($assessmentdata = $manager->get_assessment_data($assessment)) { foreach ($assessmentdata['mappings'] as $mapping) { + // If the mapping is not the assessment mapping stated in the task, skip it. + if ($mapping->id != $assessmentmapping->id) { + continue; + } + // Skip if there is no student in the mapping. if (empty($mapping->students)) { continue; } + // Push grades for each student in the mapping. foreach ($mapping->students as $student) { $manager->push_grade_to_sits($mapping, $student->userid); diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..84da444 --- /dev/null +++ b/dashboard.php @@ -0,0 +1,96 @@ +. + +/** + * Dashboard page for local_sitsgradepush to show course level assessment mappings. + * + * @package local_sitsgradepush + * @copyright 2023 onwards University College London {@link https://www.ucl.ac.uk/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Alex Yeung + */ + +namespace local_sitsgradepush; + +use context_course; +use moodle_exception; +use moodle_url; + +require_once('../../config.php'); + +// Course ID. +$courseid = required_param('id', PARAM_INT); + +// Get course context. +$context = context_course::instance($courseid); + +// Get course instance. +if (!$course = get_course($courseid)) { + throw new moodle_exception('course not found.', 'local_sitsgradepush'); +} + +// Make sure user is authenticated. +require_login(); + +// Check user's capability. +require_capability('local/sitsgradepush:mapassessment', $context); + +// Set the required data into the PAGE object. +$param = ['id' => $courseid]; +$url = new moodle_url('/local/sitsgradepush/dashboard.php', $param); +$PAGE->set_context($context); +$PAGE->set_url($url); +$PAGE->set_title('SITS Grade Push Dashboard'); +$PAGE->set_secondary_navigation(false); + +// Set the breadcrumbs. +$PAGE->navbar->add(get_string('courses'), new moodle_url('/course/index.php')); +$PAGE->navbar->add($course->fullname, new moodle_url('/course/view.php', ['id' => $courseid])); +$PAGE->navbar->add('SITS Grade Push', + new moodle_url('/local/sitsgradepush/dashboard.php', $param)); + +// Page header. +echo $OUTPUT->header(); + +// Display a notification if user does not have the capability to push grades. +if (!has_capability('local/sitsgradepush:pushgrade', $context)) { + // Add notification. + echo $OUTPUT->notification(get_string('error:pushgradespermission', 'local_sitsgradepush'), 'info'); +} + +// Get renderer. +$renderer = $PAGE->get_renderer('local_sitsgradepush'); + +// Get the component grades. +$manager = manager::get_manager(); + +// Get the component grades for each module delivery. +$result = $manager->get_component_grade_options($courseid, null); + +// Render the dashboard. +if (!empty($result)) { + echo '
'; + echo $renderer->render_dashboard($result, $courseid); + echo '
'; +} else { + echo get_string('error:nomoduledeliveryfound', 'local_sitsgradepush'); +} + +// Initialise the javascript. +$PAGE->requires->js_call_amd('local_sitsgradepush/dashboard', 'init', []); + +// Page footer. +echo $OUTPUT->footer(); diff --git a/db/install.xml b/db/install.xml index 3ca4204..b26a722 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -89,7 +89,7 @@ - + @@ -97,7 +97,7 @@ - + diff --git a/db/upgrade.php b/db/upgrade.php index d598e55..dee807f 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -302,5 +302,70 @@ function xmldb_local_sitsgradepush_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2023110600, 'local', 'sitsgradepush'); } + if ($oldversion < 2023112400) { + $table = new xmldb_table('local_sitsgradepush_tasks'); + + // Define index idx_coursemoduleid (not unique) to be dropped form local_sitsgradepush_tasks. + $index = new xmldb_index('idx_coursemoduleid', XMLDB_INDEX_NOTUNIQUE, ['coursemoduleid']); + + // Conditionally launch drop index idx_coursemoduleid. + if ($dbman->index_exists($table, $index)) { + $dbman->drop_index($table, $index); + } + + // Define field assessmentmappingid to be added to local_sitsgradepush_tasks. + $field = new xmldb_field('assessmentmappingid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'status'); + + // Conditionally launch add field assessmentmappingid. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Patch task table. + $tasks = $DB->get_records('local_sitsgradepush_tasks'); + if (!empty($tasks)) { + foreach ($tasks as $task) { + $mappings = $DB->get_records('local_sitsgradepush_mapping', ['coursemoduleid' => $task->coursemoduleid]); + if (!empty($mappings)) { + foreach ($mappings as $mapping) { + $inserttask = new stdClass(); + $inserttask->userid = $task->userid; + $inserttask->timescheduled = $task->timescheduled; + $inserttask->timeupdated = $task->timeupdated; + $inserttask->status = $task->status; + $inserttask->coursemoduleid = $task->coursemoduleid; + $inserttask->assessmentmappingid = $mapping->id; + $inserttask->info = $task->info; + $inserttask->errlogid = $task->errlogid; + $DB->insert_record('local_sitsgradepush_tasks', $inserttask); + } + } + $DB->delete_records('local_sitsgradepush_tasks', ['id' => $task->id]); + } + } + + // Launch change of nullability for field assessmentmappingid. + $dbman->change_field_notnull($table, $field); + + // Define index assessmentmappingid_idx (not unique) to be added to local_sitsgradepush_tasks. + $index = new xmldb_index('idx_assessmentmappingid', XMLDB_INDEX_NOTUNIQUE, ['assessmentmappingid']); + + // Conditionally launch add index idx_assessmentmappingid. + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Define field coursemoduleid to be dropped from local_sitsgradepush_tasks. + $field = new xmldb_field('coursemoduleid'); + + // Conditionally launch drop field coursemoduleid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Sitsgradepush savepoint reached. + upgrade_plugin_savepoint(true, 2023112400, 'local', 'sitsgradepush'); + } + return true; } diff --git a/index.php b/index.php index 8cbaa61..d4f036f 100644 --- a/index.php +++ b/index.php @@ -75,7 +75,7 @@ echo '
'; // Assessment name. -echo '

Sits grade push history

'; +echo '

' . get_string('index:header', 'local_sitsgradepush') . '

'; $manager = manager::get_manager(); // Get assessment. @@ -84,6 +84,8 @@ // Get page content. $content = $manager->get_assessment_data($assessment); +$mappingids = []; + if (!empty($content)) { // Check if asynchronous grade push is enabled. $async = get_config('local_sitsgradepush', 'async'); @@ -93,12 +95,6 @@ // Get push button label. $buttonlabel = get_string('label:pushgrade', 'local_sitsgradepush'); $disabled = ''; - - // Check if this course module has pending task. - if ($task = $manager->get_pending_task_in_queue($coursemoduleid)) { - $buttonlabel = $task->buttonlabel; - $disabled = 'disabled'; - } } else { // Push grade and submission log. if ($pushgrade == 1) { @@ -128,21 +124,13 @@ echo $renderer->render_link('local_sitsgradepush_pushbutton', $buttonlabel, $url->out(false)); } - if ($lastfinishedtask = $manager->get_last_finished_push_task($coursemoduleid)) { - echo '

'. get_string( - 'label:lastpushtext', - 'local_sitsgradepush', [ - 'statustext' => $lastfinishedtask->statustext, - 'date' => date('d/m/Y', $lastfinishedtask->timeupdated), - 'time' => date('g:i:s a', $lastfinishedtask->timeupdated), ]) . - '

'; - } } else { echo '

' . get_string('error:assessmentisnotmapped', 'local_sitsgradepush') . '

'; } // Display grade push records for each mapping. foreach ($content['mappings'] as $mapping) { + $mappingids[] = $mapping->id; echo $renderer->render_assessment_push_status_table($mapping); } @@ -155,7 +143,8 @@ } echo '
'; -$PAGE->requires->js_call_amd('local_sitsgradepush/sitsgradepush', 'init', [$coursemoduleid]); +// Initialize javascript. +$PAGE->requires->js_call_amd('local_sitsgradepush/sitsgradepush', 'init', [$coursemoduleid, $mappingids]); // And the page footer. echo $OUTPUT->footer(); diff --git a/lang/en/local_sitsgradepush.php b/lang/en/local_sitsgradepush.php index 4b824e8..f938dbe 100644 --- a/lang/en/local_sitsgradepush.php +++ b/lang/en/local_sitsgradepush.php @@ -50,6 +50,8 @@ $string['settings:userprofilefield'] = 'User Profile Field'; $string['settings:userprofilefield:desc'] = 'User profile field for export staff'; $string['label:gradepushassessmentselect'] = 'Select SITS assessment to link to'; +$string['label:jumpto'] = 'Jump to: '; +$string['label:pushall'] = 'Push All'; $string['label:reassessmentselect'] = 'Re-assessment'; $string['label:pushgrade'] = 'Push grades'; $string['label:ok'] = 'OK'; @@ -62,25 +64,34 @@ $string['subplugintype_sitsapiclient'] = 'API client used for data integration.'; $string['cachedef_studentspr'] = 'Student\'s SPR code per SITS assessment pattern'; $string['invalidstudents'] = 'Students not valid for the mapped assessment components'; +$string['pushrecordsexist'] = 'Push records exist'; +$string['pushrecordsnotexist'] = 'No push records'; + +// Grade push index page. +$string['index:header'] = 'Sits Grade Push History'; // Error strings. -$string['error:assessmentmapping'] = 'No valid mapping or component grade. {$a}'; +$string['error:assessmentmapping'] = 'Assessment mapping is not found. ID: {$a}'; $string['error:assessmentisnotmapped'] = 'This activity is not mapped to any assessment component.'; $string['error:componentgradepushed'] = '{$a} cannot be removed because it has grade push records.'; $string['error:componentgrademapped'] = '{$a} had been mapped to another activity.'; $string['error:pastactivity'] = 'It looks like this course is from a previous academic year, mappings are not allowed.'; $string['error:mapassessment'] = 'You do not have permission to map assessment.'; +$string['error:pushgradespermission'] = 'You do not have permission to push grades.'; $string['error:nostudentgrades'] = 'No student grades found.'; $string['error:nostudentfoundformapping'] = 'No student found for this assessment component.'; $string['error:emptyresponse'] = 'Empty response received when calling {$a}.'; $string['error:turnitin_numparts'] = 'Turnitin assignment with multiple parts is not supported by Grade Push.'; -$string['error:duplicatedtask'] = 'There is already a push task in queue / processing for this course module.'; +$string['error:duplicatedtask'] = 'There is already a push task in queue / processing for this assessment mapping.'; $string['error:coursemodulenotfound'] = 'Course module not found.'; $string['error:tasknotfound'] = 'Push task not found.'; $string['error:multiplemappingsnotsupported'] = 'Multiple assessment component mappings is not supported by {$a}'; $string['error:studentnotfound'] = 'Student with idnumber {$a->idnumber} not found for component grade {$a->componentgrade}'; $string['error:coursemodulenotfound'] = 'Course module not found. ID: {$a}'; $string['error:duplicatemapping'] = 'Cannot map multiple assessment components with same module delivery to an activity. Mapcode: {$a}'; +$string['error:nomoduledeliveryfound'] = 'No module delivery found.'; +$string['error:no_mab_found'] = 'No assessment component found for this module delivery.'; +$string['error:mab_not_found'] = 'Assessment component not found. ID: {$a}'; $string['form:alert_no_mab_found'] = 'No assessment components found'; $string['form:info_turnitin_numparts'] = 'Please note Turnitin assignment with multiple parts is not supported by Grade Push.'; diff --git a/lib.php b/lib.php index 4b565a2..4c28e4a 100644 --- a/lib.php +++ b/lib.php @@ -71,12 +71,23 @@ function local_sitsgradepush_coursemodule_standard_elements($formwrapper, $mform // Add component grades options to the dropdown list. if (empty($manager->get_api_errors())) { - // Get component grade options. - $options = $manager->get_component_grade_options( + // Get module deliveries with component grades data. + $moduledeliveries = $manager->get_component_grade_options( $formwrapper->get_course()->id, $formwrapper->get_current()->coursemodule ); + // Combine all component grades into a single array. + if (!empty($moduledeliveries)) { + $options = []; + foreach ($moduledeliveries as $moduledelivery) { + if (empty($moduledelivery->componentgrades)) { + continue; + } + $options = array_merge($options, $moduledelivery->componentgrades); + } + } + if (empty($options)) { $mform->addElement( 'html', @@ -235,3 +246,49 @@ function local_sitsgradepush_extend_settings_navigation(settings_navigation $set $modulesettings->add_node($node); } } + +/** + * Extend the course navigation with a link to the grade push dashboard. + * + * @param navigation_node $parentnode + * @param stdClass $course + * @param context_course $context + * @return void|null + * @throws coding_exception + * @throws dml_exception + * @throws moodle_exception + */ +function local_sitsgradepush_extend_navigation_course(navigation_node $parentnode, stdClass $course, context_course $context) { + + global $PAGE; + + // Only add this settings item on non-site course pages and if the user has the capability to perform a rollover. + if (!$PAGE->course || $PAGE->course->id == SITEID || !has_capability('local/sitsgradepush:mapassessment', $context)) { + return null; + } + + // Is the plugin enabled? + $enabled = (bool)get_config('local_sitsgradepush', 'enabled'); + if (!$enabled) { + return null; + } + + $url = new moodle_url('/local/sitsgradepush/dashboard.php', [ + 'id' => $course->id, + ]); + + $node = navigation_node::create( + get_string('pluginname', 'local_sitsgradepush'), + $url, + navigation_node::NODETYPE_LEAF, + 'local_sitsgradepush', + 'local_sitsgradepush', + new pix_icon('repeat', get_string('pluginname', 'local_sitsgradepush'), 'local_sitsgradepush') + ); + + if ($PAGE->url->compare($url, URL_MATCH_BASE)) { + $node->make_active(); + } + + $parentnode->add_node($node); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..6aa3ffd --- /dev/null +++ b/styles.css @@ -0,0 +1,57 @@ +.sitgradepush-btn-center { + text-align: center; +} + +.sitsgradepush-dasboard .selector-container { + margin-bottom: 20px; +} + +.sitsgradepush-dasboard .module-delivery-table { + text-align: center; + margin-bottom: 50px; + background-color: #fff5ea; +} + +.sitsgradepush-dasboard .module-delivery-table .seq-col { + width: 5%; +} + +.sitsgradepush-dasboard .module-delivery-table .title-col { + width: 40%; +} + +.sitsgradepush-dasboard .module-delivery-table .weight-col { + width: 5%; +} + +.sitsgradepush-dasboard .module-delivery-table .assessment-col { + width: 15%; +} + +.sitsgradepush-dasboard .module-delivery-table .mark-col { + width: 20%; +} + +.sitsgradepush-dasboard .module-delivery-table .action-col { + width: 15%; +} + +/* Add CSS styles for the "Back to Top" button */ +.sitsgradepush-dasboard .back-to-top { + display: none; + position: fixed; + bottom: 20px; /* Adjust the distance from the bottom as needed */ + right: 20px; /* Adjust the distance from the right as needed */ + z-index: 999; + padding: 10px 20px; + background-color: #007bff; /* Button background color */ + color: #fff; /* Button text color */ + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +.sitsgradepush-dasboard .back-to-top:hover { + background-color: #0056b3; /* Hover background color */ +} diff --git a/templates/assessmentgrades.mustache b/templates/assessmentgrades.mustache index 04d39dd..e49b71c 100644 --- a/templates/assessmentgrades.mustache +++ b/templates/assessmentgrades.mustache @@ -16,7 +16,7 @@ {{! @template local_sitsgradepush/assessmentgrades - Template for displaying the grades for an assessment. + Template for displaying the grade push history for an assessment. Classes required for JS: * none @@ -25,15 +25,40 @@ * none Context variables required for this template: - * tabletitle - The assessment name - * students - Array variable, array of students + * tabletitle - String, The assessment component's name + * additionalinfo - Boolean, whether to display additional information + * students - Array, array of students Example context (json): - {"tabletitle":"","students":[]} + { + "tabletitle":"PHAY0063-2022-T1-A7P-001 Coursework 4000 word written case studies (50%)", + "additionalinfo":true, + "students":[{ + "firstname":"Test", + "lastname":"User", + "idnumber":"1234567", + "marks":"0", + "handindatetime":"2019-01-01 00:00:00", + "lastgradepushresultlabel":"Success ", + "lastgradepushtime":"2019-01-01 00:00:00", + "lastsublogpushresultlabel":"Success ", + "lastsublogpushtime":"2019-01-01 00:00:00" + }] + } }} + {{#additionalinfo}} + + + + {{/additionalinfo}} diff --git a/templates/back_to_top_button.mustache b/templates/back_to_top_button.mustache new file mode 100644 index 0000000..bef2974 --- /dev/null +++ b/templates/back_to_top_button.mustache @@ -0,0 +1,34 @@ +{{! + This file is part the Local Analytics plugin for Moodle + 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_sitsgradepush/back_to_top_button + + Template for displaying back to top button. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): {} +}} + \ No newline at end of file diff --git a/templates/button.mustache b/templates/button.mustache index 62934f4..ba508ac 100644 --- a/templates/button.mustache +++ b/templates/button.mustache @@ -25,15 +25,16 @@ * none Context variables required for this template: - * id - Button id. - * name - Button display name. - * url - The URL to navigate to. + * class - String, CSS class to apply to the button. + * id - String, Button id. + * name - String, Button display name. + * url - String, The URL to navigate to. Example context (json): {"url":"https://moodlesite/index.php?id=125&modname=assign","id":"local_sitsgradepush_pushbutton","name":"Push"} }} -

+

diff --git a/templates/module_delivery_table.mustache b/templates/module_delivery_table.mustache new file mode 100644 index 0000000..c78271e --- /dev/null +++ b/templates/module_delivery_table.mustache @@ -0,0 +1,165 @@ +{{! + This file is part the Local Analytics plugin for Moodle + 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_sitsgradepush/module_delivery_table + + Template for displaying a module delivery information. + + Classes required for JS: + * none + + Data attributes required for JS: + * data-assessmentmappingid - String, assessment mapping id. + + Context variables required for this template: + * tableid - String, table id. + * modcode - String, module code. + * academicyear - String, academic year. + * level - String, level. + * graduatetype - String, graduate type. + * mapcode - String, map code. + * componentgrades - Array, array of component grades. + * mabseq - String, mab sequence. + * mabname - String, mab name. + * mabperc - String, mab percentage. + * astcode - String, ast code. + * assessmentmapping - Array, array of assessment mapping. + * id - String, assessment mapping id. + * type - String, assessment mapping type. + * url - String, assessment mapping url. + * statusicon - String, assessment mapping status icon. + * disablechangesourcebutton - String, value to insert into the button, for 'disabled' or not. + * disablepushgradebutton - String, value to insert into the button, for 'disabled' or not. + + Example context (json): + { + "tableid": "PHAY0063-A7P-T1-2022", + "modcode": "PHAY0063", + "academicyear": "2022", + "componentgrades": + { + "21": + { + "mabseq": "001", + "astcode": "CN01", + "mabperc": "50", + "mabname": "Coursework 4000 word written case studies (50%)", + "assessmentmapping": + { + "id": "39", + "type": "Assignment", + "name": "Test assignment 2", + "url": "http://test.m4.local:4001/mod/assign/view.php?id=960", + "statusicon": "" + }, + "disablechangesourcebutton": " disabled", + "disablepushgradebutton": "" + } + }, + "mapcode": "PHAY0063A7PE", + "level": "7", + "graduatetype": "POSTGRADUATE" + } +}} +
{{tabletitle}}
+
+ {{{taskstatus}}} + {{lasttask}} +
+
Student Portico number
+ + + + + + + + + + + + + + + + + + {{#componentgrades}} + + + + + + + + + {{/componentgrades}} + {{^componentgrades}} + + + + {{/componentgrades}} + +
+
+
MODULE CODE: {{modcode}}
+
ACADEMIC YEAR: {{academicyear}}
+
LEVEL: {{level}} {{graduatetype}}
+
+
MAP CODE: {{mapcode}}
SEQTITLEWGTAST TYPEMARK TRANSFERACTIONS
{{mabseq}}{{mabname}}{{mabperc}}{{astcode}} + {{#assessmentmapping}} + {{type}} + {{/assessmentmapping}} + {{^assessmentmapping}} + + {{/assessmentmapping}} + +
+ + + + + + + + {{#assessmentmapping}} + {{{statusicon}}} + {{/assessmentmapping}} + {{^assessmentmapping}} + + {{/assessmentmapping}} + +
+
{{#str}} error:no_mab_found, local_sitsgradepush {{/str}}
diff --git a/templates/selectelement.mustache b/templates/selectelement.mustache new file mode 100644 index 0000000..634fc95 --- /dev/null +++ b/templates/selectelement.mustache @@ -0,0 +1,55 @@ +{{! + This file is part the Local Analytics plugin for Moodle + 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_sitsgradepush/selectelement + + Template for displaying a select element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): + { + "label": "test", + "selectid": "test0", + "options": [ + { + "value": "test0", + "name": "test0", + "selected": "selected" + }, + { + "value": "test1", + "name": "test1", + "selected": "" + } + ] + } +}} +
+ + +
diff --git a/version.php b/version.php index d5b4663..8cc5bba 100644 --- a/version.php +++ b/version.php @@ -27,7 +27,7 @@ $plugin->component = 'local_sitsgradepush'; $plugin->release = '0.1.0'; -$plugin->version = 2023110600; +$plugin->version = 2023112400; $plugin->requires = 2021051708; $plugin->maturity = MATURITY_ALPHA; $plugin->dependencies = [