diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php index eae1e7ac1f8..48a87d12033 100644 --- a/lang/en/reportbuilder.php +++ b/lang/en/reportbuilder.php @@ -25,6 +25,11 @@ $string['actions'] = 'Actions'; $string['addcolumn'] = 'Add column \'{$a}\''; $string['apply'] = 'Apply'; +$string['columnsortdirectionasc'] = 'Sort column \'{$a}\' ascending'; +$string['columnsortdirectiondesc'] = 'Sort column \'{$a}\' descending'; +$string['columnsortdisable'] = 'Disable sorting for column \'{$a}\''; +$string['columnsortenable'] = 'Enable sorting for column \'{$a}\''; +$string['columnsortupdated'] = 'Updated sorting for column \'{$a}\''; $string['conditionadded'] = 'Added condition \'{$a}\''; $string['conditiondeleted'] = 'Deleted condition \'{$a}\''; $string['conditionmoved'] = 'Moved condition \'{$a}\''; @@ -98,9 +103,11 @@ $string['includedefaultsetup_help'] = 'Populate report with default layout as de $string['movecolumn'] = 'Move column \'{$a}\''; $string['movecondition'] = 'Move condition \'{$a}\''; $string['movefilter'] = 'Move filter \'{$a}\''; +$string['movesorting'] = 'Move sorting for column \'{$a}\''; $string['newreport'] = 'New report'; $string['noconditions'] = 'There are no conditions selected'; $string['nofilters'] = 'There are no filters selected'; +$string['nosortablecolumns'] = 'There are no sortable columns'; $string['privacy:metadata:column'] = 'Report column definitions'; $string['privacy:metadata:column:uniqueidentifier'] = 'Unique identifier of the column'; $string['privacy:metadata:column:usercreated'] = 'The ID of the user who created the column'; @@ -129,6 +136,8 @@ $string['selectafilter'] = 'Select a filter'; $string['selectareportsource'] = 'Select a report source'; $string['selectcourses'] = 'Select courses'; $string['showhide'] = 'Show/hide \'{$a}\''; +$string['sorting'] = 'Sorting'; +$string['sorting_help'] = 'Define the default sort order of the columns added to the report. Users can choose their own table sorting preferences, but will fallback to the defaults provided here.'; $string['switchedit'] = 'Switch to edit mode'; $string['switchpreview'] = 'Switch to preview mode'; $string['timeadded'] = 'Time added'; diff --git a/reportbuilder/amd/build/editor.min.js b/reportbuilder/amd/build/editor.min.js index f933bf89ab7..3fe98cb5f1f 100644 --- a/reportbuilder/amd/build/editor.min.js +++ b/reportbuilder/amd/build/editor.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_reportbuilder/editor",["exports","core/inplace_editable","core/notification","core/pending","core/templates","core_reportbuilder/local/selectors","core_reportbuilder/local/editor/columns","core_reportbuilder/local/editor/conditions","core_reportbuilder/local/editor/filters","core_reportbuilder/local/repository/reports"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;c=m(c);d=m(d);e=m(e);f=l(f);function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a){return a&&a.__esModule?a:{default:a}}var n=!1,o=function(){var a=document.querySelector(f.regions.report);(0,g.init)(a,n);(0,h.init)(a,n);(0,i.init)(a,n);if(n){return}document.addEventListener("click",function(b){var g=b.target.closest(f.actions.toggleEditPreview);if(g){b.preventDefault();var h=new d.default("core_reportbuilder/reports:get"),i="1"!==g.dataset.editMode,k="";(0,j.getReport)(a.dataset.reportId,i).then(function(a){k=a.javascript;return e.default.render("core_reportbuilder/custom_report",a)}).then(function(b,c){return e.default.replaceNodeContents(a,b,c+k)}).then(function(){return h.resolve()}).catch(c.default.exception)}});n=!0};a.init=o}); +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_reportbuilder/editor",["exports","core/inplace_editable","core/notification","core/pending","core/templates","core_reportbuilder/local/selectors","core_reportbuilder/local/editor/columns","core_reportbuilder/local/editor/conditions","core_reportbuilder/local/editor/filters","core_reportbuilder/local/editor/sorting","core_reportbuilder/local/repository/reports"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;c=n(c);d=n(d);e=n(e);f=m(f);function l(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;l=function(){return a};return a}function m(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=l();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function n(a){return a&&a.__esModule?a:{default:a}}var o=!1,p=function(){var a=document.querySelector(f.regions.report);(0,g.init)(a,o);(0,h.init)(a,o);(0,i.init)(a,o);(0,j.init)(a,o);if(o){return}document.addEventListener("click",function(b){var g=b.target.closest(f.actions.toggleEditPreview);if(g){b.preventDefault();var h=new d.default("core_reportbuilder/reports:get"),i="1"!==g.dataset.editMode,j="";(0,k.getReport)(a.dataset.reportId,i).then(function(a){j=a.javascript;return e.default.render("core_reportbuilder/custom_report",a)}).then(function(b,c){return e.default.replaceNodeContents(a,b,c+j)}).then(function(){return h.resolve()}).catch(c.default.exception)}});o=!0};a.init=p}); //# sourceMappingURL=editor.min.js.map diff --git a/reportbuilder/amd/build/editor.min.js.map b/reportbuilder/amd/build/editor.min.js.map index e863012e64f..9e0fb2785fa 100644 --- a/reportbuilder/amd/build/editor.min.js.map +++ b/reportbuilder/amd/build/editor.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/editor.js"],"names":["initialized","init","reportElement","document","querySelector","reportSelectors","regions","report","addEventListener","event","toggleEditViewMode","target","closest","actions","toggleEditPreview","preventDefault","pendingPromise","Pending","toggledEditMode","dataset","editMode","customjs","reportId","then","response","javascript","Templates","render","html","js","replaceNodeContents","resolve","catch","Notification","exception"],"mappings":"ipBAwBA,a,+DAGA,OACA,OACA,OACA,O,4lBAMIA,CAAAA,CAAW,G,CAKFC,CAAI,CAAG,UAAM,CACtB,GAAMC,CAAAA,CAAa,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,OAAhB,CAAwBC,MAA/C,CAAtB,CAEA,WAAkBL,CAAlB,CAAiCF,CAAjC,EACA,WAAqBE,CAArB,CAAoCF,CAApC,EACA,WAAkBE,CAAlB,CAAiCF,CAAjC,EAGA,GAAIA,CAAJ,CAAiB,CACb,MACH,CAGDG,QAAQ,CAACK,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAK,CAAI,CAGxC,GAAMC,CAAAA,CAAkB,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBP,CAAe,CAACQ,OAAhB,CAAwBC,iBAA7C,CAA3B,CACA,GAAIJ,CAAJ,CAAwB,CACpBD,CAAK,CAACM,cAAN,GADoB,GAGdC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAHH,CAIdC,CAAe,CAA2C,GAAxC,GAAAR,CAAkB,CAACS,OAAnB,CAA2BC,QAJ/B,CAMhBC,CAAQ,CAAG,EANK,CAQpB,gBAAUnB,CAAa,CAACiB,OAAd,CAAsBG,QAAhC,CAA0CJ,CAA1C,EACKK,IADL,CACU,SAAAC,CAAQ,CAAI,CACdH,CAAQ,CAAGG,CAAQ,CAACC,UAApB,CACA,MAAOC,WAAUC,MAAV,CAAiB,kCAAjB,CAAqDH,CAArD,CACV,CAJL,EAKKD,IALL,CAKU,SAACK,CAAD,CAAOC,CAAP,CAAc,CAChB,MAAOH,WAAUI,mBAAV,CAA8B5B,CAA9B,CAA6C0B,CAA7C,CAAmDC,CAAE,CAAGR,CAAxD,CACV,CAPL,EAQKE,IARL,CAQU,iBAAMP,CAAAA,CAAc,CAACe,OAAf,EAAN,CARV,EASKC,KATL,CASWC,UAAaC,SATxB,CAUH,CACJ,CAvBD,EAyBAlC,CAAW,GACd,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder editor\n *\n * @module core_reportbuilder/editor\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Templates from 'core/templates';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {init as columnsEditorInit} from 'core_reportbuilder/local/editor/columns';\nimport {init as conditionsEditorInit} from 'core_reportbuilder/local/editor/conditions';\nimport {init as filtersEditorInit} from 'core_reportbuilder/local/editor/filters';\nimport {getReport} from 'core_reportbuilder/local/repository/reports';\n\nlet initialized = false;\n\n/**\n * Initialise editor and all it's modules\n */\nexport const init = () => {\n const reportElement = document.querySelector(reportSelectors.regions.report);\n\n columnsEditorInit(reportElement, initialized);\n conditionsEditorInit(reportElement, initialized);\n filtersEditorInit(reportElement, initialized);\n\n // Ensure we only add our listeners once (can be called multiple times by mustache template).\n if (initialized) {\n return;\n }\n\n // Add event handlers to generic report editor elements.\n document.addEventListener('click', event => {\n\n // Toggle between edit and preview mode.\n const toggleEditViewMode = event.target.closest(reportSelectors.actions.toggleEditPreview);\n if (toggleEditViewMode) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/reports:get');\n const toggledEditMode = toggleEditViewMode.dataset.editMode !== \"1\";\n\n let customjs = '';\n\n getReport(reportElement.dataset.reportId, toggledEditMode)\n .then(response => {\n customjs = response.javascript;\n return Templates.render('core_reportbuilder/custom_report', response);\n })\n .then((html, js) => {\n return Templates.replaceNodeContents(reportElement, html, js + customjs);\n })\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n\n initialized = true;\n};\n"],"file":"editor.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/editor.js"],"names":["initialized","init","reportElement","document","querySelector","reportSelectors","regions","report","addEventListener","event","toggleEditViewMode","target","closest","actions","toggleEditPreview","preventDefault","pendingPromise","Pending","toggledEditMode","dataset","editMode","customjs","reportId","then","response","javascript","Templates","render","html","js","replaceNodeContents","resolve","catch","Notification","exception"],"mappings":"6rBAwBA,a,+DAGA,OACA,OACA,OACA,O,4lBAOIA,CAAAA,CAAW,G,CAKFC,CAAI,CAAG,UAAM,CACtB,GAAMC,CAAAA,CAAa,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,OAAhB,CAAwBC,MAA/C,CAAtB,CAEA,WAAkBL,CAAlB,CAAiCF,CAAjC,EACA,WAAqBE,CAArB,CAAoCF,CAApC,EACA,WAAkBE,CAAlB,CAAiCF,CAAjC,EACA,WAAkBE,CAAlB,CAAiCF,CAAjC,EAGA,GAAIA,CAAJ,CAAiB,CACb,MACH,CAGDG,QAAQ,CAACK,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAK,CAAI,CAGxC,GAAMC,CAAAA,CAAkB,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBP,CAAe,CAACQ,OAAhB,CAAwBC,iBAA7C,CAA3B,CACA,GAAIJ,CAAJ,CAAwB,CACpBD,CAAK,CAACM,cAAN,GADoB,GAGdC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAHH,CAIdC,CAAe,CAA2C,GAAxC,GAAAR,CAAkB,CAACS,OAAnB,CAA2BC,QAJ/B,CAMhBC,CAAQ,CAAG,EANK,CAQpB,gBAAUnB,CAAa,CAACiB,OAAd,CAAsBG,QAAhC,CAA0CJ,CAA1C,EACKK,IADL,CACU,SAAAC,CAAQ,CAAI,CACdH,CAAQ,CAAGG,CAAQ,CAACC,UAApB,CACA,MAAOC,WAAUC,MAAV,CAAiB,kCAAjB,CAAqDH,CAArD,CACV,CAJL,EAKKD,IALL,CAKU,SAACK,CAAD,CAAOC,CAAP,CAAc,CAChB,MAAOH,WAAUI,mBAAV,CAA8B5B,CAA9B,CAA6C0B,CAA7C,CAAmDC,CAAE,CAAGR,CAAxD,CACV,CAPL,EAQKE,IARL,CAQU,iBAAMP,CAAAA,CAAc,CAACe,OAAf,EAAN,CARV,EASKC,KATL,CASWC,UAAaC,SATxB,CAUH,CACJ,CAvBD,EAyBAlC,CAAW,GACd,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder editor\n *\n * @module core_reportbuilder/editor\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Templates from 'core/templates';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {init as columnsEditorInit} from 'core_reportbuilder/local/editor/columns';\nimport {init as conditionsEditorInit} from 'core_reportbuilder/local/editor/conditions';\nimport {init as filtersEditorInit} from 'core_reportbuilder/local/editor/filters';\nimport {init as sortingEditorInit} from 'core_reportbuilder/local/editor/sorting';\nimport {getReport} from 'core_reportbuilder/local/repository/reports';\n\nlet initialized = false;\n\n/**\n * Initialise editor and all it's modules\n */\nexport const init = () => {\n const reportElement = document.querySelector(reportSelectors.regions.report);\n\n columnsEditorInit(reportElement, initialized);\n conditionsEditorInit(reportElement, initialized);\n filtersEditorInit(reportElement, initialized);\n sortingEditorInit(reportElement, initialized);\n\n // Ensure we only add our listeners once (can be called multiple times by mustache template).\n if (initialized) {\n return;\n }\n\n // Add event handlers to generic report editor elements.\n document.addEventListener('click', event => {\n\n // Toggle between edit and preview mode.\n const toggleEditViewMode = event.target.closest(reportSelectors.actions.toggleEditPreview);\n if (toggleEditViewMode) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/reports:get');\n const toggledEditMode = toggleEditViewMode.dataset.editMode !== \"1\";\n\n let customjs = '';\n\n getReport(reportElement.dataset.reportId, toggledEditMode)\n .then(response => {\n customjs = response.javascript;\n return Templates.render('core_reportbuilder/custom_report', response);\n })\n .then((html, js) => {\n return Templates.replaceNodeContents(reportElement, html, js + customjs);\n })\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n\n initialized = true;\n};\n"],"file":"editor.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/editor/sorting.min.js b/reportbuilder/amd/build/local/editor/sorting.min.js new file mode 100644 index 00000000000..3e7465508e0 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/sorting.min.js @@ -0,0 +1,2 @@ +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_reportbuilder/local/editor/sorting",["exports","jquery","core/inplace_editable","core/notification","core/pending","core/pubsub","core/sortable_list","core/str","core/toast","core_reportbuilder/local/selectors","core_reportbuilder/local/repository/sorting","core/templates","core/event_dispatcher","core_reportbuilder/local/events"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=q(b);d=q(d);e=q(e);g=q(g);j=p(j);l=q(l);n=p(n);function o(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;o=function(){return a};return a}function p(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=o();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function q(a){return a&&a.__esModule?a:{default:a}}var r={ASCENDING:4,DESCENDING:3},s=function(a){var b=new e.default("core_reportbuilder/sorting:reload"),c=document.querySelector(j.regions.settingsSorting);return l.default.renderForPromise("core_reportbuilder/local/settings/sorting",{sorting:a}).then(function(a){var d=a.html,e=a.js;l.default.replaceNode(c,d,e);return b.resolve()})},t=function(a,b,c,d){var e=a.dataset.reportId,f=b.closest("li"),g=f.dataset.columnSortId,j=f.dataset.columnSortName;return(0,k.toggleColumnSorting)(e,g,c,d).then(s).then(function(){return(0,h.get_string)("columnsortupdated","core_reportbuilder",j)}).then(i.add).then(function(){(0,m.dispatchEvent)(n.tableReload,{},a);return null})},u=function(a,c){if(c){return}(0,f.subscribe)(n.publish.reportColumnsUpdated,function(a){return s(a).catch(d.default.exception)});a.addEventListener("click",function(b){var c=b.target.closest(j.actions.reportToggleColumnSort);if(c){b.preventDefault();var f=new e.default("core_reportbuilder/sorting:toggle"),g=parseInt(c.closest("li").dataset.columnSortDirection);t(a,c,c.checked,g).then(function(){return f.resolve()}).catch(d.default.exception)}var h=b.target.closest(j.actions.reportToggleColumnSortDirection);if(h){b.preventDefault();var i=new e.default("core_reportbuilder/sorting:direction"),k=h.closest("li"),l=k.dataset.columnSortEnabled,m=parseInt(k.dataset.columnSortDirection);if(m===r.ASCENDING){m=r.DESCENDING}else if(m===r.DESCENDING){m=r.ASCENDING}t(a,h,l,m).then(function(){return i.resolve()}).catch(d.default.exception)}});var l=new g.default("".concat(j.regions.settingsSorting," ul"),{isHorizontal:!1});l.getElementName=function(a){return Promise.resolve(a.data("columnSortName"))};(0,b.default)(a).on(g.default.EVENTS.DROP,"li[data-column-sort-id]",function(b,c){if(c.positionChanged){var f=new e.default("core_reportbuilder/sorting:reorder"),g=c.element.data("columnSortId"),j=c.element.data("columnSortPosition"),l=c.targetNextElement.data("columnSortPosition")||c.element.siblings().length+2;if(l>j){l--}(0,k.reorderColumnSorting)(a.dataset.reportId,g,l).then(s).then(function(){return(0,h.get_string)("columnsortupdated","core_reportbuilder",c.element.data("columnSortName"))}).then(i.add).then(function(){(0,m.dispatchEvent)(n.tableReload,{},a);return null}).then(function(){return f.resolve()}).catch(d.default.exception)}})};a.init=u}); +//# sourceMappingURL=sorting.min.js.map diff --git a/reportbuilder/amd/build/local/editor/sorting.min.js.map b/reportbuilder/amd/build/local/editor/sorting.min.js.map new file mode 100644 index 00000000000..9b528132be9 --- /dev/null +++ b/reportbuilder/amd/build/local/editor/sorting.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/editor/sorting.js"],"names":["SORTORDER","ASCENDING","DESCENDING","reloadSettingsSortingRegion","context","pendingPromise","Pending","settingsSortingRegion","document","querySelector","reportSelectors","regions","settingsSorting","Templates","renderForPromise","sorting","then","html","js","replaceNode","resolve","updateSorting","reportElement","element","sortenabled","sortdirection","reportId","dataset","listElement","closest","columnId","columnSortId","columnName","columnSortName","addToast","reportEvents","tableReload","init","initialized","publish","reportColumnsUpdated","data","catch","Notification","exception","addEventListener","event","toggleSorting","target","actions","reportToggleColumnSort","preventDefault","parseInt","columnSortDirection","checked","toggleSortDirection","reportToggleColumnSortDirection","columnSortEnabled","columnsSortingSortableList","SortableList","isHorizontal","getElementName","Promise","on","EVENTS","DROP","info","positionChanged","columnPosition","targetColumnSortPosition","targetNextElement","siblings","length"],"mappings":"oqBAwBA,a,+DAEA,OAEA,OACA,OAEA,OAGA,OAEA,OAEA,O,4lBAGMA,CAAAA,CAAS,CAAG,CACdC,SAAS,CAAE,CADG,CAEdC,UAAU,CAAE,CAFE,C,CAWZC,CAA2B,CAAG,SAAAC,CAAO,CAAI,IACrCC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CADoB,CAErCC,CAAqB,CAAGC,QAAQ,CAACC,aAAT,CAAuBC,CAAe,CAACC,OAAhB,CAAwBC,eAA/C,CAFa,CAI3C,MAAOC,WAAUC,gBAAV,CAA2B,2CAA3B,CAAwE,CAACC,OAAO,CAAEX,CAAV,CAAxE,EACFY,IADE,CACG,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CAClBL,UAAUM,WAAV,CAAsBZ,CAAtB,CAA6CU,CAA7C,CAAmDC,CAAnD,EACA,MAAOb,CAAAA,CAAc,CAACe,OAAf,EACV,CAJE,CAKV,C,CAWKC,CAAa,CAAG,SAACC,CAAD,CAAgBC,CAAhB,CAAyBC,CAAzB,CAAsCC,CAAtC,CAAwD,IACpEC,CAAAA,CAAQ,CAAGJ,CAAa,CAACK,OAAd,CAAsBD,QADmC,CAEpEE,CAAW,CAAGL,CAAO,CAACM,OAAR,CAAgB,IAAhB,CAFsD,CAGpEC,CAAQ,CAAGF,CAAW,CAACD,OAAZ,CAAoBI,YAHqC,CAIpEC,CAAU,CAAGJ,CAAW,CAACD,OAAZ,CAAoBM,cAJmC,CAM1E,MAAO,0BAAoBP,CAApB,CAA8BI,CAA9B,CAAwCN,CAAxC,CAAqDC,CAArD,EACFT,IADE,CACGb,CADH,EAEFa,IAFE,CAEG,iBAAM,iBAAU,mBAAV,CAA+B,oBAA/B,CAAqDgB,CAArD,CAAN,CAFH,EAGFhB,IAHE,CAGGkB,KAHH,EAIFlB,IAJE,CAIG,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4Cd,CAA5C,EACA,MAAO,KACV,CAPE,CAQV,C,CAQYe,CAAI,CAAG,SAACf,CAAD,CAAgBgB,CAAhB,CAAgC,CAChD,GAAIA,CAAJ,CAAiB,CACb,MACH,CAGD,gBAAUH,CAAY,CAACI,OAAb,CAAqBC,oBAA/B,CAAqD,SAAAC,CAAI,QAAItC,CAAAA,CAA2B,CAACsC,CAAD,CAA3B,CACxDC,KADwD,CAClDC,UAAaC,SADqC,CAAJ,CAAzD,EAIAtB,CAAa,CAACuB,gBAAd,CAA+B,OAA/B,CAAwC,SAAAC,CAAK,CAAI,CAG7C,GAAMC,CAAAA,CAAa,CAAGD,CAAK,CAACE,MAAN,CAAanB,OAAb,CAAqBnB,CAAe,CAACuC,OAAhB,CAAwBC,sBAA7C,CAAtB,CACA,GAAIH,CAAJ,CAAmB,CACfD,CAAK,CAACK,cAAN,GADe,GAGT9C,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,mCAAZ,CAHR,CAITmB,CAAa,CAAG2B,QAAQ,CAACL,CAAa,CAAClB,OAAd,CAAsB,IAAtB,EAA4BF,OAA5B,CAAoC0B,mBAArC,CAJf,CAMfhC,CAAa,CAACC,CAAD,CAAgByB,CAAhB,CAA+BA,CAAa,CAACO,OAA7C,CAAsD7B,CAAtD,CAAb,CACKT,IADL,CACU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CADV,EAEKsB,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CAGD,GAAMW,CAAAA,CAAmB,CAAGT,CAAK,CAACE,MAAN,CAAanB,OAAb,CAAqBnB,CAAe,CAACuC,OAAhB,CAAwBO,+BAA7C,CAA5B,CACA,GAAID,CAAJ,CAAyB,CACrBT,CAAK,CAACK,cAAN,GADqB,GAGf9C,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,sCAAZ,CAHF,CAIfsB,CAAW,CAAG2B,CAAmB,CAAC1B,OAApB,CAA4B,IAA5B,CAJC,CAKfL,CAAW,CAAGI,CAAW,CAACD,OAAZ,CAAoB8B,iBALnB,CAOjBhC,CAAa,CAAG2B,QAAQ,CAACxB,CAAW,CAACD,OAAZ,CAAoB0B,mBAArB,CAPP,CAQrB,GAAI5B,CAAa,GAAKzB,CAAS,CAACC,SAAhC,CAA2C,CACvCwB,CAAa,CAAGzB,CAAS,CAACE,UAC7B,CAFD,IAEO,IAAIuB,CAAa,GAAKzB,CAAS,CAACE,UAAhC,CAA4C,CAC/CuB,CAAa,CAAGzB,CAAS,CAACC,SAC7B,CAEDoB,CAAa,CAACC,CAAD,CAAgBiC,CAAhB,CAAqC/B,CAArC,CAAkDC,CAAlD,CAAb,CACKT,IADL,CACU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CADV,EAEKsB,KAFL,CAEWC,UAAaC,SAFxB,CAGH,CACJ,CAnCD,EAsCA,GAAIc,CAAAA,CAA0B,CAAG,GAAIC,UAAJ,WAAoBjD,CAAe,CAACC,OAAhB,CAAwBC,eAA5C,QAAkE,CAACgD,YAAY,GAAb,CAAlE,CAAjC,CACAF,CAA0B,CAACG,cAA3B,CAA4C,SAAAtC,CAAO,QAAIuC,CAAAA,OAAO,CAAC1C,OAAR,CAAgBG,CAAO,CAACkB,IAAR,CAAa,gBAAb,CAAhB,CAAJ,CAAnD,CAEA,cAAEnB,CAAF,EAAiByC,EAAjB,CAAoBJ,UAAaK,MAAb,CAAoBC,IAAxC,CAA8C,yBAA9C,CAAyE,SAACnB,CAAD,CAAQoB,CAAR,CAAiB,CACtF,GAAIA,CAAI,CAACC,eAAT,CAA0B,IAChB9D,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,oCAAZ,CADD,CAEhBwB,CAAQ,CAAGoC,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,cAAlB,CAFK,CAGhB2B,CAAc,CAAGF,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,oBAAlB,CAHD,CAMlB4B,CAAwB,CAAGH,CAAI,CAACI,iBAAL,CAAuB7B,IAAvB,CAA4B,oBAA5B,GAAqDyB,CAAI,CAAC3C,OAAL,CAAagD,QAAb,GAAwBC,MAAxB,CAAiC,CAN/F,CAOtB,GAAIH,CAAwB,CAAGD,CAA/B,CAA+C,CAC3CC,CAAwB,EAC3B,CAED,2BAAqB/C,CAAa,CAACK,OAAd,CAAsBD,QAA3C,CAAqDI,CAArD,CAA+DuC,CAA/D,EACKrD,IADL,CACUb,CADV,EAEKa,IAFL,CAEU,iBAAM,iBAAU,mBAAV,CAA+B,oBAA/B,CAAqDkD,CAAI,CAAC3C,OAAL,CAAakB,IAAb,CAAkB,gBAAlB,CAArD,CAAN,CAFV,EAGKzB,IAHL,CAGUkB,KAHV,EAIKlB,IAJL,CAIU,UAAM,CACR,oBAAcmB,CAAY,CAACC,WAA3B,CAAwC,EAAxC,CAA4Cd,CAA5C,EACA,MAAO,KACV,CAPL,EAQKN,IARL,CAQU,iBAAMX,CAAAA,CAAc,CAACe,OAAf,EAAN,CARV,EASKsB,KATL,CASWC,UAAaC,SATxB,CAUH,CACJ,CAvBD,CAwBH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder columns sorting editor\n *\n * @module core_reportbuilder/local/editor/sorting\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport $ from 'jquery';\nimport 'core/inplace_editable';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {subscribe} from 'core/pubsub';\nimport SortableList from 'core/sortable_list';\nimport {get_string as getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {reorderColumnSorting, toggleColumnSorting} from 'core_reportbuilder/local/repository/sorting';\nimport Templates from 'core/templates';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport * as reportEvents from 'core_reportbuilder/local/events';\n\n// These constants match PHP consts SORT_ASC, SORT_DESC.\nconst SORTORDER = {\n ASCENDING: 4,\n DESCENDING: 3,\n};\n\n/**\n * Reload sorting settings region\n *\n * @param {Object} context\n * @return {Promise}\n */\nconst reloadSettingsSortingRegion = context => {\n const pendingPromise = new Pending('core_reportbuilder/sorting:reload');\n const settingsSortingRegion = document.querySelector(reportSelectors.regions.settingsSorting);\n\n return Templates.renderForPromise('core_reportbuilder/local/settings/sorting', {sorting: context})\n .then(({html, js}) => {\n Templates.replaceNode(settingsSortingRegion, html, js);\n return pendingPromise.resolve();\n });\n};\n\n/**\n * Updates column sorting\n *\n * @param {Element} reportElement\n * @param {Element} element\n * @param {Number} sortenabled\n * @param {Number} sortdirection\n * @return {Promise}\n */\nconst updateSorting = (reportElement, element, sortenabled, sortdirection) => {\n const reportId = reportElement.dataset.reportId;\n const listElement = element.closest('li');\n const columnId = listElement.dataset.columnSortId;\n const columnName = listElement.dataset.columnSortName;\n\n return toggleColumnSorting(reportId, columnId, sortenabled, sortdirection)\n .then(reloadSettingsSortingRegion)\n .then(() => getString('columnsortupdated', 'core_reportbuilder', columnName))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return null;\n });\n};\n\n/**\n * Initialise module\n *\n * @param {Element} reportElement\n * @param {Boolean} initialized Ensure we only add our listeners once\n */\nexport const init = (reportElement, initialized) => {\n if (initialized) {\n return;\n }\n\n // Update sorting region each time report columns are updated (added or removed).\n subscribe(reportEvents.publish.reportColumnsUpdated, data => reloadSettingsSortingRegion(data)\n .catch(Notification.exception)\n );\n\n reportElement.addEventListener('click', event => {\n\n // Enable/disable sorting on columns.\n const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort);\n if (toggleSorting) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/sorting:toggle');\n const sortdirection = parseInt(toggleSorting.closest('li').dataset.columnSortDirection);\n\n updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n\n // Change column sort direction.\n const toggleSortDirection = event.target.closest(reportSelectors.actions.reportToggleColumnSortDirection);\n if (toggleSortDirection) {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/sorting:direction');\n const listElement = toggleSortDirection.closest('li');\n const sortenabled = listElement.dataset.columnSortEnabled;\n\n let sortdirection = parseInt(listElement.dataset.columnSortDirection);\n if (sortdirection === SORTORDER.ASCENDING) {\n sortdirection = SORTORDER.DESCENDING;\n } else if (sortdirection === SORTORDER.DESCENDING) {\n sortdirection = SORTORDER.ASCENDING;\n }\n\n updateSorting(reportElement, toggleSortDirection, sortenabled, sortdirection)\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n\n // Initialize sortable list to handle column sorting moving (note JQuery dependency, see MDL-72293 for resolution).\n var columnsSortingSortableList = new SortableList(`${reportSelectors.regions.settingsSorting} ul`, {isHorizontal: false});\n columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName'));\n\n $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-column-sort-id]', (event, info) => {\n if (info.positionChanged) {\n const pendingPromise = new Pending('core_reportbuilder/sorting:reorder');\n const columnId = info.element.data('columnSortId');\n const columnPosition = info.element.data('columnSortPosition');\n\n // Select target position, if moving to the end then count number of element siblings.\n let targetColumnSortPosition = info.targetNextElement.data('columnSortPosition') || info.element.siblings().length + 2;\n if (targetColumnSortPosition > columnPosition) {\n targetColumnSortPosition--;\n }\n\n reorderColumnSorting(reportElement.dataset.reportId, columnId, targetColumnSortPosition)\n .then(reloadSettingsSortingRegion)\n .then(() => getString('columnsortupdated', 'core_reportbuilder', info.element.data('columnSortName')))\n .then(addToast)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n return null;\n })\n .then(() => pendingPromise.resolve())\n .catch(Notification.exception);\n }\n });\n};\n"],"file":"sorting.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/events.min.js b/reportbuilder/amd/build/local/events.min.js index 6a67e2031ef..12bfe48d2b9 100644 --- a/reportbuilder/amd/build/local/events.min.js +++ b/reportbuilder/amd/build/local/events.min.js @@ -1,2 +1,2 @@ -define ("core_reportbuilder/local/events",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={tableReload:"core_reportbuilder_table_reload"};return a.default}); +define ("core_reportbuilder/local/events",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={tableReload:"core_reportbuilder_table_reload",publish:{reportColumnsUpdated:"core_reportbuilder_report_columns_updated"}};return a.default}); //# sourceMappingURL=events.min.js.map diff --git a/reportbuilder/amd/build/local/events.min.js.map b/reportbuilder/amd/build/local/events.min.js.map index b3e28edea3a..15a70ccab98 100644 --- a/reportbuilder/amd/build/local/events.min.js.map +++ b/reportbuilder/amd/build/local/events.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/events.js"],"names":["tableReload"],"mappings":"2JA6Be,CAeXA,WAAW,CAAE,iCAfF,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder events\n *\n * @module core_reportbuilder/local/events\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Events for the Report builder subsystem\n *\n * @constant\n * @property {String} tableReload See {@link event:tableReload}\n */\nexport default {\n /**\n * Trigger table reloading\n *\n * @event tableReload\n * @type {CustomEvent}\n * @property {object} detail\n * @property {Boolean} detail.preservePagination Whether current pagination should be preserved (default false)\n *\n * @example Triggering table reload\n * import {dispatchEvent} from 'core/event_dispatcher';\n * import * as reportEvents from 'core_reportbuilder/local/events';\n *\n * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...));\n */\n tableReload: 'core_reportbuilder_table_reload',\n};\n"],"file":"events.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/events.js"],"names":["tableReload","publish","reportColumnsUpdated"],"mappings":"2JA6Be,CAeXA,WAAW,CAAE,iCAfF,CAgBXC,OAAO,CAAE,CACLC,oBAAoB,CAAE,2CADjB,CAhBE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder events\n *\n * @module core_reportbuilder/local/events\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Events for the Report builder subsystem\n *\n * @constant\n * @property {String} tableReload See {@link event:tableReload}\n */\nexport default {\n /**\n * Trigger table reloading\n *\n * @event tableReload\n * @type {CustomEvent}\n * @property {object} detail\n * @property {Boolean} detail.preservePagination Whether current pagination should be preserved (default false)\n *\n * @example Triggering table reload\n * import {dispatchEvent} from 'core/event_dispatcher';\n * import * as reportEvents from 'core_reportbuilder/local/events';\n *\n * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...));\n */\n tableReload: 'core_reportbuilder_table_reload',\n publish: {\n reportColumnsUpdated: 'core_reportbuilder_report_columns_updated',\n },\n};\n"],"file":"events.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/repository/sorting.min.js b/reportbuilder/amd/build/local/repository/sorting.min.js new file mode 100644 index 00000000000..5943332fefe --- /dev/null +++ b/reportbuilder/amd/build/local/repository/sorting.min.js @@ -0,0 +1,2 @@ +define ("core_reportbuilder/local/repository/sorting",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.toggleColumnSorting=a.reorderColumnSorting=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"core_reportbuilder_columns_sort_reorder",args:{reportid:a,columnid:c,position:d}}])[0]};a.reorderColumnSorting=c;var d=function(a,c,d,e){return b.default.call([{methodname:"core_reportbuilder_columns_sort_toggle",args:{reportid:a,columnid:c,enabled:d,direction:e}}])[0]};a.toggleColumnSorting=d}); +//# sourceMappingURL=sorting.min.js.map diff --git a/reportbuilder/amd/build/local/repository/sorting.min.js.map b/reportbuilder/amd/build/local/repository/sorting.min.js.map new file mode 100644 index 00000000000..418716b9c69 --- /dev/null +++ b/reportbuilder/amd/build/local/repository/sorting.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/repository/sorting.js"],"names":["reorderColumnSorting","reportId","columnId","position","Ajax","call","methodname","args","reportid","columnid","toggleColumnSorting","enabled","direction"],"mappings":"8MAwBA,uDAUO,GAAMA,CAAAA,CAAoB,CAAG,SAACC,CAAD,CAAWC,CAAX,CAAqBC,CAArB,CAAkC,CAMlE,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,yCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEP,CAAX,CAAqBQ,QAAQ,CAAEP,CAA/B,CAAyCC,QAAQ,CAAEA,CAAnD,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C,yBAkBA,GAAMO,CAAAA,CAAmB,CAAG,SAACT,CAAD,CAAWC,CAAX,CAAqBS,CAArB,CAA8BC,CAA9B,CAA4C,CAM3E,MAAOR,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CAACC,QAAQ,CAAEP,CAAX,CAAqBQ,QAAQ,CAAEP,CAA/B,CAAyCS,OAAO,CAAEA,CAAlD,CAA2DC,SAAS,CAAEA,CAAtE,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle column AJAX requests\n *\n * @module core_reportbuilder/local/repository/sorting\n * @package core_reportbuilder\n * @copyright 2021 David Matamoros \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Re-order sort column position\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @param {Number} position\n * @return {Promise}\n */\nexport const reorderColumnSorting = (reportId, columnId, position) => {\n const request = {\n methodname: 'core_reportbuilder_columns_sort_reorder',\n args: {reportid: reportId, columnid: columnId, position: position}\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Enables/disabled sorting on column\n *\n * @param {Number} reportId\n * @param {Number} columnId\n * @param {Boolean} enabled\n * @param {Number} direction\n * @return {Promise}\n */\nexport const toggleColumnSorting = (reportId, columnId, enabled, direction) => {\n const request = {\n methodname: 'core_reportbuilder_columns_sort_toggle',\n args: {reportid: reportId, columnid: columnId, enabled: enabled, direction: direction}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"sorting.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/build/local/selectors.min.js b/reportbuilder/amd/build/local/selectors.min.js index d0aae51481c..464913dbd60 100644 --- a/reportbuilder/amd/build/local/selectors.min.js +++ b/reportbuilder/amd/build/local/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{report:"[data-region=\"core_reportbuilder/report\"]",reportTable:"[data-region=\"reportbuilder-table\"]",columnHeader:"[data-region=\"column-header\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]",sidebarMenu:"[data-region=\"sidebar-menu\"]",sidebarCard:"[data-region=\"sidebar-card\"]",settingsConditions:"[data-region=\"settings-conditions\"]",activeConditions:"[data-region=\"active-conditions\"]",activeCondition:"[data-region=\"active-condition\"]",settingsFilters:"[data-region=\"settings-filters\"]",activeFilters:"[data-region=\"active-filters\"]",activeFilter:"[data-region=\"active-filter\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]",reportCreate:"[data-action=\"report-create\"]",reportEdit:"[data-action=\"report-edit\"]",reportDelete:"[data-action=\"report-delete\"]",reportAddColumn:"[data-action=\"report-add-column\"]",reportRemoveColumn:"[data-action=\"report-remove-column\"]",reportAddCondition:"[data-action=\"report-add-condition\"]",reportRemoveCondition:"[data-action=\"report-remove-condition\"]",reportAddFilter:"[data-action=\"report-add-filter\"]",reportRemoveFilter:"[data-action=\"report-remove-filter\"]",sidebarSearch:"[data-action=\"sidebar-search\"]"}};b.forReport=function(a){return"".concat(b.regions.report,"[data-report-id=\"").concat(a,"\"]")};a.default=b;return a.default}); +define ("core_reportbuilder/local/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b={regions:{report:"[data-region=\"core_reportbuilder/report\"]",reportTable:"[data-region=\"reportbuilder-table\"]",columnHeader:"[data-region=\"column-header\"]",filterButtonLabel:"[data-region=\"filter-button-label\"]",filtersForm:"[data-region=\"filters-form\"]",sidebarMenu:"[data-region=\"sidebar-menu\"]",sidebarCard:"[data-region=\"sidebar-card\"]",settingsConditions:"[data-region=\"settings-conditions\"]",activeConditions:"[data-region=\"active-conditions\"]",activeCondition:"[data-region=\"active-condition\"]",settingsFilters:"[data-region=\"settings-filters\"]",activeFilters:"[data-region=\"active-filters\"]",activeFilter:"[data-region=\"active-filter\"]",settingsSorting:"[data-region=\"settings-sorting\"]"},actions:{reportActionPopup:"[data-action=\"report-action-popup\"]",reportCreate:"[data-action=\"report-create\"]",reportEdit:"[data-action=\"report-edit\"]",reportDelete:"[data-action=\"report-delete\"]",reportAddColumn:"[data-action=\"report-add-column\"]",reportRemoveColumn:"[data-action=\"report-remove-column\"]",reportAddCondition:"[data-action=\"report-add-condition\"]",reportRemoveCondition:"[data-action=\"report-remove-condition\"]",reportAddFilter:"[data-action=\"report-add-filter\"]",reportRemoveFilter:"[data-action=\"report-remove-filter\"]",reportToggleColumnSort:"[data-action=\"report-toggle-column-sorting\"]",reportToggleColumnSortDirection:"[data-action=\"report-toggle-sort-direction\"]",sidebarSearch:"[data-action=\"sidebar-search\"]"}};b.forReport=function(a){return"".concat(b.regions.report,"[data-report-id=\"").concat(a,"\"]")};a.default=b;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/reportbuilder/amd/build/local/selectors.min.js.map b/reportbuilder/amd/build/local/selectors.min.js.map index d8ac19dc5e4..51285fa0f75 100644 --- a/reportbuilder/amd/build/local/selectors.min.js.map +++ b/reportbuilder/amd/build/local/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","report","reportTable","columnHeader","filterButtonLabel","filtersForm","sidebarMenu","sidebarCard","settingsConditions","activeConditions","activeCondition","settingsFilters","activeFilters","activeFilter","actions","reportActionPopup","reportCreate","reportEdit","reportDelete","reportAddColumn","reportRemoveColumn","reportAddCondition","reportRemoveCondition","reportAddFilter","reportRemoveFilter","sidebarSearch","forReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,MAAM,CAAE,6CADH,CAELC,WAAW,CAAE,uCAFR,CAGLC,YAAY,CAAE,iCAHT,CAILC,iBAAiB,CAAE,uCAJd,CAKLC,WAAW,CAAE,gCALR,CAMLC,WAAW,CAAE,gCANR,CAOLC,WAAW,CAAE,gCAPR,CAQLC,kBAAkB,CAAE,uCARf,CASLC,gBAAgB,CAAE,qCATb,CAULC,eAAe,CAAE,oCAVZ,CAWLC,eAAe,CAAE,oCAXZ,CAYLC,aAAa,CAAE,kCAZV,CAaLC,YAAY,CAAE,iCAbT,CADK,CAgBdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CAELC,YAAY,CAAE,iCAFT,CAGLC,UAAU,CAAE,+BAHP,CAILC,YAAY,CAAE,iCAJT,CAKLC,eAAe,CAAE,qCALZ,CAMLC,kBAAkB,CAAE,wCANf,CAOLC,kBAAkB,CAAE,wCAPf,CAQLC,qBAAqB,CAAE,2CARlB,CASLC,eAAe,CAAE,qCATZ,CAULC,kBAAkB,CAAE,wCAVf,CAWLC,aAAa,CAAE,kCAXV,CAhBK,CAAlB,CAsCA1B,CAAS,CAAC2B,SAAV,CAAsB,SAAAC,CAAQ,kBAAO5B,CAAS,CAACC,OAAV,CAAkBC,MAAzB,8BAAmD0B,CAAnD,QAA9B,C,UAEe5B,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n report: '[data-region=\"core_reportbuilder/report\"]',\n reportTable: '[data-region=\"reportbuilder-table\"]',\n columnHeader: '[data-region=\"column-header\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n sidebarMenu: '[data-region=\"sidebar-menu\"]',\n sidebarCard: '[data-region=\"sidebar-card\"]',\n settingsConditions: '[data-region=\"settings-conditions\"]',\n activeConditions: '[data-region=\"active-conditions\"]',\n activeCondition: '[data-region=\"active-condition\"]',\n settingsFilters: '[data-region=\"settings-filters\"]',\n activeFilters: '[data-region=\"active-filters\"]',\n activeFilter: '[data-region=\"active-filter\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n reportCreate: '[data-action=\"report-create\"]',\n reportEdit: '[data-action=\"report-edit\"]',\n reportDelete: '[data-action=\"report-delete\"]',\n reportAddColumn: '[data-action=\"report-add-column\"]',\n reportRemoveColumn: '[data-action=\"report-remove-column\"]',\n reportAddCondition: '[data-action=\"report-add-condition\"]',\n reportRemoveCondition: '[data-action=\"report-remove-condition\"]',\n reportAddFilter: '[data-action=\"report-add-filter\"]',\n reportRemoveFilter: '[data-action=\"report-remove-filter\"]',\n sidebarSearch: '[data-action=\"sidebar-search\"]',\n },\n};\n\n/**\n * Selector for given report\n *\n * @method forReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forReport = reportId => `${SELECTORS.regions.report}[data-report-id=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/selectors.js"],"names":["SELECTORS","regions","report","reportTable","columnHeader","filterButtonLabel","filtersForm","sidebarMenu","sidebarCard","settingsConditions","activeConditions","activeCondition","settingsFilters","activeFilters","activeFilter","settingsSorting","actions","reportActionPopup","reportCreate","reportEdit","reportDelete","reportAddColumn","reportRemoveColumn","reportAddCondition","reportRemoveCondition","reportAddFilter","reportRemoveFilter","reportToggleColumnSort","reportToggleColumnSortDirection","sidebarSearch","forReport","reportId"],"mappings":"oJA+BA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,MAAM,CAAE,6CADH,CAELC,WAAW,CAAE,uCAFR,CAGLC,YAAY,CAAE,iCAHT,CAILC,iBAAiB,CAAE,uCAJd,CAKLC,WAAW,CAAE,gCALR,CAMLC,WAAW,CAAE,gCANR,CAOLC,WAAW,CAAE,gCAPR,CAQLC,kBAAkB,CAAE,uCARf,CASLC,gBAAgB,CAAE,qCATb,CAULC,eAAe,CAAE,oCAVZ,CAWLC,eAAe,CAAE,oCAXZ,CAYLC,aAAa,CAAE,kCAZV,CAaLC,YAAY,CAAE,iCAbT,CAcLC,eAAe,CAAE,oCAdZ,CADK,CAiBdC,OAAO,CAAE,CACLC,iBAAiB,CAAE,uCADd,CAELC,YAAY,CAAE,iCAFT,CAGLC,UAAU,CAAE,+BAHP,CAILC,YAAY,CAAE,iCAJT,CAKLC,eAAe,CAAE,qCALZ,CAMLC,kBAAkB,CAAE,wCANf,CAOLC,kBAAkB,CAAE,wCAPf,CAQLC,qBAAqB,CAAE,2CARlB,CASLC,eAAe,CAAE,qCATZ,CAULC,kBAAkB,CAAE,wCAVf,CAWLC,sBAAsB,CAAE,gDAXnB,CAYLC,+BAA+B,CAAE,gDAZ5B,CAaLC,aAAa,CAAE,kCAbV,CAjBK,CAAlB,CAyCA7B,CAAS,CAAC8B,SAAV,CAAsB,SAAAC,CAAQ,kBAAO/B,CAAS,CAACC,OAAV,CAAkBC,MAAzB,8BAAmD6B,CAAnD,QAA9B,C,UAEe/B,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder selectors\n *\n * @module core_reportbuilder/local/selectors\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Selectors for the Report builder subsystem\n *\n * @property {Object} regions\n * @property {String} regions.systemReport System report page region\n * @property {String} regions.filterButtonLabel Filters form toggle region\n * @property {String} regions.filtersForm Filters form page region\n */\nconst SELECTORS = {\n regions: {\n report: '[data-region=\"core_reportbuilder/report\"]',\n reportTable: '[data-region=\"reportbuilder-table\"]',\n columnHeader: '[data-region=\"column-header\"]',\n filterButtonLabel: '[data-region=\"filter-button-label\"]',\n filtersForm: '[data-region=\"filters-form\"]',\n sidebarMenu: '[data-region=\"sidebar-menu\"]',\n sidebarCard: '[data-region=\"sidebar-card\"]',\n settingsConditions: '[data-region=\"settings-conditions\"]',\n activeConditions: '[data-region=\"active-conditions\"]',\n activeCondition: '[data-region=\"active-condition\"]',\n settingsFilters: '[data-region=\"settings-filters\"]',\n activeFilters: '[data-region=\"active-filters\"]',\n activeFilter: '[data-region=\"active-filter\"]',\n settingsSorting: '[data-region=\"settings-sorting\"]',\n },\n actions: {\n reportActionPopup: '[data-action=\"report-action-popup\"]',\n reportCreate: '[data-action=\"report-create\"]',\n reportEdit: '[data-action=\"report-edit\"]',\n reportDelete: '[data-action=\"report-delete\"]',\n reportAddColumn: '[data-action=\"report-add-column\"]',\n reportRemoveColumn: '[data-action=\"report-remove-column\"]',\n reportAddCondition: '[data-action=\"report-add-condition\"]',\n reportRemoveCondition: '[data-action=\"report-remove-condition\"]',\n reportAddFilter: '[data-action=\"report-add-filter\"]',\n reportRemoveFilter: '[data-action=\"report-remove-filter\"]',\n reportToggleColumnSort: '[data-action=\"report-toggle-column-sorting\"]',\n reportToggleColumnSortDirection: '[data-action=\"report-toggle-sort-direction\"]',\n sidebarSearch: '[data-action=\"sidebar-search\"]',\n },\n};\n\n/**\n * Selector for given report\n *\n * @method forReport\n * @param {Number} reportId\n * @return {String}\n */\nSELECTORS.forReport = reportId => `${SELECTORS.regions.report}[data-report-id=\"${reportId}\"]`;\n\nexport default SELECTORS;\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/reportbuilder/amd/src/editor.js b/reportbuilder/amd/src/editor.js index f3077c88097..9da84f0e6cf 100644 --- a/reportbuilder/amd/src/editor.js +++ b/reportbuilder/amd/src/editor.js @@ -32,6 +32,7 @@ import * as reportSelectors from 'core_reportbuilder/local/selectors'; import {init as columnsEditorInit} from 'core_reportbuilder/local/editor/columns'; import {init as conditionsEditorInit} from 'core_reportbuilder/local/editor/conditions'; import {init as filtersEditorInit} from 'core_reportbuilder/local/editor/filters'; +import {init as sortingEditorInit} from 'core_reportbuilder/local/editor/sorting'; import {getReport} from 'core_reportbuilder/local/repository/reports'; let initialized = false; @@ -45,6 +46,7 @@ export const init = () => { columnsEditorInit(reportElement, initialized); conditionsEditorInit(reportElement, initialized); filtersEditorInit(reportElement, initialized); + sortingEditorInit(reportElement, initialized); // Ensure we only add our listeners once (can be called multiple times by mustache template). if (initialized) { diff --git a/reportbuilder/amd/src/local/editor/sorting.js b/reportbuilder/amd/src/local/editor/sorting.js new file mode 100644 index 00000000000..5b6325b7192 --- /dev/null +++ b/reportbuilder/amd/src/local/editor/sorting.js @@ -0,0 +1,170 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Report builder columns sorting editor + * + * @module core_reportbuilder/local/editor/sorting + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +"use strict"; + +import $ from 'jquery'; +import 'core/inplace_editable'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {subscribe} from 'core/pubsub'; +import SortableList from 'core/sortable_list'; +import {get_string as getString} from 'core/str'; +import {add as addToast} from 'core/toast'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {reorderColumnSorting, toggleColumnSorting} from 'core_reportbuilder/local/repository/sorting'; +import Templates from 'core/templates'; +import {dispatchEvent} from 'core/event_dispatcher'; +import * as reportEvents from 'core_reportbuilder/local/events'; + +// These constants match PHP consts SORT_ASC, SORT_DESC. +const SORTORDER = { + ASCENDING: 4, + DESCENDING: 3, +}; + +/** + * Reload sorting settings region + * + * @param {Object} context + * @return {Promise} + */ +const reloadSettingsSortingRegion = context => { + const pendingPromise = new Pending('core_reportbuilder/sorting:reload'); + const settingsSortingRegion = document.querySelector(reportSelectors.regions.settingsSorting); + + return Templates.renderForPromise('core_reportbuilder/local/settings/sorting', {sorting: context}) + .then(({html, js}) => { + Templates.replaceNode(settingsSortingRegion, html, js); + return pendingPromise.resolve(); + }); +}; + +/** + * Updates column sorting + * + * @param {Element} reportElement + * @param {Element} element + * @param {Number} sortenabled + * @param {Number} sortdirection + * @return {Promise} + */ +const updateSorting = (reportElement, element, sortenabled, sortdirection) => { + const reportId = reportElement.dataset.reportId; + const listElement = element.closest('li'); + const columnId = listElement.dataset.columnSortId; + const columnName = listElement.dataset.columnSortName; + + return toggleColumnSorting(reportId, columnId, sortenabled, sortdirection) + .then(reloadSettingsSortingRegion) + .then(() => getString('columnsortupdated', 'core_reportbuilder', columnName)) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return null; + }); +}; + +/** + * Initialise module + * + * @param {Element} reportElement + * @param {Boolean} initialized Ensure we only add our listeners once + */ +export const init = (reportElement, initialized) => { + if (initialized) { + return; + } + + // Update sorting region each time report columns are updated (added or removed). + subscribe(reportEvents.publish.reportColumnsUpdated, data => reloadSettingsSortingRegion(data) + .catch(Notification.exception) + ); + + reportElement.addEventListener('click', event => { + + // Enable/disable sorting on columns. + const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort); + if (toggleSorting) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/sorting:toggle'); + const sortdirection = parseInt(toggleSorting.closest('li').dataset.columnSortDirection); + + updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + + // Change column sort direction. + const toggleSortDirection = event.target.closest(reportSelectors.actions.reportToggleColumnSortDirection); + if (toggleSortDirection) { + event.preventDefault(); + + const pendingPromise = new Pending('core_reportbuilder/sorting:direction'); + const listElement = toggleSortDirection.closest('li'); + const sortenabled = listElement.dataset.columnSortEnabled; + + let sortdirection = parseInt(listElement.dataset.columnSortDirection); + if (sortdirection === SORTORDER.ASCENDING) { + sortdirection = SORTORDER.DESCENDING; + } else if (sortdirection === SORTORDER.DESCENDING) { + sortdirection = SORTORDER.ASCENDING; + } + + updateSorting(reportElement, toggleSortDirection, sortenabled, sortdirection) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); + + // Initialize sortable list to handle column sorting moving (note JQuery dependency, see MDL-72293 for resolution). + var columnsSortingSortableList = new SortableList(`${reportSelectors.regions.settingsSorting} ul`, {isHorizontal: false}); + columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName')); + + $(reportElement).on(SortableList.EVENTS.DROP, 'li[data-column-sort-id]', (event, info) => { + if (info.positionChanged) { + const pendingPromise = new Pending('core_reportbuilder/sorting:reorder'); + const columnId = info.element.data('columnSortId'); + const columnPosition = info.element.data('columnSortPosition'); + + // Select target position, if moving to the end then count number of element siblings. + let targetColumnSortPosition = info.targetNextElement.data('columnSortPosition') || info.element.siblings().length + 2; + if (targetColumnSortPosition > columnPosition) { + targetColumnSortPosition--; + } + + reorderColumnSorting(reportElement.dataset.reportId, columnId, targetColumnSortPosition) + .then(reloadSettingsSortingRegion) + .then(() => getString('columnsortupdated', 'core_reportbuilder', info.element.data('columnSortName'))) + .then(addToast) + .then(() => { + dispatchEvent(reportEvents.tableReload, {}, reportElement); + return null; + }) + .then(() => pendingPromise.resolve()) + .catch(Notification.exception); + } + }); +}; diff --git a/reportbuilder/amd/src/local/events.js b/reportbuilder/amd/src/local/events.js index 28b67c59aa2..0e791813014 100644 --- a/reportbuilder/amd/src/local/events.js +++ b/reportbuilder/amd/src/local/events.js @@ -43,4 +43,7 @@ export default { * dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...)); */ tableReload: 'core_reportbuilder_table_reload', + publish: { + reportColumnsUpdated: 'core_reportbuilder_report_columns_updated', + }, }; diff --git a/reportbuilder/amd/src/local/repository/sorting.js b/reportbuilder/amd/src/local/repository/sorting.js new file mode 100644 index 00000000000..bcd5a03b04c --- /dev/null +++ b/reportbuilder/amd/src/local/repository/sorting.js @@ -0,0 +1,60 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Module to handle column AJAX requests + * + * @module core_reportbuilder/local/repository/sorting + * @package core_reportbuilder + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Re-order sort column position + * + * @param {Number} reportId + * @param {Number} columnId + * @param {Number} position + * @return {Promise} + */ +export const reorderColumnSorting = (reportId, columnId, position) => { + const request = { + methodname: 'core_reportbuilder_columns_sort_reorder', + args: {reportid: reportId, columnid: columnId, position: position} + }; + + return Ajax.call([request])[0]; +}; + +/** + * Enables/disabled sorting on column + * + * @param {Number} reportId + * @param {Number} columnId + * @param {Boolean} enabled + * @param {Number} direction + * @return {Promise} + */ +export const toggleColumnSorting = (reportId, columnId, enabled, direction) => { + const request = { + methodname: 'core_reportbuilder_columns_sort_toggle', + args: {reportid: reportId, columnid: columnId, enabled: enabled, direction: direction} + }; + + return Ajax.call([request])[0]; +}; diff --git a/reportbuilder/amd/src/local/selectors.js b/reportbuilder/amd/src/local/selectors.js index 583c5ed2880..c33753f79b7 100644 --- a/reportbuilder/amd/src/local/selectors.js +++ b/reportbuilder/amd/src/local/selectors.js @@ -44,6 +44,7 @@ const SELECTORS = { settingsFilters: '[data-region="settings-filters"]', activeFilters: '[data-region="active-filters"]', activeFilter: '[data-region="active-filter"]', + settingsSorting: '[data-region="settings-sorting"]', }, actions: { reportActionPopup: '[data-action="report-action-popup"]', @@ -56,6 +57,8 @@ const SELECTORS = { reportRemoveCondition: '[data-action="report-remove-condition"]', reportAddFilter: '[data-action="report-add-filter"]', reportRemoveFilter: '[data-action="report-remove-filter"]', + reportToggleColumnSort: '[data-action="report-toggle-column-sorting"]', + reportToggleColumnSortDirection: '[data-action="report-toggle-sort-direction"]', sidebarSearch: '[data-action="sidebar-search"]', }, }; diff --git a/reportbuilder/classes/external/columns/sort/reorder.php b/reportbuilder/classes/external/columns/sort/reorder.php new file mode 100644 index 00000000000..72482cb3a64 --- /dev/null +++ b/reportbuilder/classes/external/columns/sort/reorder.php @@ -0,0 +1,100 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\external\custom_report_columns_sorting_exporter; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for re-ordering report column sorting + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder extends external_api { + + /** + * External method parameters + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'reportid' => new external_value(PARAM_INT, 'Report ID'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + 'position' => new external_value(PARAM_INT, 'New column sort position'), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return array + */ + public static function execute(int $reportid, int $columnid, int $position): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'position' => $position, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::reorder_report_column_sorting($reportid, $columnid, $position); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/columns/sort/toggle.php b/reportbuilder/classes/external/columns/sort/toggle.php new file mode 100644 index 00000000000..640d6f37663 --- /dev/null +++ b/reportbuilder/classes/external/columns/sort/toggle.php @@ -0,0 +1,104 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use core_reportbuilder\manager; +use core_reportbuilder\permission; +use core_reportbuilder\local\helpers\report; +use core_reportbuilder\external\custom_report_columns_sorting_exporter; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for toggling report column sorting + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class toggle extends external_api { + + /** + * External method parameters + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'reportid' => new external_value(PARAM_INT, 'Report ID'), + 'columnid' => new external_value(PARAM_INT, 'Column ID'), + 'enabled' => new external_value(PARAM_BOOL, 'Sort enabled'), + 'direction' => new external_value(PARAM_INT, 'Sort direction', VALUE_DEFAULT, SORT_ASC), + ]); + } + + /** + * External method execution + * + * @param int $reportid + * @param int $columnid + * @param bool $enabled + * @param int $direction + * @return array + */ + public static function execute(int $reportid, int $columnid, bool $enabled, int $direction = SORT_ASC): array { + global $PAGE; + + [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'enabled' => $enabled, + 'direction' => $direction, + ] = self::validate_parameters(self::execute_parameters(), [ + 'reportid' => $reportid, + 'columnid' => $columnid, + 'enabled' => $enabled, + 'direction' => $direction, + ]); + + $report = manager::get_report_from_id($reportid); + + self::validate_context($report->get_context()); + permission::require_can_edit_report($report->get_report_persistent()); + + report::toggle_report_column_sorting($reportid, $columnid, $enabled, $direction); + + $exporter = new custom_report_columns_sorting_exporter(null, [ + 'report' => $report, + ]); + + return (array) $exporter->export($PAGE->get_renderer('core')); + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return custom_report_columns_sorting_exporter::get_read_structure(); + } +} diff --git a/reportbuilder/classes/external/custom_report_exporter.php b/reportbuilder/classes/external/custom_report_exporter.php index bf63d9806ac..a298f50c5e9 100644 --- a/reportbuilder/classes/external/custom_report_exporter.php +++ b/reportbuilder/classes/external/custom_report_exporter.php @@ -90,6 +90,7 @@ class custom_report_exporter extends persistent_exporter { 'sidebarmenucards' => ['type' => custom_report_menu_cards_exporter::read_properties_definition()], 'conditions' => ['type' => custom_report_conditions_exporter::read_properties_definition()], 'filters' => ['type' => custom_report_filters_exporter::read_properties_definition()], + 'sorting' => ['type' => custom_report_columns_sorting_exporter::read_properties_definition()], 'filtersapplied' => ['type' => PARAM_INT], 'filtersform' => [ 'type' => PARAM_RAW, @@ -122,6 +123,7 @@ class custom_report_exporter extends persistent_exporter { $conditionsexporter = new custom_report_conditions_exporter(null, ['report' => $report]); $filtersexporter = new custom_report_filters_exporter(null, ['report' => $report]); + $sortingexporter = new custom_report_columns_sorting_exporter(null, ['report' => $report]); if ($this->editmode) { $menucardexporter = new custom_report_menu_cards_exporter(null, [ 'menucards' => report_helper::get_available_columns($report->get_report_persistent()) @@ -134,6 +136,7 @@ class custom_report_exporter extends persistent_exporter { 'sidebarmenucards' => $menucards, 'conditions' => (array) $conditionsexporter->export($output), 'filters' => (array) $filtersexporter->export($output), + 'sorting' => (array) $sortingexporter->export($output), 'filtersapplied' => $report->get_applied_filter_count(), 'filtersform' => $filtersform, 'editmode' => (int)$this->editmode, diff --git a/reportbuilder/classes/local/helpers/report.php b/reportbuilder/classes/local/helpers/report.php index 398cd9c03ed..ac2e5fc8b1d 100644 --- a/reportbuilder/classes/local/helpers/report.php +++ b/reportbuilder/classes/local/helpers/report.php @@ -57,6 +57,9 @@ class report { $source = $reportpersistent->get('source'); /** @var datasource $datasource */ $datasource = new $source($reportpersistent, []); + $datasource->add_default_columns(); + $datasource->add_default_filters(); + $datasource->add_default_conditions(); } return $reportpersistent; @@ -96,7 +99,6 @@ class report { return $report->delete(); } - /** * Add given column to report * @@ -176,6 +178,54 @@ class report { return static::reorder_persistents_by_field($column, $columns, $position, 'columnorder'); } + /** + * Re-order given column sorting within a report + * + * @param int $reportid + * @param int $columnid + * @param int $position + * @return bool + * @throws invalid_parameter_exception + */ + public static function reorder_report_column_sorting(int $reportid, int $columnid, int $position): bool { + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + // Get the rest of the report columns, excluding the one we are moving. + $columns = column::get_records_select('reportid = :reportid AND id <> :id', [ + 'reportid' => $reportid, + 'id' => $columnid, + ], 'sortorder'); + + return static::reorder_persistents_by_field($column, $columns, $position, 'sortorder'); + } + + /** + * Toggle sorting options for given column within a report + * + * @param int $reportid + * @param int $columnid + * @param bool $enabled + * @param int $direction + * @return bool + * @throws invalid_parameter_exception + */ + public static function toggle_report_column_sorting(int $reportid, int $columnid, bool $enabled, + int $direction = SORT_ASC): bool { + + $column = column::get_record(['id' => $columnid, 'reportid' => $reportid]); + if ($column === false) { + throw new invalid_parameter_exception('Invalid column'); + } + + return $column + ->set('sortenabled', $enabled) + ->set('sortdirection', $direction) + ->update(); + } + /** * Add given condition to report * @@ -375,11 +425,12 @@ class report { * * @param persistent $persistent The persistent we are moving * @param persistent[] $persistents The rest of the persistents + * @param int $position * @param string $field The field we need to update * @return bool */ private static function reorder_persistents_by_field(persistent $persistent, array $persistents, int $position, - string $field): bool { + string $field): bool { // Splice into new position. array_splice($persistents, $position - 1, 0, [$persistent]); diff --git a/reportbuilder/templates/local/settings/area.mustache b/reportbuilder/templates/local/settings/area.mustache index 287deb8375e..2ff5afcd0c1 100644 --- a/reportbuilder/templates/local/settings/area.mustache +++ b/reportbuilder/templates/local/settings/area.mustache @@ -21,8 +21,54 @@ Example context (json): { - "id": 3, - "filtersform": "form" + "conditions": [{ + "hasavailableconditions": true, + "availableconditions": [{ + "optiongroup": [{ + "text": "User", + "values": [{ + "value": 1, + "visiblename": "User name" + }] + }] + }], + "hasactiveconditions": true, + "activeconditionsform": "form" + }], + "filters": [{ + "hasavailablefilters": true, + "availablefilters": [{ + "optiongroup": [{ + "text": "User", + "values": [{ + "value": 1, + "visiblename": "User name" + }] + }] + }], + "hasactivefilters": true, + "activefilters": [{ + "heading": "Email address", + "entityname": "user", + "headingeditable": "Email address" + }] + }], + "sorting": [{ + "hassortablecolumns": true, + "sortablecolumns": [{ + "id": 1, + "title": "Email address", + "sortdirection": "4", + "sortenabled": true, + "sortorder": 1, + "sorticon": [{ + "key": "t/uplong", + "component": "core", + "title": "Sort column 'Email address' ascending" + }], + "heading": "Email address" + }] + }] } }} @@ -50,6 +96,16 @@ {{/body}} {{/ core_reportbuilder/toggle_card }} + {{< core_reportbuilder/toggle_card }} + {{$collapsed}}collapsed{{/collapsed}} + {{$id}}settingssorting{{/id}} + {{$header}}{{#str}} sorting, core_reportbuilder {{/str}}{{/header}} + {{$helpicon}}{{{sorting.helpicon}}}{{/helpicon}} + {{$body}} + {{> core_reportbuilder/local/settings/sorting}} + {{/body}} + {{/ core_reportbuilder/toggle_card }} + diff --git a/reportbuilder/templates/local/settings/sorting.mustache b/reportbuilder/templates/local/settings/sorting.mustache new file mode 100644 index 00000000000..1a0feef1ffa --- /dev/null +++ b/reportbuilder/templates/local/settings/sorting.mustache @@ -0,0 +1,79 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_reportbuilder/local/settings/sorting + + Template for custom report sortable columns settings area + + Example context (json): + { + "sorting": [{ + "hassortablecolumns": true, + "sortablecolumns": [{ + "id": 1, + "title": "Email address", + "sortdirection": "4", + "sortenabled": true, + "sortorder": 1, + "sorticon": [{ + "key": "t/uplong", + "component": "core", + "title": "Sort column 'Email address' ascending" + }], + "heading": "Email address" + }] + }] + } +}} + +
+ {{#sorting}} + {{#hassortablecolumns}} +
    + {{#sortablecolumns}} +
  • + {{>core/drag_handle}} + {{#sorticon}} + + + + {{/sorticon}} + + +
  • + {{/sortablecolumns}} +
+ {{/hassortablecolumns}} + + {{^hassortablecolumns}} + {{< core_reportbuilder/local/settings/empty_message }} + {{$nothingtoshow}}{{#str}} nosortablecolumns, core_reportbuilder {{/str}} {{/nothingtoshow}} + {{/ core_reportbuilder/local/settings/empty_message }} + {{/hassortablecolumns}} + {{/sorting}} +
\ No newline at end of file diff --git a/reportbuilder/tests/behat/columnsortingeditor.feature b/reportbuilder/tests/behat/columnsortingeditor.feature new file mode 100644 index 00000000000..95cf40fe319 --- /dev/null +++ b/reportbuilder/tests/behat/columnsortingeditor.feature @@ -0,0 +1,72 @@ +@core_reportbuilder @javascript +Feature: Manage custom report columns sorting + In order to manage the sorting for columns of custom reports + As an admin + I need to be able to enable/disable, change sort direction and reorder sorting for columns + + Background: + Given the following "core_reportbuilder > Reports" exist: + | name | source | default | + | My report | core_user\reportbuilder\datasource\users | 0 | + And the following "core_reportbuilder > Columns" exist: + | report | uniqueidentifier | + | My report | user:username | + | My report | user:lastname | + | My report | user:firstname | + And the following "users" exist: + | username | firstname | lastname | email | + | user01 | Alice | Zebra | user01@example.com | + | user02 | Zoe | Aardvark | user02@example.com | + And I am on the "My report" "reportbuilder > Editor" page logged in as "admin" + + Scenario: Toggle column sorting in report + Given I click on "Show/hide settings sidebar" "button" + And I change window size to "large" + And I click on "Show/hide 'Sorting'" "button" + # This will be the fallback sort after toggling lastname sorting. + And I click on "Enable sorting for column 'First name'" "checkbox" + When I click on "Enable sorting for column 'Surname'" "checkbox" + Then I should see "Updated sorting for column 'Surname'" + And "user02" "table_row" should appear before "user01" "table_row" + And I click on "Disable sorting for column 'Surname'" "checkbox" + And I should see "Updated sorting for column 'Surname'" + And "user01" "table_row" should appear before "user02" "table_row" + + Scenario: Change column sort direction in report + Given I click on "Show/hide settings sidebar" "button" + And I change window size to "large" + And I click on "Show/hide 'Sorting'" "button" + When I click on "Enable sorting for column 'Surname'" "checkbox" + And I click on "Sort column 'Surname' descending" "button" + Then I should see "Updated sorting for column 'Surname'" + And "user01" "table_row" should appear before "user02" "table_row" + And I click on "Sort column 'Surname' ascending" "button" + And I should see "Updated sorting for column 'Surname'" + And "user02" "table_row" should appear before "user01" "table_row" + + Scenario: Change column sort order in report + Given I click on "Show/hide settings sidebar" "button" + And I change window size to "large" + And I click on "Show/hide 'Sorting'" "button" + When I click on "Enable sorting for column 'Surname'" "checkbox" + And I click on "Enable sorting for column 'First name'" "checkbox" + And I click on "Move sorting for column 'First name'" "button" + And I click on "To the top of the list" "link" in the "Move sorting for column 'First name'" "dialogue" + Then I should see "Updated sorting for column 'First name'" + And "First name" "text" should appear before "Surname" "text" in the "#settingssorting" "css_element" + And "user01" "table_row" should appear before "user02" "table_row" + + Scenario: Sortable columns are updated when column is added to report + Given I click on "Show/hide settings sidebar" "button" + And I change window size to "large" + And I click on "Show/hide 'Sorting'" "button" + When I click on "Add column 'Full name'" "link" + Then I should see "Full name" in the "#settingssorting" "css_element" + + Scenario: Sortable columns are updated when column is deleted from report + Given I click on "Show/hide settings sidebar" "button" + And I change window size to "large" + And I click on "Show/hide 'Sorting'" "button" + When I click on "Delete column 'Username'" "link" + And I click on "Delete" "button" in the "Delete column 'Username'" "dialogue" + Then I should not see "Username" in the "#settingssorting" "css_element" diff --git a/reportbuilder/tests/external/columns/sort/reorder_test.php b/reportbuilder/tests/external/columns/sort/reorder_test.php new file mode 100644 index 00000000000..10b4b7c763e --- /dev/null +++ b/reportbuilder/tests/external/columns/sort/reorder_test.php @@ -0,0 +1,114 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use core_reportbuilder_generator; +use external_api; +use externallib_advanced_testcase; +use core_reportbuilder\report_access_exception; +use core_reportbuilder\local\models\column; +use core_user\reportbuilder\datasource\users; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->dirroot}/webservice/tests/helpers.php"); + +/** + * Unit tests of external class for re-ordering report column sorting + * + * @package core_reportbuilder + * @covers \core_reportbuilder\external\columns\sort\reorder + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class reorder_test extends externallib_advanced_testcase { + + /** + * Text execute method + */ + public function test_execute(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]); + + // Add four columns. + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']); + $columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']); + + // Move the city column sort order to second position. + $result = reorder::execute($report->get('id'), $columncity->get('id'), 2); + $result = external_api::clean_returnvalue(reorder::execute_returns(), $result); + + $this->assertTrue($result['hassortablecolumns']); + $this->assertCount(4, $result['sortablecolumns']); + $columnid = $columncity->get('id'); + $sortablecolumn = array_filter($result['sortablecolumns'], function(array $column) use($columnid) { + return $column['id'] == $columnid; + }); + $sortablecolumn = reset($sortablecolumn); + $this->assertEquals($columnid, $sortablecolumn['id']); + $this->assertEquals('City/town', $sortablecolumn['title']); + $this->assertEquals(SORT_ASC, $sortablecolumn['sortdirection']); + $this->assertEquals(0, $sortablecolumn['sortenabled']); + $this->assertEquals(2, $sortablecolumn['sortorder']); + $this->assertEquals('t/uplong', $sortablecolumn['sorticon']['key']); + $this->assertEquals('moodle', $sortablecolumn['sorticon']['component']); + $str = get_string('columnsortdirectiondesc', 'core_reportbuilder', 'City/town'); + $this->assertEquals($str, $sortablecolumn['sorticon']['title']); + + // Assert report column sort order. + $columns = column::get_records(['reportid' => $report->get('id')], 'sortorder'); + + $columnidentifiers = array_map(static function(column $column): string { + return $column->get('uniqueidentifier'); + }, $columns); + + $this->assertEquals([ + 'user:fullname', + 'user:city', + 'user:email', + 'user:country', + ], $columnidentifiers); + } + + /** + * Test execute method for a user without permission to edit reports + */ + public function test_execute_access_exception(): void { + $this->resetAfterTest(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(report_access_exception::class); + $this->expectExceptionMessage('You can not edit this report'); + reorder::execute($report->get('id'), $column->get('id'), 2); + } +} diff --git a/reportbuilder/tests/external/columns/sort/toggle_test.php b/reportbuilder/tests/external/columns/sort/toggle_test.php new file mode 100644 index 00000000000..ff307bd2e59 --- /dev/null +++ b/reportbuilder/tests/external/columns/sort/toggle_test.php @@ -0,0 +1,100 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\columns\sort; + +use core_reportbuilder_generator; +use external_api; +use externallib_advanced_testcase; +use core_reportbuilder\report_access_exception; +use core_reportbuilder\local\models\column; +use core_user\reportbuilder\datasource\users; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->dirroot}/webservice/tests/helpers.php"); + +/** + * Unit tests of external class for toggling report column sorting + * + * @package core_reportbuilder + * @covers \core_reportbuilder\external\columns\sort\toggle + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class toggle_test extends externallib_advanced_testcase { + + /** + * Text execute method + */ + public function test_execute(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + + // Toggle sort descending. + $result = toggle::execute($report->get('id'), $column->get('id'), true, SORT_DESC); + $result = external_api::clean_returnvalue(toggle::execute_returns(), $result); + + $this->assertTrue($result['hassortablecolumns']); + $this->assertCount(4, $result['sortablecolumns']); + $columnid = $column->get('id'); + $sortablecolumn = array_filter($result['sortablecolumns'], function(array $column) use($columnid) { + return $column['id'] == $columnid; + }); + $sortablecolumn = reset($sortablecolumn); + $this->assertEquals($columnid, $sortablecolumn['id']); + $this->assertEquals('Email address', $sortablecolumn['title']); + $this->assertEquals(SORT_DESC, $sortablecolumn['sortdirection']); + $this->assertEquals(1, $sortablecolumn['sortenabled']); + $this->assertEquals(4, $sortablecolumn['sortorder']); + $this->assertEquals('t/downlong', $sortablecolumn['sorticon']['key']); + $this->assertEquals('moodle', $sortablecolumn['sorticon']['component']); + $str = get_string('columnsortdirectionasc', 'core_reportbuilder', 'Email address'); + $this->assertEquals($str, $sortablecolumn['sorticon']['title']); + + // Confirm column was updated. + $columnupdated = new column($column->get('id')); + $this->assertTrue($columnupdated->get('sortenabled')); + $this->assertEquals(SORT_DESC, $columnupdated->get('sortdirection')); + } + + /** + * Test execute method for a user without permission to edit reports + */ + public function test_execute_access_exception(): void { + $this->resetAfterTest(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(report_access_exception::class); + $this->expectExceptionMessage('You can not edit this report'); + toggle::execute($report->get('id'), $column->get('id'), true, SORT_DESC); + } +} diff --git a/reportbuilder/tests/local/helpers/report_test.php b/reportbuilder/tests/local/helpers/report_test.php index e5bae3390cc..8bc6fd34335 100644 --- a/reportbuilder/tests/local/helpers/report_test.php +++ b/reportbuilder/tests/local/helpers/report_test.php @@ -33,7 +33,7 @@ use core_user\reportbuilder\datasource\users; * @copyright 2021 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class report_testcase extends advanced_testcase { +class report_test extends advanced_testcase { /** * Test deleting report @@ -44,20 +44,33 @@ class report_testcase extends advanced_testcase { /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); - $report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]); - // Add some elements. - $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); - $filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); - $condition = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + // Create Report1 and add some elements. + $report1 = $generator->create_report(['name' => 'My report 1', 'source' => users::class, 'default' => false]); + $column1 = $generator->create_column(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']); + $filter1 = $generator->create_filter(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']); + $condition1 = $generator->create_condition(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']); - report::delete_report($report->get('id')); + // Create Report2 and add some elements. + $report2 = $generator->create_report(['name' => 'My report 2', 'source' => users::class, 'default' => false]); + $column2 = $generator->create_column(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']); + $filter2 = $generator->create_filter(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']); + $condition2 = $generator->create_condition(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']); - // Make sure report, and all it's elements are deleted. - $this->assertFalse($report::record_exists($report->get('id'))); - $this->assertFalse($column::record_exists($column->get('id'))); - $this->assertFalse($filter::record_exists($filter->get('id'))); - $this->assertFalse($condition::record_exists($condition->get('id'))); + // Delete Report1. + report::delete_report($report1->get('id')); + + // Make sure Report1, and all it's elements are deleted. + $this->assertFalse($report1::record_exists($report1->get('id'))); + $this->assertFalse($column1::record_exists($column1->get('id'))); + $this->assertFalse($filter1::record_exists($filter1->get('id'))); + $this->assertFalse($condition1::record_exists($condition1->get('id'))); + + // Make sure Report2, and all it's elements still exist. + $this->assertTrue($report2::record_exists($report2->get('id'))); + $this->assertTrue($column2::record_exists($column2->get('id'))); + $this->assertTrue($filter2::record_exists($filter2->get('id'))); + $this->assertTrue($condition2::record_exists($condition2->get('id'))); } /** @@ -232,6 +245,101 @@ class report_testcase extends advanced_testcase { report::reorder_report_column($report->get('id'), 42, 1); } + /** + * Testing re-ordering report column sorting + */ + public function test_reorder_report_column_sorting(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]); + + // Add four columns. + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']); + $columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']); + + // Move the city column to second position. + $result = report::reorder_report_column_sorting($report->get('id'), $columncity->get('id'), 2); + $this->assertTrue($result); + + // Assert report columns order. + $columns = column::get_records(['reportid' => $report->get('id')], 'sortorder'); + + $columnidentifiers = array_map(static function(column $column): string { + return $column->get('uniqueidentifier'); + }, $columns); + + $this->assertEquals([ + 'user:fullname', + 'user:city', + 'user:email', + 'user:country', + ], $columnidentifiers); + } + + /** + * Testing re-ordering invalid report column sorting + */ + public function test_reorder_report_column_sorting_invalid(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + + $report = $generator->create_report([ + 'name' => 'My report', + 'source' => users::class, + 'default' => false, + ]); + + $this->expectException(invalid_parameter_exception::class); + $this->expectExceptionMessage('Invalid column'); + report::reorder_report_column_sorting($report->get('id'), 42, 1); + } + + /** + * Test toggling of report column sorting + */ + public function test_toggle_report_column_sorting(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); + + // Toggle sort descending. + $result = report::toggle_report_column_sorting($report->get('id'), $column->get('id'), true, SORT_DESC); + $this->assertTrue($result); + + // Confirm column was updated. + $columnupdated = new column($column->get('id')); + $this->assertTrue($columnupdated->get('sortenabled')); + $this->assertEquals(SORT_DESC, $columnupdated->get('sortdirection')); + } + + /** + * Test toggling of report column sorting with invalid column + */ + public function test_toggle_report_column_sorting_invalid(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + + $this->expectException(invalid_parameter_exception::class); + $this->expectExceptionMessage('Invalid column'); + report::toggle_report_column_sorting($report->get('id'), 42, false); + } + /** * Test adding report condition */ @@ -290,7 +398,10 @@ class report_testcase extends advanced_testcase { $report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]); // Add two conditions. - $conditionfullname = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']); + $conditionfullname = $generator->create_condition([ + 'reportid' => $report->get('id'), + 'uniqueidentifier' => 'user:fullname', + ]); $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']); // Delete the first condition. @@ -525,4 +636,4 @@ class report_testcase extends advanced_testcase { $this->expectExceptionMessage('Invalid filter'); report::reorder_report_filter($report->get('id'), 42, 1); } - } +}