diff --git a/amd/build/dashboard.min.js b/amd/build/dashboard.min.js index 6ab83e6..3b72d6a 100644 --- a/amd/build/dashboard.min.js +++ b/amd/build/dashboard.min.js @@ -1,3 +1,3 @@ -define("local_sitsgradepush/dashboard",["exports","./sitsgradepush_helper","core/notification"],(function(_exports,_sitsgradepush_helper,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};let updatePageIntervalId=null,globalCourseid=null;async function pushMarks(assessmentmappingid){try{let result=await(0,_sitsgradepush_helper.schedulePushTask)(assessmentmappingid);result.success&&updateAssessments(globalCourseid);let message="";return!result.success&&result.message&&(message=result.message),function(assessmentmappingid,message){let currentrow=document.getElementById("marks-col-field-"+assessmentmappingid).closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove();if(""!==message){let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='",currentrow.insertAdjacentElement("afterend",errormessagerow)}}(assessmentmappingid,message),result}catch(error){return window.console.error(error),!1}}async function updateAssessments(courseid){let update=await(0,_sitsgradepush_helper.getAssessmentsUpdate)(courseid);if(update.success){let assessments=JSON.parse(update.assessments);assessments.length>0&&function(assessments){assessments.forEach((assessment=>{let marksColumnFieldId="marks-col-field-"+assessment.assessmentmappingid,marksColumnField=document.getElementById(marksColumnFieldId);if(marksColumnField){let marksContainer=marksColumnField.querySelector(".marks-container"),taskContainer=marksColumnField.querySelector(".task-status-container");marksColumnField.setAttribute("data-markscount",assessment.markscount),marksColumnField.querySelector(".marks-count").innerHTML=assessment.markscount;let transferButton=marksColumnField.querySelector(".js-btn-transfer-marks");assessment.markscount>0?transferButton.style.display="block":transferButton.style.display="none",null===assessment.task?(marksColumnField.setAttribute("data-task-running",!1),taskContainer.style.display="none",marksContainer.style.display="block"):(marksColumnField.setAttribute("data-task-running",!0),marksContainer.style.display="none",taskContainer.style.display="block",(0,_sitsgradepush_helper.updateProgressBar)(taskContainer,assessment.task.progress))}}))}(assessments)}else clearInterval(updatePageIntervalId),window.console.error(update.message)}_exports.init=courseid=>{var page;!function(){let successMessage=localStorage.getItem("successMessage");successMessage&&(_notification.default.addNotification({message:successMessage,type:"success"}),localStorage.removeItem("successMessage"))}(),globalCourseid=courseid,page=window,document.querySelectorAll(".jump-to-dropdown-item").forEach((function(item){item.addEventListener("click",(function(){let value=item.getAttribute("data-value");if(null!==value){let pagePosition=function(page){return page instanceof Window?page.scrollY:page.scrollTop}(page),selectedTable=document.getElementById(value);if(selectedTable){let offset=-100,scrollPosition=pagePosition+selectedTable.getBoundingClientRect().top+offset;page.scrollTo({top:scrollPosition,behavior:"smooth"})}}}))})),function(courseid){updateAssessments(courseid),updatePageIntervalId=setInterval((()=>{updateAssessments(courseid)}),15e3),document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState?clearInterval(updatePageIntervalId):(updateAssessments(courseid),updatePageIntervalId=setInterval((()=>{updateAssessments(courseid)}),15e3))}))}(courseid),function(page){let confirmTransferButton=document.getElementById("js-transfer-modal-button");if(null===confirmTransferButton)return;confirmTransferButton.addEventListener("click",(async function(){let assessmentmappingid=confirmTransferButton.getAttribute("data-assessmentmappingid");null!==assessmentmappingid&&"all"!==assessmentmappingid?await pushMarks(assessmentmappingid):"all"===assessmentmappingid&&await async function(page){let assessmentmappings=Array.from(document.querySelectorAll(".marks-col-field")).filter((element=>parseInt(element.getAttribute("data-markscount"),10)>0&&"false"===element.getAttribute("data-task-running"))),total=assessmentmappings.length,count=0,promises=[];assessmentmappings.forEach((function(element){let promise=pushMarks(element.getAttribute("data-assessmentmappingid")).then((function(result){return result.success&&(count+=1),result})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),page.scrollTo({top:0,behavior:"instant"}),await _notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"}),updateAssessments(globalCourseid)}(page)}))}(window)}})); +define("local_sitsgradepush/dashboard",["exports","./sitsgradepush_helper","core/notification"],(function(_exports,_sitsgradepush_helper,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};let updatePageIntervalId=null,globalCourseid=null;async function pushMarks(assessmentmappingid){try{let result=await(0,_sitsgradepush_helper.schedulePushTask)(assessmentmappingid);result.success&&function(assessmentmappingid){let changeSourceButton=document.getElementById("change-source-button-"+assessmentmappingid);changeSourceButton&&(changeSourceButton.style.display="none");updateMarksColumn([{task:{progress:0},assessmentmappingid:assessmentmappingid,markscount:0}])}(assessmentmappingid);let message="";return!result.success&&result.message&&(message=result.message),function(assessmentmappingid,message){let currentrow=document.getElementById("marks-col-field-"+assessmentmappingid).closest("tr");null!==currentrow.nextElementSibling&¤trow.nextElementSibling.classList.contains("error-message-row")&¤trow.nextElementSibling.remove();if(""!==message){let errormessagerow=document.createElement("tr");errormessagerow.setAttribute("class","error-message-row"),errormessagerow.innerHTML='",currentrow.insertAdjacentElement("afterend",errormessagerow)}}(assessmentmappingid,message),result}catch(error){return window.console.error(error),!1}}async function updateAssessments(courseid){let update=await(0,_sitsgradepush_helper.getAssessmentsUpdate)(courseid);if(update.success){let assessments=JSON.parse(update.assessments);assessments.length>0&&updateMarksColumn(assessments)}else clearInterval(updatePageIntervalId),window.console.error(update.message)}function updateMarksColumn(assessments){assessments.forEach((assessment=>{let marksColumnFieldId="marks-col-field-"+assessment.assessmentmappingid,marksColumnField=document.getElementById(marksColumnFieldId);if(marksColumnField){let marksContainer=marksColumnField.querySelector(".marks-container"),taskContainer=marksColumnField.querySelector(".task-status-container");marksColumnField.setAttribute("data-markscount",assessment.markscount),marksColumnField.querySelector(".marks-count").innerHTML=assessment.markscount;let transferButton=marksColumnField.querySelector(".js-btn-transfer-marks");assessment.markscount>0?transferButton.style.display="block":transferButton.style.display="none",null===assessment.task?(marksColumnField.setAttribute("data-task-running",!1),taskContainer.style.display="none",marksContainer.style.display="block"):(marksColumnField.setAttribute("data-task-running",!0),marksContainer.style.display="none",taskContainer.style.display="block",(0,_sitsgradepush_helper.updateProgressBar)(taskContainer,assessment.task.progress))}}))}_exports.init=courseid=>{var page;!function(){let successMessage=localStorage.getItem("successMessage");successMessage&&(_notification.default.addNotification({message:successMessage,type:"success"}),localStorage.removeItem("successMessage"))}(),globalCourseid=courseid,page=window,document.querySelectorAll(".jump-to-dropdown-item").forEach((function(item){item.addEventListener("click",(function(){let value=item.getAttribute("data-value");if(null!==value){let pagePosition=function(page){return page instanceof Window?page.scrollY:page.scrollTop}(page),selectedTable=document.getElementById(value);if(selectedTable){let offset=-100,scrollPosition=pagePosition+selectedTable.getBoundingClientRect().top+offset;page.scrollTo({top:scrollPosition,behavior:"smooth"})}}}))})),function(courseid){updateAssessments(courseid),updatePageIntervalId=setInterval((()=>{updateAssessments(courseid)}),15e3),document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState?clearInterval(updatePageIntervalId):(updateAssessments(courseid),updatePageIntervalId=setInterval((()=>{updateAssessments(courseid)}),15e3))}))}(courseid),function(page){let confirmTransferButton=document.getElementById("js-transfer-modal-button");if(null===confirmTransferButton)return;confirmTransferButton.addEventListener("click",(async function(){let assessmentmappingid=confirmTransferButton.getAttribute("data-assessmentmappingid");null!==assessmentmappingid&&"all"!==assessmentmappingid?await pushMarks(assessmentmappingid):"all"===assessmentmappingid&&await async function(page){let assessmentmappings=Array.from(document.querySelectorAll(".marks-col-field")).filter((element=>parseInt(element.getAttribute("data-markscount"),10)>0&&"false"===element.getAttribute("data-task-running"))),total=assessmentmappings.length,count=0,promises=[];assessmentmappings.forEach((function(element){let promise=pushMarks(element.getAttribute("data-assessmentmappingid")).then((function(result){return result.success&&(count+=1),result})).catch((function(error){window.console.error(error)}));promises.push(promise)})),await Promise.all(promises),page.scrollTo({top:0,behavior:"instant"}),await _notification.default.addNotification({message:count+" of "+total+" push tasks have been scheduled.",type:count===total?"success":"warning"}),updateAssessments(globalCourseid)}(page)}))}(window)}})); //# 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 index 17d3ed7..7f5daea 100644 --- a/amd/build/dashboard.min.js.map +++ b/amd/build/dashboard.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["import {schedulePushTask, getAssessmentsUpdate, updateProgressBar} from \"./sitsgradepush_helper\";\nimport notification from \"core/notification\";\n\nlet updatePageIntervalId = null; // The interval ID for updating the progress.\nlet globalCourseid = null; // The global variable for course ID.\nlet updatePageDelay = 15000; // The delay for updating the page.\n\n/**\n * Initialize the dashboard page.\n *\n * @param {int} courseid\n */\nexport const init = (courseid) => {\n // If there is a saved message by successfully mapped an assessment in localStorage, display it.\n displayNotification();\n\n // Set the global variable course ID.\n globalCourseid = courseid;\n\n // Initialize the module delivery dropdown list.\n initModuleDeliverySelector(window);\n\n // Initialize assessment updates.\n initAssessmentUpdate(courseid);\n\n // Initialize confirmation modal.\n initConfirmationModal(window);\n};\n\n/**\n * Initialize the module delivery dropdown list.\n *\n * @param {Window} page\n */\nfunction initModuleDeliverySelector(page) {\n // Find all the dropdown items.\n let dropdownitems = document.querySelectorAll('.jump-to-dropdown-item');\n\n // Add event listener to each dropdown item.\n dropdownitems.forEach(function(item) {\n item.addEventListener('click', function() {\n let value = item.getAttribute('data-value');\n if (value !== null) {\n // Get the scroll position of the page.\n let pagePosition = getPagePosition(page);\n\n // Find the selected table by ID.\n let selectedTable = document.getElementById(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 = pagePosition + tablePosition + offset;\n\n // Scroll to the calculated position.\n page.scrollTo({\n top: scrollPosition,\n behavior: \"smooth\"\n });\n }\n }\n });\n });\n}\n\n/**\n * Initialize the confirmation modal.\n *\n * @param {Window} page\n */\nfunction initConfirmationModal(page) {\n // Find the confirmation modal.\n let confirmTransferButton = document.getElementById(\"js-transfer-modal-button\");\n\n // Exit if the confirmation modal is not found.\n if (confirmTransferButton === null) {\n return;\n }\n\n // Add event listener to the confirmation modal.\n confirmTransferButton.addEventListener(\"click\", async function() {\n let assessmentmappingid = confirmTransferButton.getAttribute('data-assessmentmappingid');\n if (assessmentmappingid !== null && assessmentmappingid !== 'all') {\n // Single transfer.\n await pushMarks(assessmentmappingid);\n } else if (assessmentmappingid === 'all') {\n // Bulk transfer.\n await pushAllMarks(page);\n }\n });\n}\n\n/**\n * Initialize the assessment updates.\n *\n * @param {int} courseid\n */\nfunction initAssessmentUpdate(courseid) {\n updateAssessments(courseid);\n\n // Update the page every 15 seconds.\n updatePageIntervalId = setInterval(() => {\n updateAssessments(courseid);\n }, updatePageDelay);\n\n // Add event listener to stop update the page when the page is not visible. e.g. when the user switches to another tab.\n document.addEventListener(\"visibilitychange\", function() {\n if (document.visibilityState === \"hidden\") {\n clearInterval(updatePageIntervalId);\n } else {\n updateAssessments(courseid);\n updatePageIntervalId = setInterval(() => {\n updateAssessments(courseid);\n }, updatePageDelay);\n }\n });\n}\n\n/**\n * Schedule a push task when the user clicks on a push button.\n *\n * @param {int} assessmentmappingid The button element.\n * @return {Promise|boolean} Promise.\n */\nasync function pushMarks(assessmentmappingid) {\n try {\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 updateAssessments(globalCourseid);\n }\n let message = '';\n if (!result.success && result.message) {\n message = result.message;\n }\n\n // Show error message if there is any.\n showTransferErrorMessage(assessmentmappingid, message);\n return result;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n}\n\n/**\n *\n * @param {HTMLElement} page\n * @return {Promise}\n */\nasync function pushAllMarks(page) {\n let assessmentmappings = Array.from(document.querySelectorAll('.marks-col-field'))\n .filter(element =>\n parseInt(element.getAttribute('data-markscount'), 10) > 0 &&\n element.getAttribute('data-task-running') === 'false'\n );\n\n // Number of not disabled push buttons.\n let total = assessmentmappings.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 component grade.\n assessmentmappings.forEach(function(element) {\n // Get the assessment mapping ID.\n let assessmentmappingid = element.getAttribute('data-assessmentmappingid');\n // Create a Promise for each button and push it into the array.\n let promise = pushMarks(assessmentmappingid)\n .then(function(result) {\n if (result.success) {\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 await notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n\n // Update the page information.\n updateAssessments(globalCourseid);\n}\n\n/**\n * Update the dashboard page with the latest information.\n * e.g. progress bars, push buttons, records icons.\n *\n * @param {int} courseid\n * @return {Promise}\n */\nasync function updateAssessments(courseid) {\n // Get latest assessments information for the dashboard page.\n let update = await getAssessmentsUpdate(courseid);\n\n if (update.success) {\n // Parse the JSON string.\n let assessments = JSON.parse(update.assessments);\n\n if (assessments.length > 0) {\n updateMarksColumn(assessments);\n }\n } else {\n // Stop update the page if error occurred.\n clearInterval(updatePageIntervalId);\n window.console.error(update.message);\n }\n}\n\n/**\n * Update the marks' column for all assessments mappings.\n *\n * @param {object[]} assessments\n */\nfunction updateMarksColumn(assessments) {\n // Update assessment components which has mappings.\n assessments.forEach(assessment => {\n let marksColumnFieldId = 'marks-col-field-' + assessment.assessmentmappingid;\n let marksColumnField = document.getElementById(marksColumnFieldId);\n if (marksColumnField) {\n let marksContainer = marksColumnField.querySelector('.marks-container');\n let taskContainer = marksColumnField.querySelector('.task-status-container');\n\n // Set the marks count attribute.\n marksColumnField.setAttribute('data-markscount', assessment.markscount);\n\n // Marks count element that displays the number of marks.\n let marksCountElement = marksColumnField.querySelector('.marks-count');\n\n // Update the marks count.\n marksCountElement.innerHTML = assessment.markscount;\n\n // Show the transfer button if there are marks to transfer.\n let transferButton = marksColumnField.querySelector('.js-btn-transfer-marks');\n if (assessment.markscount > 0) {\n transferButton.style.display = 'block';\n } else {\n transferButton.style.display = 'none';\n }\n\n // Show marks information if no task running.\n if (assessment.task === null) {\n marksColumnField.setAttribute('data-task-running', false);\n taskContainer.style.display = 'none';\n marksContainer.style.display = 'block';\n } else {\n // Show task information if task running.\n marksColumnField.setAttribute('data-task-running', true);\n marksContainer.style.display = 'none';\n taskContainer.style.display = 'block';\n updateProgressBar(taskContainer, assessment.task.progress);\n }\n }\n });\n}\n\n/**\n * Show an error message at the table row under the button.\n *\n * @param {int} assessmentmappingid\n * @param {string} message\n */\nfunction showTransferErrorMessage(assessmentmappingid, message) {\n // Find the marks column field.\n let marksColumnField = document.getElementById('marks-col-field-' + assessmentmappingid);\n\n // Find the closest row to the button.\n let currentrow = marksColumnField.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 if (message !== '') {\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 '
' + message + '
' +\n '';\n\n // Insert the error message row after the current row.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n}\n\n/**\n * Display a notification if a success message is available in localStorage.\n */\nfunction displayNotification() {\n // Retrieve the success message from localStorage.\n let successMessage = localStorage.getItem('successMessage');\n\n // Check if a success message is available.\n if (successMessage) {\n // Display the success message using a notification library or other means.\n notification.addNotification({\n message: successMessage,\n type: 'success'\n });\n\n // Remove the success message from localStorage to avoid showing it again.\n localStorage.removeItem('successMessage');\n }\n}\n\n/**\n * Get the scroll position of the page.\n *\n * @param {HTMLElement} page\n * @return {*|number}\n */\nfunction getPagePosition(page) {\n if (page instanceof Window) {\n // Get the scroll position of the page.\n return page.scrollY;\n } else {\n // Get the scroll position of the page.\n return page.scrollTop;\n }\n}\n"],"names":["updatePageIntervalId","globalCourseid","pushMarks","assessmentmappingid","result","success","updateAssessments","message","currentrow","document","getElementById","closest","nextElementSibling","classList","contains","remove","errormessagerow","createElement","setAttribute","innerHTML","insertAdjacentElement","showTransferErrorMessage","error","window","console","courseid","update","assessments","JSON","parse","length","forEach","assessment","marksColumnFieldId","marksColumnField","marksContainer","querySelector","taskContainer","markscount","transferButton","style","display","task","progress","updateMarksColumn","clearInterval","page","successMessage","localStorage","getItem","addNotification","type","removeItem","displayNotification","querySelectorAll","item","addEventListener","value","getAttribute","pagePosition","Window","scrollY","scrollTop","getPagePosition","selectedTable","offset","scrollPosition","getBoundingClientRect","top","scrollTo","behavior","setInterval","visibilityState","initAssessmentUpdate","confirmTransferButton","async","assessmentmappings","Array","from","filter","element","parseInt","total","count","promises","promise","then","catch","push","Promise","all","notification","pushAllMarks","initConfirmationModal"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["import {schedulePushTask, getAssessmentsUpdate, updateProgressBar} from \"./sitsgradepush_helper\";\nimport notification from \"core/notification\";\n\nlet updatePageIntervalId = null; // The interval ID for updating the progress.\nlet globalCourseid = null; // The global variable for course ID.\nlet updatePageDelay = 15000; // The delay for updating the page.\n\n/**\n * Initialize the dashboard page.\n *\n * @param {int} courseid\n */\nexport const init = (courseid) => {\n // If there is a saved message by successfully mapped an assessment in localStorage, display it.\n displayNotification();\n\n // Set the global variable course ID.\n globalCourseid = courseid;\n\n // Initialize the module delivery dropdown list.\n initModuleDeliverySelector(window);\n\n // Initialize assessment updates.\n initAssessmentUpdate(courseid);\n\n // Initialize confirmation modal.\n initConfirmationModal(window);\n};\n\n/**\n * Initialize the module delivery dropdown list.\n *\n * @param {Window} page\n */\nfunction initModuleDeliverySelector(page) {\n // Find all the dropdown items.\n let dropdownitems = document.querySelectorAll('.jump-to-dropdown-item');\n\n // Add event listener to each dropdown item.\n dropdownitems.forEach(function(item) {\n item.addEventListener('click', function() {\n let value = item.getAttribute('data-value');\n if (value !== null) {\n // Get the scroll position of the page.\n let pagePosition = getPagePosition(page);\n\n // Find the selected table by ID.\n let selectedTable = document.getElementById(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 = pagePosition + tablePosition + offset;\n\n // Scroll to the calculated position.\n page.scrollTo({\n top: scrollPosition,\n behavior: \"smooth\"\n });\n }\n }\n });\n });\n}\n\n/**\n * Initialize the confirmation modal.\n *\n * @param {Window} page\n */\nfunction initConfirmationModal(page) {\n // Find the confirmation modal.\n let confirmTransferButton = document.getElementById(\"js-transfer-modal-button\");\n\n // Exit if the confirmation modal is not found.\n if (confirmTransferButton === null) {\n return;\n }\n\n // Add event listener to the confirmation modal.\n confirmTransferButton.addEventListener(\"click\", async function() {\n let assessmentmappingid = confirmTransferButton.getAttribute('data-assessmentmappingid');\n if (assessmentmappingid !== null && assessmentmappingid !== 'all') {\n // Single transfer.\n await pushMarks(assessmentmappingid);\n } else if (assessmentmappingid === 'all') {\n // Bulk transfer.\n await pushAllMarks(page);\n }\n });\n}\n\n/**\n * Initialize the assessment updates.\n *\n * @param {int} courseid\n */\nfunction initAssessmentUpdate(courseid) {\n updateAssessments(courseid);\n\n // Update the page every 15 seconds.\n updatePageIntervalId = setInterval(() => {\n updateAssessments(courseid);\n }, updatePageDelay);\n\n // Add event listener to stop update the page when the page is not visible. e.g. when the user switches to another tab.\n document.addEventListener(\"visibilitychange\", function() {\n if (document.visibilityState === \"hidden\") {\n clearInterval(updatePageIntervalId);\n } else {\n updateAssessments(courseid);\n updatePageIntervalId = setInterval(() => {\n updateAssessments(courseid);\n }, updatePageDelay);\n }\n });\n}\n\n/**\n * Schedule a push task when the user clicks on a push button.\n *\n * @param {int} assessmentmappingid The button element.\n * @return {Promise|boolean} Promise.\n */\nasync function pushMarks(assessmentmappingid) {\n try {\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 UI once a task is scheduled successfully.\n updateUIOnTaskScheduling(assessmentmappingid);\n }\n let message = '';\n if (!result.success && result.message) {\n message = result.message;\n }\n\n // Show error message if there is any.\n showTransferErrorMessage(assessmentmappingid, message);\n return result;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n}\n\n/**\n *\n * @param {HTMLElement} page\n * @return {Promise}\n */\nasync function pushAllMarks(page) {\n let assessmentmappings = Array.from(document.querySelectorAll('.marks-col-field'))\n .filter(element =>\n parseInt(element.getAttribute('data-markscount'), 10) > 0 &&\n element.getAttribute('data-task-running') === 'false'\n );\n\n // Number of not disabled push buttons.\n let total = assessmentmappings.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 component grade.\n assessmentmappings.forEach(function(element) {\n // Get the assessment mapping ID.\n let assessmentmappingid = element.getAttribute('data-assessmentmappingid');\n // Create a Promise for each button and push it into the array.\n let promise = pushMarks(assessmentmappingid)\n .then(function(result) {\n if (result.success) {\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 await notification.addNotification({\n message: count + ' of ' + total + ' push tasks have been scheduled.',\n type: (count === total) ? 'success' : 'warning'\n });\n\n // Update the page information.\n updateAssessments(globalCourseid);\n}\n\n/**\n * Update the UI once a task is scheduled successfully.\n * e.g. hide change source button, show progress bar.\n *\n * @param {int} assessmentmappingid\n */\nfunction updateUIOnTaskScheduling(assessmentmappingid) {\n // Find the change source button.\n let changeSourceButton = document.getElementById('change-source-button-' + assessmentmappingid);\n if (changeSourceButton) {\n // Hide the change source button.\n changeSourceButton.style.display = 'none';\n }\n\n // Hide the transfer button and show the progress bar immediately.\n let assessments = [\n {task: {progress: 0}, assessmentmappingid: assessmentmappingid, markscount: 0},\n ];\n updateMarksColumn(assessments);\n}\n\n/**\n * Update the dashboard page with the latest information.\n * e.g. progress bars, push buttons, records icons.\n *\n * @param {int} courseid\n * @return {Promise}\n */\nasync function updateAssessments(courseid) {\n // Get latest assessments information for the dashboard page.\n let update = await getAssessmentsUpdate(courseid);\n\n if (update.success) {\n // Parse the JSON string.\n let assessments = JSON.parse(update.assessments);\n\n if (assessments.length > 0) {\n updateMarksColumn(assessments);\n }\n } else {\n // Stop update the page if error occurred.\n clearInterval(updatePageIntervalId);\n window.console.error(update.message);\n }\n}\n\n/**\n * Update the marks' column for all assessments mappings.\n *\n * @param {object[]} assessments\n */\nfunction updateMarksColumn(assessments) {\n // Update assessment components which has mappings.\n assessments.forEach(assessment => {\n let marksColumnFieldId = 'marks-col-field-' + assessment.assessmentmappingid;\n let marksColumnField = document.getElementById(marksColumnFieldId);\n if (marksColumnField) {\n let marksContainer = marksColumnField.querySelector('.marks-container');\n let taskContainer = marksColumnField.querySelector('.task-status-container');\n\n // Set the marks count attribute.\n marksColumnField.setAttribute('data-markscount', assessment.markscount);\n\n // Marks count element that displays the number of marks.\n let marksCountElement = marksColumnField.querySelector('.marks-count');\n\n // Update the marks count.\n marksCountElement.innerHTML = assessment.markscount;\n\n // Show the transfer button if there are marks to transfer.\n let transferButton = marksColumnField.querySelector('.js-btn-transfer-marks');\n if (assessment.markscount > 0) {\n transferButton.style.display = 'block';\n } else {\n transferButton.style.display = 'none';\n }\n\n // Show marks information if no task running.\n if (assessment.task === null) {\n marksColumnField.setAttribute('data-task-running', false);\n taskContainer.style.display = 'none';\n marksContainer.style.display = 'block';\n } else {\n // Show task information if task running.\n marksColumnField.setAttribute('data-task-running', true);\n marksContainer.style.display = 'none';\n taskContainer.style.display = 'block';\n updateProgressBar(taskContainer, assessment.task.progress);\n }\n }\n });\n}\n\n/**\n * Show an error message at the table row under the button.\n *\n * @param {int} assessmentmappingid\n * @param {string} message\n */\nfunction showTransferErrorMessage(assessmentmappingid, message) {\n // Find the marks column field.\n let marksColumnField = document.getElementById('marks-col-field-' + assessmentmappingid);\n\n // Find the closest row to the button.\n let currentrow = marksColumnField.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 if (message !== '') {\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 '
' + message + '
' +\n '';\n\n // Insert the error message row after the current row.\n currentrow.insertAdjacentElement(\"afterend\", errormessagerow);\n }\n}\n\n/**\n * Display a notification if a success message is available in localStorage.\n */\nfunction displayNotification() {\n // Retrieve the success message from localStorage.\n let successMessage = localStorage.getItem('successMessage');\n\n // Check if a success message is available.\n if (successMessage) {\n // Display the success message using a notification library or other means.\n notification.addNotification({\n message: successMessage,\n type: 'success'\n });\n\n // Remove the success message from localStorage to avoid showing it again.\n localStorage.removeItem('successMessage');\n }\n}\n\n/**\n * Get the scroll position of the page.\n *\n * @param {HTMLElement} page\n * @return {*|number}\n */\nfunction getPagePosition(page) {\n if (page instanceof Window) {\n // Get the scroll position of the page.\n return page.scrollY;\n } else {\n // Get the scroll position of the page.\n return page.scrollTop;\n }\n}\n"],"names":["updatePageIntervalId","globalCourseid","pushMarks","assessmentmappingid","result","success","changeSourceButton","document","getElementById","style","display","updateMarksColumn","task","progress","markscount","updateUIOnTaskScheduling","message","currentrow","closest","nextElementSibling","classList","contains","remove","errormessagerow","createElement","setAttribute","innerHTML","insertAdjacentElement","showTransferErrorMessage","error","window","console","updateAssessments","courseid","update","assessments","JSON","parse","length","clearInterval","forEach","assessment","marksColumnFieldId","marksColumnField","marksContainer","querySelector","taskContainer","transferButton","page","successMessage","localStorage","getItem","addNotification","type","removeItem","displayNotification","querySelectorAll","item","addEventListener","value","getAttribute","pagePosition","Window","scrollY","scrollTop","getPagePosition","selectedTable","offset","scrollPosition","getBoundingClientRect","top","scrollTo","behavior","setInterval","visibilityState","initAssessmentUpdate","confirmTransferButton","async","assessmentmappings","Array","from","filter","element","parseInt","total","count","promises","promise","then","catch","push","Promise","all","notification","pushAllMarks","initConfirmationModal"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/dashboard.js b/amd/src/dashboard.js index 5b6839c..b15c039 100644 --- a/amd/src/dashboard.js +++ b/amd/src/dashboard.js @@ -130,7 +130,8 @@ async function pushMarks(assessmentmappingid) { // Check if the push task is successfully scheduled. if (result.success) { - updateAssessments(globalCourseid); + // Update the UI once a task is scheduled successfully. + updateUIOnTaskScheduling(assessmentmappingid); } let message = ''; if (!result.success && result.message) { @@ -199,6 +200,27 @@ async function pushAllMarks(page) { updateAssessments(globalCourseid); } +/** + * Update the UI once a task is scheduled successfully. + * e.g. hide change source button, show progress bar. + * + * @param {int} assessmentmappingid + */ +function updateUIOnTaskScheduling(assessmentmappingid) { + // Find the change source button. + let changeSourceButton = document.getElementById('change-source-button-' + assessmentmappingid); + if (changeSourceButton) { + // Hide the change source button. + changeSourceButton.style.display = 'none'; + } + + // Hide the transfer button and show the progress bar immediately. + let assessments = [ + {task: {progress: 0}, assessmentmappingid: assessmentmappingid, markscount: 0}, + ]; + updateMarksColumn(assessments); +} + /** * Update the dashboard page with the latest information. * e.g. progress bars, push buttons, records icons. diff --git a/classes/cachemanager.php b/classes/cachemanager.php index c0f2beb..5ecdda7 100644 --- a/classes/cachemanager.php +++ b/classes/cachemanager.php @@ -36,6 +36,9 @@ class cachemanager { /** @var string Cache area for storing students in an assessment component.*/ const CACHE_AREA_COMPONENTGRADES = 'componentgrades'; + /** @var string Cache area for storing marking schemes.*/ + const CACHE_AREA_MARKINGSCHEMES = 'markingschemes'; + /** * Get cache. * diff --git a/classes/manager.php b/classes/manager.php index d0d7d9f..d36b95c 100644 --- a/classes/manager.php +++ b/classes/manager.php @@ -85,6 +85,7 @@ class manager { 'mabperc' => 'MAB_PERC', 'mabname' => 'MAB_NAME', 'examroomcode' => 'APA_ROMC', + 'mkscode' => 'MKS_CODE', ]; /** @var string[] Allowed activity types */ @@ -177,9 +178,6 @@ public function fetch_component_grades_from_sits(array $modocc): void { // Check response. $this->check_response($response, $request); - // Filter out unwanted component grades by marking scheme. - $response = $this->filter_out_invalid_component_grades($response); - // Set cache expiry to 1 hour. cachemanager::set_cache(cachemanager::CACHE_AREA_COMPONENTGRADES, $key, $response, 3600); @@ -199,6 +197,14 @@ public function fetch_component_grades_from_sits(array $modocc): void { */ public function fetch_marking_scheme_from_sits() { try { + $key = 'markingschemes'; + $cache = cachemanager::get_cache(cachemanager::CACHE_AREA_MARKINGSCHEMES, $key); + + // Return cache if exists. + if (!empty($cache)) { + return (array) $cache; + } + // Get marking scheme from SITS. $request = $this->apiclient->build_request(self::GET_MARKING_SCHEMES, null); $response = $this->apiclient->send_request($request); @@ -206,6 +212,9 @@ public function fetch_marking_scheme_from_sits() { // Check response. $this->check_response($response, $request); + // Set cache expiry to 1 hour. + cachemanager::set_cache(cachemanager::CACHE_AREA_MARKINGSCHEMES, $key, $response, 3600); + return $response; } catch (\moodle_exception $e) { $this->apierrors[] = $e->getMessage(); @@ -213,27 +222,16 @@ public function fetch_marking_scheme_from_sits() { } /** - * Filter out invalid component grades data (MAB) from SITS. + * Is the component grade's marking scheme supported. * - * @param array $componentgrades - * @return array - * @throws \dml_exception + * @param \stdClass $componentgrade + * @return bool */ - public function filter_out_invalid_component_grades(array $componentgrades): array { - $filtered = []; - + public function is_marking_scheme_supported(\stdClass $componentgrade): bool { $makingschemes = $this->fetch_marking_scheme_from_sits(); - if (!empty($makingschemes)) { - foreach ($componentgrades as $componentgrade) { - if ($makingschemes[$componentgrade['MKS_CODE']]['MKS_MARKS'] == 'Y' && - $makingschemes[$componentgrade['MKS_CODE']]['MKS_IUSE'] == 'Y' && - $makingschemes[$componentgrade['MKS_CODE']]['MKS_TYPE'] == 'A') { - $filtered[] = $componentgrade; - } - } - } - - return $filtered; + return ($makingschemes[$componentgrade->mkscode]['MKS_MARKS'] == 'Y' && + $makingschemes[$componentgrade->mkscode]['MKS_IUSE'] == 'Y' && + $makingschemes[$componentgrade->mkscode]['MKS_TYPE'] == 'A'); } /** @@ -325,27 +323,17 @@ public function get_local_component_grades(array $modocc): array { 'periodslotcode' => $occ->mod_occ_psl_code, ]; - // Get AST codes. - if ($astcodes = self::get_moodle_ast_codes()) { - list($astcodessql, $astcodesparam) = $DB->get_in_or_equal($astcodes, SQL_PARAMS_NAMED, 'astcode'); - $sql .= " AND astcode {$astcodessql}"; - $params = array_merge($params, $astcodesparam); - } - // Get component grades for all potential assessment types. $records = $DB->get_records_sql($sql, $params); - // Get ast codes that work with exam room code. - $astcodesworkwithexamroomcodes = self::get_moodle_ast_codes_work_with_exam_room_code(); - - // Get moodle exam room code. - $examroomcode = get_config('local_sitsgradepush', 'moodle_exam_room_code'); - - // Remove component grades that do not match the exam room code. - if (!empty($astcodesworkwithexamroomcodes) && !empty($examroomcode)) { + if (!empty($records)) { foreach ($records as $record) { - if (in_array($record->astcode, $astcodesworkwithexamroomcodes) && $record->examroomcode != $examroomcode) { - unset($records[$record->id]); + $record->unavailablereasons = ''; + // Check if the component grade is valid for mapping. + list($valid, $unavailablereasons) = $this->is_component_grade_valid_for_mapping($record); + $record->available = $valid; + if (!$valid) { + $record->unavailablereasons = implode('
', $unavailablereasons); } } } @@ -377,6 +365,46 @@ public function get_local_component_grades(array $modocc): array { return $moduledeliveries; } + /** + * Check if the component grade is valid for mapping. + * + * @param \stdClass $componentgrade + * @return array + * @throws \dml_exception|\coding_exception + */ + public function is_component_grade_valid_for_mapping(\stdClass $componentgrade): array { + // Get settings. + $assessmenttypecodes = self::get_moodle_ast_codes(); + $assessmenttypecodeswithexamcode = self::get_moodle_ast_codes_work_with_exam_room_code(); + $examroomcode = get_config('local_sitsgradepush', 'moodle_exam_room_code'); + + $valid = true; + $unavailablereasons = []; + + // Check marking scheme. + if (!$this->is_marking_scheme_supported($componentgrade)) { + $valid = false; + $unavailablereasons[] = get_string('error:mks_scheme_not_supported', 'local_sitsgradepush'); + } + + // Check assessment type codes. + if (!empty($assessmenttypecodes) && !in_array($componentgrade->astcode, $assessmenttypecodes)) { + $valid = false; + $unavailablereasons[] = get_string('error:ast_code_not_supported', 'local_sitsgradepush'); + } + + // Check assessment type codes that works with exam room code. + if (!empty($assessmenttypecodeswithexamcode) && !empty($examroomcode)) { + if (in_array($componentgrade->astcode, $assessmenttypecodeswithexamcode) && + $componentgrade->examroomcode != $examroomcode) { + $valid = false; + $unavailablereasons[] = get_string('error:ast_code_exam_room_code_not_matched', 'local_sitsgradepush'); + } + } + + return [$valid, $unavailablereasons]; + } + /** * Save component grades from SITS to database. * @@ -404,6 +432,7 @@ public function save_component_grades(array $componentgrades) { $record->mabperc = $componentgrade['MAB_PERC']; $record->mabname = $componentgrade['MAB_NAME']; $record->examroomcode = $componentgrade['APA_ROMC']; + $record->mkscode = $componentgrade['MKS_CODE']; $record->timemodified = time(); $DB->update_record(self::TABLE_COMPONENT_GRADE, $record); @@ -1395,6 +1424,23 @@ public function get_data_for_page_update(int $courseid, int $couresmoduleid = 0) return $results; } + /** + * Can the source be changed for a component grade. + * + * @param int $componentgradeid + * @return bool + * @throws \coding_exception + * @throws \dml_exception + */ + public function can_change_source(int $componentgradeid): bool { + if ($assessementmapping = $this->is_component_grade_mapped($componentgradeid)) { + return !taskmanager::get_pending_task_in_queue($assessementmapping->id) && + !$this->has_grades_pushed($assessementmapping->id); + } else { + return true; + } + } + /** * Save transfer log. * diff --git a/classes/output/renderer.php b/classes/output/renderer.php index d6a4a21..f418796 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -94,6 +94,9 @@ public function render_marks_transfer_history_page(array $assessmentdata, int $c // Check if the user has the capability to see the submission log column. $showsublogcolumn = has_capability('local/sitsgradepush:showsubmissionlogcolumn', \context_course::instance($courseid)); + // Check if the course is in the current academic year. + $iscurrentacademicyear = $this->manager->is_current_academic_year_activity($courseid); + $mappingtables = []; $totalmarkscount = 0; $runningtasks = []; @@ -148,6 +151,7 @@ public function render_marks_transfer_history_page(array $assessmentdata, int $c // Render the table. return $this->output->render_from_template('local_sitsgradepush/marks_transfer_history_page', [ + 'currentacademicyear' => $iscurrentacademicyear, 'module-delivery-tables' => $mappingtables, 'transfer-all-button-label' => get_string('label:pushgrade', 'local_sitsgradepush'), 'latest-transferred-text' => $this->get_latest_tranferred_text($assessmentdata['mappings']), @@ -171,6 +175,9 @@ 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']; + // Check if the course is in the current academic year. + $iscurrentacademicyear = $this->manager->is_current_academic_year_activity($courseid); + $moduledeliverytables = []; // Prepare the content for each module delivery table. foreach ($moduledeliveries as $moduledelivery) { @@ -200,8 +207,6 @@ public function render_dashboard(array $moduledeliveries, int $courseid): string // 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'; continue; } @@ -228,7 +233,11 @@ public function render_dashboard(array $moduledeliveries, int $courseid): string // Check if there is a task running for the assessment mapping. $taskrunning = taskmanager::get_pending_task_in_queue($componentgrade->assessmentmappingid); $assessmentmapping->taskrunning = !empty($taskrunning); - $assessmentmapping->taskprogress = $taskrunning ? $taskrunning->progress : 0; + $assessmentmapping->taskprogress = $taskrunning && $taskrunning->progress ? $taskrunning->progress : 0; + + // Disable the change source button if there is a task running. + $assessmentmapping->disablechangesource = + !empty($taskrunning) || $this->manager->has_grades_pushed($componentgrade->assessmentmappingid); $componentgrade->assessmentmapping = $assessmentmapping; } else { @@ -252,6 +261,7 @@ public function render_dashboard(array $moduledeliveries, int $courseid): string return $this->output->render_from_template( 'local_sitsgradepush/dashboard', [ + 'currentacademicyear' => $iscurrentacademicyear, 'module-delivery-tables' => $moduledeliverytables, 'jump-to-options' => $options, 'jump-to-label' => get_string('label:jumpto', 'local_sitsgradepush'), diff --git a/classes/taskmanager.php b/classes/taskmanager.php index 8125a33..2867f9e 100644 --- a/classes/taskmanager.php +++ b/classes/taskmanager.php @@ -388,7 +388,7 @@ public static function send_email_notification(int $taskid): void { 'map_code' => $result->mab, 'sits_assessment' => $result->mabname, 'activity_url' => $url->out(false), - 'support_url' => get_config('local_sitsgradepush', 'suppuort_page_url') ?? '', + 'support_url' => get_config('local_sitsgradepush', 'support_page_url') ?? '', 'succeeded_count' => $succeededcount, 'failed_count' => $failedcount, ]); diff --git a/db/caches.php b/db/caches.php index 803981f..d89fe0b 100644 --- a/db/caches.php +++ b/db/caches.php @@ -36,4 +36,9 @@ 'simplekeys' => true, 'simpledata' => false, ], + 'markingschemes' => [ + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'simpledata' => false, + ], ]; diff --git a/db/install.xml b/db/install.xml index cb5841d..3db274a 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -34,6 +34,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index c8d8b95..51214e6 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -396,5 +396,20 @@ function xmldb_local_sitsgradepush_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024020100, 'local', 'sitsgradepush'); } + if ($oldversion < 2024022600) { + + // Define field mkscode to be added to local_sitsgradepush_mab. + $table = new xmldb_table('local_sitsgradepush_mab'); + $field = new xmldb_field('mkscode', XMLDB_TYPE_CHAR, '6', null, null, null, null, 'examroomcode'); + + // Conditionally launch add field mkscode. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Sitsgradepush savepoint reached. + upgrade_plugin_savepoint(true, 2024022600, 'local', 'sitsgradepush'); + } + return true; } diff --git a/lang/en/local_sitsgradepush.php b/lang/en/local_sitsgradepush.php index df12995..d9b3c34 100644 --- a/lang/en/local_sitsgradepush.php +++ b/lang/en/local_sitsgradepush.php @@ -65,6 +65,7 @@ $string['subplugintype_sitsapiclient'] = 'API client used for data integration.'; $string['cachedef_studentspr'] = 'Student\'s SPR code per SITS assessment pattern'; $string['cachedef_componentgrades'] = 'SITS assessment components'; +$string['cachedef_markingschemes'] = 'SITS marking schemes'; $string['invalidstudents'] = 'Students not valid for the mapped assessment components'; $string['pushrecordsexist'] = 'Transfer records exist'; $string['pushrecordsnotexist'] = 'No transfer records'; @@ -147,6 +148,11 @@ $string['error:submission_log_transfer_failed'] = 'Submission Transfer failed.'; $string['error:grade_items_not_found'] = 'Grade items not found.'; $string['error:gradetype_not_supported'] = 'Marking {$a} are not currently supported.'; +$string['error:cannot_change_source'] = 'Cannot change source as marks have already been transferred for this assessment component.'; +$string['error:mks_scheme_not_supported'] = 'Marking Scheme is not supported for marks transfer'; +$string['error:ast_code_not_supported'] = 'Assessment Type is not expected to take place in Moodle'; +$string['error:ast_code_exam_room_code_not_matched'] = 'Centrally managed exam NOT due to take place in Moodle'; +$string['error:mab_invalid_for_mapping'] = 'This assessment component is not valid for mapping due to the following reasons: {$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 Marks Transfer.'; diff --git a/select_source.php b/select_source.php index e1ce8f6..959bd3d 100644 --- a/select_source.php +++ b/select_source.php @@ -47,11 +47,27 @@ // Get the component grades. $manager = manager::get_manager(); +// Check if the course is in the current academic year. +if (!$manager->is_current_academic_year_activity($courseid)) { + throw new moodle_exception('error:pastactivity', 'local_sitsgradepush'); +} + // Check MAB exists. -if ($manager->get_local_component_grade_by_id($mabid) === false) { +if (!$mab = $manager->get_local_component_grade_by_id($mabid)) { throw new moodle_exception('error:mab_not_found', 'local_sitsgradepush', '', $mabid); } +// Check if the component grade is valid for mapping. +list($mabvalid, $errormessages) = $manager->is_component_grade_valid_for_mapping($mab); +if (!$mabvalid) { + throw new moodle_exception('error:mab_invalid_for_mapping', 'local_sitsgradepush', '', implode(', ', $errormessages)); +} + +// Check there is no task running and no marks transfer records. +if (!$manager->can_change_source($mabid)) { + throw new moodle_exception('error:cannot_change_source', 'local_sitsgradepush'); +} + // Make sure user is authenticated. require_login(); diff --git a/settings.php b/settings.php index 4025ef4..afffbdf 100644 --- a/settings.php +++ b/settings.php @@ -125,7 +125,7 @@ ); // Set the support page URL. - $settings->add(new admin_setting_configtext('local_sitsgradepush/suppuort_page_url', + $settings->add(new admin_setting_configtext('local_sitsgradepush/support_page_url', get_string('settings:support_page_url', 'local_sitsgradepush'), get_string('settings:support_page_url:desc', 'local_sitsgradepush'), 'https://wiki.ucl.ac.uk/pages/viewpage.action?pageId=306185189', diff --git a/templates/dashboard.mustache b/templates/dashboard.mustache index 82e5276..82fa48b 100644 --- a/templates/dashboard.mustache +++ b/templates/dashboard.mustache @@ -35,6 +35,7 @@ * jump-to-label - String, jump to label. * jump-to-options - Array, array of jump to options. * name - String, module delivery name. + * currentacademicyear - Boolean, indicate if the course is in the current academic year. * module-delivery-tables - Array, array of module delivery tables. * tableid - String, table id. * modcode - String, module code. @@ -66,6 +67,7 @@ "name": "PHAY0063-2022-T1-A7P-001 Coursework 4000 word written case studies (50%)" } ], + "currentacademicyear":true, "module-delivery-tables": [ { @@ -80,6 +82,8 @@ "astcode": "CN01", "mabperc": "50", "mabname": "Coursework 4000 word written case studies (50%)", + "available": true, + "unavailablereasons": "", "assessmentmapping": { "id": "39", @@ -91,7 +95,8 @@ "selectsourceurl": "http://test.m4.local:4001/local/sitsgradepush/select_source.php?courseid=2&mabid=22", "transferhistoryurl": "http://test.m4.local:4001/local/sitsgradepush/index.php?id=22", "taskrunning": false, - "taskprogress": "0" + "taskprogress": "0", + "disablechangesource": false } } }, @@ -150,11 +155,24 @@ {{type}} {{name}}

+ {{#currentacademicyear}} + + {{#str}} dashboard:changesource, local_sitsgradepush {{/str}} + + {{/currentacademicyear}} {{/assessmentmapping}} - - {{#assessmentmapping}}{{#str}} dashboard:changesource, local_sitsgradepush {{/str}}{{/assessmentmapping}} - {{^assessmentmapping}}{{#str}} selectsource:header, local_sitsgradepush {{/str}}{{/assessmentmapping}} - + {{#currentacademicyear}} + {{^assessmentmapping}} + {{#available}} + + {{#str}} selectsource:header, local_sitsgradepush {{/str}} + + {{/available}} + {{^available}} + {{{unavailablereasons}}} + {{/available}} + {{/assessmentmapping}} + {{/currentacademicyear}} {{#assessmentmapping}} @@ -169,9 +187,11 @@

{{markstotransfer}}

+ {{#currentacademicyear}} + {{/currentacademicyear}}
{{#str}} dashboard:marks_transfer_in_progress, local_sitsgradepush {{/str}} @@ -195,7 +215,9 @@ {{/module-delivery-tables}}
+{{#currentacademicyear}} {{> local_sitsgradepush/transfer_all_button }} +{{/currentacademicyear}}
{{! Modal }} diff --git a/templates/marks_transfer_history_page.mustache b/templates/marks_transfer_history_page.mustache index efe2e7a..d7f53fe 100644 --- a/templates/marks_transfer_history_page.mustache +++ b/templates/marks_transfer_history_page.mustache @@ -27,6 +27,7 @@ Context variables required for this template: * transfer-all-button-label - String, button label. * latest-transferred-text - String, latest transferred datetime. + * currentacademicyear - Boolean, indicate if the course is in the current academic year. * module-delivery-tables - Array of module delivery tables. * tabletitle - String, table title. * students - Array of students. @@ -54,6 +55,7 @@ Example context (json): { + "currentacademicyear":true, "transfer-all-button-label":"Transfer Marks", "latest-transferred-text":"Last transferred 29/01/2024 at 11:00:49 pm", "module-delivery-tables":[{ @@ -96,7 +98,9 @@
- {{> local_sitsgradepush/transfer_all_button }} + {{#currentacademicyear}} + {{> local_sitsgradepush/transfer_all_button }} + {{/currentacademicyear}}
diff --git a/version.php b/version.php index 8d3ca75..02b2ad0 100644 --- a/version.php +++ b/version.php @@ -27,7 +27,7 @@ $plugin->component = 'local_sitsgradepush'; $plugin->release = '0.1.0'; -$plugin->version = 2024020100; +$plugin->version = 2024022600; $plugin->requires = 2023100900; $plugin->maturity = MATURITY_ALPHA; $plugin->dependencies = [