554 lines
20 KiB
JavaScript
554 lines
20 KiB
JavaScript
/* globals _, Logger, Slick, tinyMCE, InstructorDashboard, PendingInstructorTasks, createTaskListTable */
|
|
|
|
(function() {
|
|
'use strict';
|
|
var IntervalManager, KeywordValidator,
|
|
createEmailContentTable, createEmailMessageViews,
|
|
findAndAssert, pWrapper, plantInterval, plantTimeout,
|
|
sentToFormatter, setupCopyEmailButton, subjectFormatter,
|
|
unknownIfNullFormatter, unknownP,
|
|
anyOf = [].indexOf || function(item) {
|
|
var i, l;
|
|
for (i = 0, l = this.length; i < l; i++) {
|
|
if (i in this && this[i] === item) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
plantTimeout = function(ms, cb) {
|
|
return setTimeout(cb, ms);
|
|
};
|
|
|
|
plantInterval = function(ms, cb) {
|
|
return setInterval(cb, ms);
|
|
};
|
|
|
|
findAndAssert = function($root, selector) {
|
|
var item, msg;
|
|
item = $root.find(selector);
|
|
if (item.length !== 1) {
|
|
msg = 'Failed Element Selection';
|
|
throw msg;
|
|
} else {
|
|
return item;
|
|
}
|
|
};
|
|
|
|
this.statusAjaxError = function(handler) {
|
|
return function(jqXHR, textStatus, errorThrown) { // eslint-disable-line no-unused-vars
|
|
return handler.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
this.createTaskListTable = function($tableTasks, tasksData) {
|
|
var $tablePlaceholder, columns, options, tableData;
|
|
$tableTasks.empty();
|
|
options = {
|
|
enableCellNavigation: true,
|
|
enableColumnReorder: false,
|
|
autoHeight: true,
|
|
rowHeight: 100,
|
|
forceFitColumns: true
|
|
};
|
|
columns = [
|
|
{
|
|
id: 'task_type',
|
|
field: 'task_type',
|
|
/*
|
|
Translators: a "Task" is a background process such as grading students or sending email
|
|
*/
|
|
|
|
name: gettext('Task Type'),
|
|
minWidth: 102
|
|
}, {
|
|
id: 'task_input',
|
|
field: 'task_input',
|
|
/*
|
|
Translators: a "Task" is a background process such as grading students or sending email
|
|
*/
|
|
|
|
name: gettext('Task inputs'),
|
|
minWidth: 150
|
|
}, {
|
|
id: 'task_id',
|
|
field: 'task_id',
|
|
/*
|
|
Translators: a "Task" is a background process such as grading students or sending email
|
|
*/
|
|
|
|
name: gettext('Task ID'),
|
|
minWidth: 150
|
|
}, {
|
|
id: 'requester',
|
|
field: 'requester',
|
|
/*
|
|
Translators: a "Requester" is a username that requested a task such as sending email
|
|
*/
|
|
|
|
name: gettext('Requester'),
|
|
minWidth: 80
|
|
}, {
|
|
id: 'created',
|
|
field: 'created',
|
|
/*
|
|
Translators: A timestamp of when a task (eg, sending email) was submitted appears after this
|
|
*/
|
|
|
|
name: gettext('Submitted'),
|
|
minWidth: 120
|
|
}, {
|
|
id: 'duration_sec',
|
|
field: 'duration_sec',
|
|
/*
|
|
Translators: The length of a task (eg, sending email) in seconds appears this
|
|
*/
|
|
|
|
name: gettext('Duration (sec)'),
|
|
minWidth: 80
|
|
}, {
|
|
id: 'task_state',
|
|
field: 'task_state',
|
|
/*
|
|
Translators: The state (eg, "In progress") of a task (eg, sending email) appears after this.
|
|
*/
|
|
|
|
name: gettext('State'),
|
|
minWidth: 80
|
|
}, {
|
|
id: 'status',
|
|
field: 'status',
|
|
/*
|
|
Translators: a "Task" is a background process such as grading students or sending email
|
|
*/
|
|
|
|
name: gettext('Task Status'),
|
|
minWidth: 80
|
|
}, {
|
|
id: 'task_message',
|
|
field: 'task_message',
|
|
/*
|
|
Translators: a "Task" is a background process such as grading students or sending email
|
|
*/
|
|
|
|
name: gettext('Task Progress'),
|
|
minWidth: 120
|
|
}
|
|
];
|
|
tableData = tasksData;
|
|
$tablePlaceholder = $('<div/>', {
|
|
class: 'slickgrid'
|
|
});
|
|
$tableTasks.append($tablePlaceholder);
|
|
return new Slick.Grid($tablePlaceholder, tableData, columns, options);
|
|
};
|
|
|
|
subjectFormatter = function(row, cell, value) {
|
|
var subjectText;
|
|
if (value === null) {
|
|
return gettext('An error occurred retrieving your email. Please try again later, and contact technical support if the problem persists.'); // eslint-disable-line max-len
|
|
}
|
|
subjectText = $('<span>').text(value.subject).html();
|
|
return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML(
|
|
'<p><a href="#email_message_'), value.id, edx.HtmlUtils.HTML(
|
|
'" id="email_message_'), value.id, edx.HtmlUtils.HTML('_trig">'),
|
|
subjectText, edx.HtmlUtils.HTML('</a></p>')
|
|
);
|
|
};
|
|
|
|
pWrapper = function(value) {
|
|
return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<p>'), value, edx.HtmlUtils.HTML('</p>'));
|
|
};
|
|
|
|
unknownP = function() {
|
|
return pWrapper(gettext('Unknown'));
|
|
};
|
|
|
|
sentToFormatter = function(row, cell, value) {
|
|
if (value === null) {
|
|
return unknownP();
|
|
} else {
|
|
return pWrapper(value.join(', '));
|
|
}
|
|
};
|
|
|
|
unknownIfNullFormatter = function(row, cell, value) {
|
|
if (value === null) {
|
|
return unknownP();
|
|
} else {
|
|
return pWrapper(value);
|
|
}
|
|
};
|
|
|
|
createEmailContentTable = function($tableEmails, $tableEmailsInner, emailData) {
|
|
var $tablePlaceholder, columns, options, tableData;
|
|
$tableEmailsInner.empty();
|
|
$tableEmails.show();
|
|
options = {
|
|
enableCellNavigation: true,
|
|
enableColumnReorder: false,
|
|
autoHeight: true,
|
|
rowHeight: 50,
|
|
forceFitColumns: true
|
|
};
|
|
columns = [
|
|
{
|
|
id: 'email',
|
|
field: 'email',
|
|
name: gettext('Subject'),
|
|
minWidth: 80,
|
|
cssClass: 'email-content-cell',
|
|
formatter: subjectFormatter
|
|
}, {
|
|
id: 'requester',
|
|
field: 'requester',
|
|
name: gettext('Sent By'),
|
|
minWidth: 80,
|
|
maxWidth: 100,
|
|
cssClass: 'email-content-cell',
|
|
formatter: unknownIfNullFormatter
|
|
}, {
|
|
id: 'sent_to',
|
|
field: 'sent_to',
|
|
name: gettext('Sent To'),
|
|
minWidth: 80,
|
|
maxWidth: 100,
|
|
cssClass: 'email-content-cell',
|
|
formatter: sentToFormatter
|
|
}, {
|
|
id: 'created',
|
|
field: 'created',
|
|
name: gettext('Time Sent'),
|
|
minWidth: 80,
|
|
cssClass: 'email-content-cell',
|
|
formatter: unknownIfNullFormatter
|
|
}, {
|
|
id: 'number_sent',
|
|
field: 'number_sent',
|
|
name: gettext('Number Sent'),
|
|
minwidth: 100,
|
|
maxWidth: 150,
|
|
cssClass: 'email-content-cell',
|
|
formatter: unknownIfNullFormatter
|
|
}
|
|
];
|
|
tableData = emailData;
|
|
$tablePlaceholder = $('<div/>', {
|
|
class: 'slickgrid'
|
|
});
|
|
$tableEmailsInner.append($tablePlaceholder);
|
|
Slick.Grid($tablePlaceholder, tableData, columns, options);
|
|
return $tableEmails.append($('<br/>'));
|
|
};
|
|
|
|
createEmailMessageViews = function($messagesWrapper, emails) {
|
|
var $closeButton, $created, $emailContent, $emailContentHeader,
|
|
$emailHeader, $emailWrapper, $message, $messageContent,
|
|
$requester, $sentTo, $subject, emailId, emailInfo, interpolateHeader, i, len;
|
|
$messagesWrapper.empty();
|
|
for (i = 0, len = emails.length; i < len; i++) {
|
|
emailInfo = emails[i];
|
|
if (!emailInfo.email) {
|
|
return;
|
|
}
|
|
emailId = emailInfo.email.id;
|
|
$messageContent = $('<section>', {
|
|
'aria-hidden': 'true',
|
|
class: 'modal email-modal',
|
|
id: 'email_message_' + emailId
|
|
});
|
|
$emailWrapper = $('<div>', {
|
|
class: 'inner-wrapper email-content-wrapper'
|
|
});
|
|
$emailHeader = $('<div>', {
|
|
class: 'email-content-header'
|
|
});
|
|
$emailHeader.append($('<input>', {
|
|
type: 'button',
|
|
name: 'copy-email-body-text',
|
|
value: gettext('Copy Email To Editor'),
|
|
id: 'copy_email_' + emailId
|
|
}));
|
|
$closeButton = $('<a>', {
|
|
href: '#',
|
|
class: 'close-modal'
|
|
});
|
|
$closeButton.append($('<i>', {
|
|
class: 'icon fa fa-times'
|
|
}));
|
|
$emailHeader.append($closeButton);
|
|
interpolateHeader = function(title, value) {
|
|
return edx.HtmlUtils.setHtml($('<h2>', {
|
|
class: 'message-bold'
|
|
}), edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<em>'), title, edx.HtmlUtils.HTML('</em>'), value));
|
|
};
|
|
$subject = interpolateHeader(gettext('Subject:'), emailInfo.email.subject);
|
|
$requester = interpolateHeader(gettext('Sent By:'), emailInfo.requester);
|
|
$created = interpolateHeader(gettext('Time Sent:'), emailInfo.created);
|
|
$sentTo = interpolateHeader(gettext('Sent To:'), emailInfo.sent_to.join(', '));
|
|
$emailHeader.append($subject);
|
|
$emailHeader.append($requester);
|
|
$emailHeader.append($created);
|
|
$emailHeader.append($sentTo);
|
|
$emailWrapper.append($emailHeader);
|
|
$emailWrapper.append($('<hr>'));
|
|
$emailContent = $('<div>', {
|
|
class: 'email-content-message'
|
|
});
|
|
$emailContentHeader = edx.HtmlUtils.setHtml($('<h2>', {
|
|
class: 'message-bold'
|
|
}), edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<em>'), gettext('Message:'), edx.HtmlUtils.HTML('</em>')));
|
|
$emailContent.append($emailContentHeader);
|
|
$message = edx.HtmlUtils.setHtml($('<div>'), edx.HtmlUtils.HTML(emailInfo.email.html_message));
|
|
$emailContent.append($message);
|
|
$emailWrapper.append($emailContent);
|
|
$messageContent.append($emailWrapper);
|
|
$messagesWrapper.append($messageContent);
|
|
$('#email_message_' + emailInfo.email.id + '_trig').leanModal({
|
|
closeButton: '.close-modal',
|
|
copyEmailButton: '#copy_email_' + emailId
|
|
});
|
|
setupCopyEmailButton(emailId, emailInfo.email.html_message, emailInfo.email.subject);
|
|
}
|
|
};
|
|
|
|
setupCopyEmailButton = function(emailId, htmlMessage, subject) {
|
|
return $('#copy_email_' + emailId).click(function() {
|
|
var editor;
|
|
editor = tinyMCE.get('mce_0');
|
|
editor.setContent(htmlMessage);
|
|
return $('#id_subject').val(subject);
|
|
});
|
|
};
|
|
|
|
IntervalManager = (function() {
|
|
function intervalManager(ms, fn) {
|
|
this.ms = ms;
|
|
this.fn = fn;
|
|
this.intervalID = null;
|
|
this.failedTries = 0;
|
|
}
|
|
|
|
intervalManager.prototype.start = function() {
|
|
this.fn();
|
|
if (this.intervalID === null) {
|
|
this.intervalID = setInterval(this.fn, this.ms);
|
|
return this.intervalID;
|
|
}
|
|
return this.intervalID;
|
|
};
|
|
|
|
intervalManager.prototype.stop = function() {
|
|
clearInterval(this.intervalID);
|
|
this.intervalID = null;
|
|
return this.intervalID;
|
|
};
|
|
|
|
intervalManager.prototype.failed_retry_threshold = 5;
|
|
|
|
intervalManager.prototype.backOff = function() {
|
|
this.failedTries++;
|
|
if (this.failedTries >= this.failed_retry_threshold) {
|
|
this.stop();
|
|
}
|
|
};
|
|
|
|
return intervalManager;
|
|
}());
|
|
|
|
this.PendingInstructorTasks = (function() {
|
|
function PendingInstructorTasks($section) {
|
|
var TASK_LIST_POLL_INTERVAL,
|
|
ths = this;
|
|
this.$section = $section;
|
|
this.reload_running_tasks_list = function() {
|
|
return PendingInstructorTasks.prototype.reload_running_tasks_list.apply(
|
|
ths, arguments
|
|
);
|
|
};
|
|
this.$running_tasks_section = findAndAssert(this.$section, '.running-tasks-section');
|
|
this.$table_running_tasks = findAndAssert(this.$section, '.running-tasks-table');
|
|
this.$no_tasks_message = findAndAssert(this.$section, '.no-pending-tasks-message');
|
|
if (this.$table_running_tasks.length) {
|
|
TASK_LIST_POLL_INTERVAL = 20000;
|
|
this.reload_running_tasks_list();
|
|
this.task_poller = new IntervalManager(TASK_LIST_POLL_INTERVAL, function() {
|
|
return ths.reload_running_tasks_list();
|
|
});
|
|
}
|
|
}
|
|
|
|
PendingInstructorTasks.prototype.reload_running_tasks_list = function() {
|
|
var listEndpoint,
|
|
ths = this;
|
|
listEndpoint = this.$table_running_tasks.data('endpoint');
|
|
return $.ajax({
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
url: listEndpoint,
|
|
success: function(data) {
|
|
if (data.tasks.length) {
|
|
createTaskListTable(ths.$table_running_tasks, data.tasks);
|
|
ths.$no_tasks_message.hide();
|
|
return ths.$running_tasks_section.show();
|
|
} else {
|
|
ths.$running_tasks_section.hide();
|
|
ths.$no_tasks_message.empty();
|
|
ths.$no_tasks_message.append($('<p>').text(gettext('No tasks currently running.')));
|
|
return ths.$no_tasks_message.show();
|
|
}
|
|
},
|
|
error: function() {
|
|
ths.task_poller.backOff();
|
|
}
|
|
});
|
|
};
|
|
|
|
return PendingInstructorTasks;
|
|
}());
|
|
|
|
KeywordValidator = (function() {
|
|
function keywordValidator() {}
|
|
|
|
keywordValidator.keyword_regex = /%%+[^%]+%%/g;
|
|
|
|
keywordValidator.keywords = [
|
|
'%%USER_ID%%', '%%USER_FULLNAME%%', '%%COURSE_DISPLAY_NAME%%', '%%COURSE_END_DATE%%'
|
|
];
|
|
|
|
keywordValidator.validate_string = function(string) {
|
|
var foundKeyword, foundKeywords, invalidKeywords, isValid,
|
|
keywords, regexMatch, validation, i, len;
|
|
regexMatch = string.match(KeywordValidator.keyword_regex);
|
|
foundKeywords = regexMatch === null ? [] : regexMatch;
|
|
invalidKeywords = [];
|
|
isValid = true;
|
|
keywords = KeywordValidator.keywords;
|
|
validation = function(foundkeyword) {
|
|
if (anyOf.call(keywords, foundkeyword) < 0) {
|
|
return invalidKeywords.push(foundkeyword);
|
|
} else {
|
|
return invalidKeywords;
|
|
}
|
|
};
|
|
for (i = 0, len = foundKeywords.length; i < len; i++) {
|
|
foundKeyword = foundKeywords[i];
|
|
validation(foundKeyword);
|
|
}
|
|
if (invalidKeywords.length !== 0) {
|
|
isValid = false;
|
|
}
|
|
return {
|
|
isValid: isValid,
|
|
invalidKeywords: invalidKeywords
|
|
};
|
|
};
|
|
|
|
return keywordValidator;
|
|
}).call(this);
|
|
|
|
this.ReportDownloads = (function() {
|
|
/* Report Downloads -- links expire quickly, so we refresh every 5 mins
|
|
*/
|
|
|
|
function ReportDownloads($section) {
|
|
var POLL_INTERVAL,
|
|
reportdownloads = this;
|
|
this.$section = $section;
|
|
this.$report_downloads_table = this.$section.find('.report-downloads-table');
|
|
POLL_INTERVAL = 20000;
|
|
this.downloads_poller = new InstructorDashboard.util.IntervalManager(POLL_INTERVAL, function() {
|
|
return reportdownloads.reload_report_downloads();
|
|
});
|
|
}
|
|
|
|
ReportDownloads.prototype.reload_report_downloads = function() {
|
|
var endpoint,
|
|
ths = this;
|
|
endpoint = this.$report_downloads_table.data('endpoint');
|
|
return $.ajax({
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
url: endpoint,
|
|
success: function(data) {
|
|
if (data.downloads.length) {
|
|
return ths.create_report_downloads_table(data.downloads);
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
error: function() {
|
|
ths.downloads_poller.backOff();
|
|
}
|
|
});
|
|
};
|
|
|
|
ReportDownloads.prototype.create_report_downloads_table = function(reportDownloadsData) {
|
|
var $tablePlaceholder, columns, grid, options;
|
|
this.$report_downloads_table.empty();
|
|
options = {
|
|
enableCellNavigation: true,
|
|
enableColumnReorder: false,
|
|
rowHeight: 30,
|
|
forceFitColumns: true
|
|
};
|
|
columns = [
|
|
{
|
|
id: 'link',
|
|
field: 'link',
|
|
name: gettext('File Name'),
|
|
toolTip: gettext('Links are generated on demand and expire within 5 minutes due to the sensitive nature of student information.'), // eslint-disable-line max-len
|
|
sortable: false,
|
|
minWidth: 150,
|
|
cssClass: 'file-download-link',
|
|
formatter: function(row, cell, value, columnDef, dataContext) {
|
|
return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML(
|
|
'<a rel="noopener" target="_blank" href="'), dataContext.url,
|
|
edx.HtmlUtils.HTML('">'), dataContext.name,
|
|
edx.HtmlUtils.HTML('</a>'));
|
|
}
|
|
}
|
|
];
|
|
$tablePlaceholder = $('<div/>', {
|
|
class: 'slickgrid'
|
|
});
|
|
this.$report_downloads_table.append($tablePlaceholder);
|
|
grid = new Slick.Grid($tablePlaceholder, reportDownloadsData, columns, options);
|
|
grid.onClick.subscribe(function(event) {
|
|
var reportUrl;
|
|
reportUrl = event.target.href;
|
|
if (reportUrl) {
|
|
return Logger.log('edx.instructor.report.downloaded', {
|
|
report_url: reportUrl
|
|
});
|
|
}
|
|
return Logger.log('edx.instructor.report.downloaded', {
|
|
report_url: reportUrl
|
|
});
|
|
});
|
|
return grid.autosizeColumns();
|
|
};
|
|
|
|
return ReportDownloads;
|
|
}());
|
|
|
|
if (typeof _ !== 'undefined' && _ !== null) {
|
|
_.defaults(window, {
|
|
InstructorDashboard: {}
|
|
});
|
|
window.InstructorDashboard.util = {
|
|
plantTimeout: plantTimeout,
|
|
plantInterval: plantInterval,
|
|
statusAjaxError: this.statusAjaxError,
|
|
IntervalManager: IntervalManager,
|
|
createTaskListTable: createTaskListTable,
|
|
createEmailContentTable: createEmailContentTable,
|
|
createEmailMessageViews: createEmailMessageViews,
|
|
PendingInstructorTasks: PendingInstructorTasks,
|
|
KeywordValidator: KeywordValidator,
|
|
ReportDownloads: this.ReportDownloads
|
|
};
|
|
}
|
|
}).call(this);
|