MDL-73312 mod_bigbluebuttonbn: Fix sort by date for recordings

* Fix sorting for dates in recording table
* Code review and getString usage in JS
This commit is contained in:
Laurent David
2022-02-15 08:07:51 +01:00
parent d24a4ab56f
commit 749b416473
7 changed files with 84 additions and 52 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+43 -48
View File
@@ -23,46 +23,39 @@
import * as repository from './repository';
import {exception as displayException} from 'core/notification';
import {get_strings as getStrings} from 'core/str';
import {prefetchStrings} from 'core/prefetch';
import {get_string as getString, get_strings as getStrings} from 'core/str';
import {addIconToContainerWithPromise} from 'core/loadingicon';
import ModalFactory from 'core/modal_factory';
import ModalEvents from 'core/modal_events';
import * as Str from 'core/str';
import Pending from 'core/pending';
const stringList = [
'view_recording_yui_first',
'view_recording_yui_prev',
'view_recording_yui_next',
'view_recording_yui_last',
'view_recording_yui_page',
'view_recording_yui_go',
'view_recording_yui_rows',
'view_recording_yui_show_all',
];
const stringsWithKeys = {
first: 'view_recording_yui_first',
prev: 'view_recording_yui_prev',
next: 'view_recording_yui_next',
last: 'view_recording_yui_last',
page: 'view_recording_yui_page',
go: 'view_recording_yui_go',
rows: 'view_recording_yui_rows',
all: 'view_recording_yui_all',
};
// Load global strings.
prefetchStrings('bigbluebuttonbn', Object.entries(stringsWithKeys).map((entry) => entry[1]));
const getStringsForYui = () => {
const stringMap = stringList.map(key => {
const stringMap = Object.keys(stringsWithKeys).map(key => {
return {
key,
component: 'bigbluebuttonbn',
key: stringsWithKeys[key],
component: 'mod_bigbluebuttonbn',
};
});
// Return an object with the matching string keys (we want an object with {<stringkey>: <stringvalue>...}).
return getStrings(stringMap)
.then(([first, prev, next, last, goToLabel, goToAction, perPage, showAll]) => {
return {
first,
prev,
next,
last,
goToLabel,
goToAction,
perPage,
showAll,
};
})
.catch();
.then((stringArray) => Object.assign({}, ...Object.keys(stringsWithKeys).map(
(key, index) => ({[key]: stringArray[index]})))
).catch();
};
const getYuiInstance = lang => new Promise(resolve => {
@@ -78,20 +71,18 @@ const getYuiInstance = lang => new Promise(resolve => {
* Format the supplied date per the specified locale.
*
* @param {string} locale
* @param {array} dateList
* @param {number} date
* @returns {array}
*/
const formatDates = (locale, dateList) => dateList.map(row => {
const date = new Date(row.date);
row.date = date.toLocaleDateString(locale, {
const formatDate = (locale, date) => {
const realDate = new Date(date);
return realDate.toLocaleDateString(locale, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
return row;
});
};
/**
* Format response data for the table.
@@ -101,9 +92,7 @@ const formatDates = (locale, dateList) => dateList.map(row => {
*/
const getFormattedData = response => {
const recordingData = response.tabledata;
const rowData = JSON.parse(recordingData.data);
return formatDates(recordingData.locale, rowData);
return JSON.parse(recordingData.data);
};
const getTableNode = tableSelector => document.querySelector(tableSelector);
@@ -215,11 +204,11 @@ const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
// Create the confirmation dialogue.
return new Promise((resolve) =>
ModalFactory.create({
title: Str.get_string('confirm'),
title: getString('confirm'),
body: recordingConfirmationMessage(payload),
type: ModalFactory.types.SAVE_CANCEL
}).then(async(modal) => {
modal.setSaveButtonText(await Str.get_string('ok', 'moodle'));
modal.setSaveButtonText(await getString('ok', 'moodle'));
// Handle save event.
modal.getRoot().on(ModalEvents.save, () => {
@@ -248,12 +237,12 @@ const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
const recordingConfirmationMessage = async(data) => {
const playbackElement = document.querySelector(`#playbacks-${data.recordingid}`);
const recordingType = await Str.get_string(
const recordingType = await getString(
playbackElement.dataset.imported === 'true' ? 'view_recording_link' : 'view_recording',
'bigbluebuttonbn'
);
const confirmation = await Str.get_string(`view_recording_${data.action}_confirmation`, 'bigbluebuttonbn', recordingType);
const confirmation = await getString(`view_recording_${data.action}_confirmation`, 'bigbluebuttonbn', recordingType);
if (data.action === 'import') {
return confirmation;
@@ -265,7 +254,7 @@ const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
return confirmation;
}
const confirmationWarning = await Str.get_string(
const confirmationWarning = await getString(
associatedLinkCount === 1
? `view_recording_${data.action}_confirmation_warning_p`
: `view_recording_${data.action}_confirmation_warning_s`,
@@ -360,15 +349,22 @@ const setupDatatable = (tableId, searchFormId, response) => {
const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings/setupDatatable');
return Promise.all([getYuiInstance(recordingData.locale), getStringsForYui()])
.then(([yuiInstance, strings]) => {
// Here we use a custom formatter for date.
// See https://clarle.github.io/yui3/yui/docs/api/classes/DataTable.BodyView.Formatters.html
// Inspired from examples here: https://clarle.github.io/yui3/yui/docs/datatable/
// Normally formatter have the prototype: (col) => (cell) => <computed value>, see:
// https://clarle.github.io/yui3/yui/docs/api/files/datatable_js_formatters.js.html#l100 .
const dateCustomFormatter = () => (cell) => formatDate(recordingData.locale, cell.value);
// Add the fetched strings to the YUI Instance.
yuiInstance.Intl.add('datatable-paginator', yuiInstance.config.lang, {...strings});
yuiInstance.DataTable.BodyView.Formatters.customDate = dateCustomFormatter;
return yuiInstance;
})
.then(yuiInstance => {
const tableData = getFormattedData(response);
yuiInstance.RecordsPaginatorView = Y.Base.create('my-paginator-view',yuiInstance.DataTable.Paginator.View, [],{
_modelChange : function (e) {
yuiInstance.RecordsPaginatorView = Y.Base.create('my-paginator-view', yuiInstance.DataTable.Paginator.View, [], {
_modelChange: function(e) {
var changed = e.changed,
totalItems = (changed && changed.totalItems);
if (totalItems) {
@@ -376,7 +372,7 @@ const setupDatatable = (tableId, searchFormId, response) => {
}
}
});
const dataTable = new yuiInstance.DataTable({
return new yuiInstance.DataTable({
paginatorView: "RecordsPaginatorView",
width: "1195px",
columns: recordingData.columns,
@@ -385,7 +381,6 @@ const setupDatatable = (tableId, searchFormId, response) => {
paginatorLocation: ['header', 'footer'],
autoSync: true
});
return dataTable;
})
.then(dataTable => {
dataTable.render(tableId);
@@ -150,6 +150,7 @@ class get_recordings extends external_api {
'type' => new external_value(PARAM_ALPHANUMEXT, 'Column type', VALUE_OPTIONAL),
'sortable' => new external_value(PARAM_BOOL, 'Whether this column is sortable', VALUE_OPTIONAL, false),
'allowHTML' => new external_value(PARAM_BOOL, 'Whether this column contains HTML', VALUE_OPTIONAL, false),
'formatter' => new external_value(PARAM_ALPHANUMEXT, 'Formatter name', VALUE_OPTIONAL),
])),
'data' => new external_value(PARAM_RAW), // For now it will be json encoded.
], '', VALUE_OPTIONAL),
@@ -129,7 +129,7 @@ class recording_data {
'sortable' => true,
'width' => '225px',
'type' => 'html',
'allowHTML' => true,
'formatter' => 'customDate',
];
$columns[] = [
'key' => 'duration',
@@ -0,0 +1,34 @@
@mod @mod_bigbluebuttonbn @core_form @course
Feature: The recording can be managed through the room page and as a user I can interact with the table
Background: Make sure that import recording is enabled and course, activities and recording exists
Given a BigBlueButton mock server is configured
And the following "courses" exist:
| fullname | shortname | category |
| Test Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
And the following "activities" exist:
| activity | name | intro | course | idnumber | type | recordings_imported |
| bigbluebuttonbn | RoomRecordings | Test Room Recording description | C1 | bigbluebuttonbn1 | 0 | 0 |
And the following "mod_bigbluebuttonbn > meeting" exists:
| activity | RoomRecordings |
And the following "mod_bigbluebuttonbn > recordings" exist:
| bigbluebuttonbn | name | description | status | starttime |
| RoomRecordings | Recording 1 | Description 1 | 2 | 1619666194 |
| RoomRecordings | Recording 2 | Description 2 | 2 | 1639668194 |
| RoomRecordings | Recording 3 | Description 3 | 2 | 1629666194 |
| RoomRecordings | Recording 4 | Description 4 | 2 | 1649666194 |
@javascript
Scenario: Recording should be sortable by date
Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
Then I click on "th[data-yui3-col-id='date'] .yui3-datatable-sort-indicator" "css_element"
Then "Recording 1" "text" should appear before "Recording 3" "text"
Then "Recording 3" "text" should appear before "Recording 2" "text"
Then "Recording 2" "text" should appear before "Recording 4" "text"
Then I click on "th[data-yui3-col-id='date'] .yui3-datatable-sort-indicator" "css_element"
Then "Recording 1" "text" should appear after "Recording 3" "text"
Then "Recording 3" "text" should appear after "Recording 2" "text"
Then "Recording 2" "text" should appear after "Recording 4" "text"
+3 -1
View File
@@ -232,6 +232,7 @@ class mod_bigbluebuttonbn_generator extends \testing_module_generator {
* @throws moodle_exception
*/
protected function create_mockserver_recording(instance $instance, stdClass $recordingdata, array $data): string {
$now = time();
$mockdata = array_merge((array) $recordingdata, [
'meetingID' => $instance->get_meeting_id(),
'meta' => [
@@ -246,7 +247,8 @@ class mod_bigbluebuttonbn_generator extends \testing_module_generator {
'bbb-recording-tags' => $data['tags'] ?? '',
],
]);
$mockdata['startTime'] = $data['starttime'] ?? $now;
$mockdata['endTime'] = $data['endtime'] ?? $mockdata['startTime'] + HOURSECS;
$result = $this->send_mock_request('backoffice/createRecording', [], $mockdata);
return (string) $result->recordID;