Commit db7102eb authored by David Matamoros's avatar David Matamoros Committed by Paul Holden
Browse files

MDL-70795 reportbuilder: report configuration for default sorting.



Co-authored-By: Mikel Martín Corrales's avatarMikel Martín <mikel@moodle.com>
parent 9000d8d1
......@@ -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';
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -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) {
......
// 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 <http://www.gnu.org/licenses/>.
/**
* Report builder columns sorting editor
*
* @module core_reportbuilder/local/editor/sorting
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @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);
}
});
};
......@@ -43,4 +43,7 @@ export default {
* dispatchEvent(reportEvents.tableReload, {}, document.querySelector(...));
*/
tableReload: 'core_reportbuilder_table_reload',
publish: {
reportColumnsUpdated: 'core_reportbuilder_report_columns_updated',
},
};
// 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 <http://www.gnu.org/licenses/>.
/**
* Module to handle column AJAX requests
*
* @module core_reportbuilder/local/repository/sorting
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @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];
};
......@@ -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"]',
},
};
......
<?php
// 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 <http://www.gnu.org/licenses/>.
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 <paulh@moodle.com>
* @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();
}
}
<?php
// 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 <http://www.gnu.org/licenses/>.
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 <paulh@moodle.com>
* @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();
}
}
......@@ -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,
......
......@@ -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');