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:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user