Commit 5fe397f8 by alisan617

change Instructor Dashboard coffeeScript bundle path and move fixtures to JS folder, plus eslint

parent 7b9c566f
...@@ -1319,10 +1319,7 @@ discussion_vendor_js = [ ...@@ -1319,10 +1319,7 @@ discussion_vendor_js = [
] ]
notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js')) notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js'))
instructor_dash_js = ( instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js'))
sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/instructor_dashboard/**/*.js')) +
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js'))
)
verify_student_js = [ verify_student_js = [
'js/sticky_filter.js', 'js/sticky_filter.js',
......
describe 'AutoEnrollment', ->
beforeEach ->
loadFixtures 'coffee/fixtures/autoenrollment.html'
@autoenrollment = new AutoEnrollmentViaCsv $('.auto_enroll_csv')
it 'binds the ajax call and the result will be success', ->
spyOn($, "ajax").and.callFake((params) =>
params.success({row_errors: [], general_errors: [], warnings: []})
{always: ->}
)
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake =>
return '<div><div class="message message-confirmation"><h3 class="message-title">Success</h3><div class="message-copy"><p>All accounts were created successfully.</p></div></div><div>'
submitCallback = jasmine.createSpy().and.returnValue()
@autoenrollment.$student_enrollment_form.submit(submitCallback)
@autoenrollment.$enrollment_signup_button.click()
expect($('.results .message-copy').text()).toEqual('All accounts were created successfully.')
expect(submitCallback).toHaveBeenCalled()
it 'binds the ajax call and the result will be error', ->
spyOn($, "ajax").and.callFake((params) =>
params.success({
row_errors: [{
'username': 'testuser1',
'email': 'testemail1@email.com',
'response': 'Username already exists'
}],
general_errors: [{
'response': 'cannot read the line 2'
}],
warnings: []
})
{always: ->}
)
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake =>
return '<div><div class="message message-error"><h3 class="message-title">Errors</h3><div class="message-copy"><p>The following errors were generated:</p><ul class="list-summary summary-items"><li class="summary-item">cannot read the line 2</li><li class="summary-item">testuser1 (testemail1@email.com): (Username already exists)</li></ul></div></div></div>'
submitCallback = jasmine.createSpy().and.returnValue()
@autoenrollment.$student_enrollment_form.submit(submitCallback)
@autoenrollment.$enrollment_signup_button.click()
expect($('.results .list-summary').text()).toEqual('cannot read the line 2testuser1 (testemail1@email.com): (Username already exists)');
expect(submitCallback).toHaveBeenCalled()
it 'binds the ajax call and the result will be warnings', ->
spyOn($, "ajax").and.callFake((params) =>
params.success({
row_errors: [],
general_errors: [],
warnings: [{
'username': 'user1',
'email': 'user1email',
'response': 'email is in valid'
}]
})
{always: ->}
)
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").and.callFake =>
return '<div><div class="message message-warning"><h3 class="message-title">Warnings</h3><div class="message-copy"><p>The following warnings were generated:</p><ul class="list-summary summary-items"><li class="summary-item">user1 (user1email): (email is in valid)</li></ul></div></div></div>'
submitCallback = jasmine.createSpy().and.returnValue()
@autoenrollment.$student_enrollment_form.submit(submitCallback)
@autoenrollment.$enrollment_signup_button.click()
expect($('.results .list-summary').text()).toEqual('user1 (user1email): (email is in valid)')
expect(submitCallback).toHaveBeenCalled()
\ No newline at end of file
describe "Bulk Email Queueing", ->
beforeEach ->
testSubject = "Test Subject"
testBody = "Hello, World! This is a test email message!"
loadFixtures 'coffee/fixtures/send_email.html'
@send_email = new SendEmail $('.send-email')
@send_email.$subject.val(testSubject)
@send_email.$send_to.first().prop("checked", true)
@send_email.$emailEditor =
save: ->
{"data": testBody}
@ajax_params = {
type: "POST",
dataType: "json",
url: undefined,
data: {
action: "send",
send_to: JSON.stringify([@send_email.$send_to.first().val()]),
subject: testSubject,
message: testBody,
},
success: jasmine.any(Function),
error: jasmine.any(Function),
}
it 'cannot send an email with no target', ->
spyOn(window, "alert")
spyOn($, "ajax")
for target in @send_email.$send_to
target.checked = false
@send_email.$btn_send.click()
expect(window.alert).toHaveBeenCalledWith("Your message must have at least one target.")
expect($.ajax).not.toHaveBeenCalled()
it 'cannot send an email with no subject', ->
spyOn(window, "alert")
spyOn($, "ajax")
@send_email.$subject.val("")
@send_email.$btn_send.click()
expect(window.alert).toHaveBeenCalledWith("Your message must have a subject.")
expect($.ajax).not.toHaveBeenCalled()
it 'cannot send an email with no message', ->
spyOn(window, "alert")
spyOn($, "ajax")
@send_email.$emailEditor =
save: ->
{"data": ""}
@send_email.$btn_send.click()
expect(window.alert).toHaveBeenCalledWith("Your message cannot be blank.")
expect($.ajax).not.toHaveBeenCalled()
it 'can send a simple message to a single target', ->
spyOn($, "ajax").and.callFake((params) =>
params.success()
)
@send_email.$btn_send.click()
expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.')
expect($.ajax).toHaveBeenCalledWith(@ajax_params)
it 'can send a simple message to a multiple targets', ->
spyOn($, "ajax").and.callFake((params) =>
params.success()
)
@ajax_params.data.send_to = JSON.stringify(target.value for target in @send_email.$send_to)
for target in @send_email.$send_to
target.checked = true
@send_email.$btn_send.click()
expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.')
expect($.ajax).toHaveBeenCalledWith(@ajax_params)
it 'can handle an error result from the bulk email api', ->
spyOn($, "ajax").and.callFake((params) =>
params.error()
)
spyOn(console, "warn")
@send_email.$btn_send.click()
expect($('.request-response-error').text()).toEqual('Error sending email.')
expect(console.warn).toHaveBeenCalled()
it 'selecting all learners disables cohort selections', ->
@send_email.$send_to.filter("[value='learners']").click
@send_email.$cohort_targets.each ->
expect(this.disabled).toBe(true)
@send_email.$send_to.filter("[value='learners']").click
@send_email.$cohort_targets.each ->
expect(this.disabled).toBe(false)
it 'selected targets are listed after "send to:"', ->
@send_email.$send_to.click
$('input[name="send_to"]:checked+label').each ->
expect($('.send_to_list'.text())).toContain(this.innerText.replace(/\s*\n.*/g,''))
### (function() {
Course Info Section 'use strict';
var InstructorDashboardCourseInfo, PendingInstructorTasks;
imports from other modules.
wrap in (-> ... apply) to defer evaluation PendingInstructorTasks = function() {
such that the value can be defined later than this assignment (file load order). return window.InstructorDashboard.util.PendingInstructorTasks;
### };
# Load utilities InstructorDashboardCourseInfo = (function() {
PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks function CourseInfo($section) {
var courseInfo = this;
# A typical section object. this.$section = $section;
# constructed with $section, a jquery object this.$section.data('wrapper', this);
# which holds the section body container. this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
class CourseInfo this.$course_errors_wrapper = this.$section.find('.course-errors-wrapper');
constructor: (@$section) -> if (this.$course_errors_wrapper.length) {
# attach self to html so that instructor_dashboard.coffee can find this.$course_error_toggle = this.$course_errors_wrapper.find('.toggle-wrapper');
# this object to call event handlers like 'onClickTitle' this.$course_error_toggle_text = this.$course_error_toggle.find('h2');
@$section.data 'wrapper', @ this.$course_errors = this.$course_errors_wrapper.find('.course-error');
this.$course_error_toggle_text.text(
# gather elements this.$course_error_toggle_text.text() + (" (' + this.$course_errors.length + ')")
@instructor_tasks = new (PendingInstructorTasks()) @$section );
@$course_errors_wrapper = @$section.find '.course-errors-wrapper' this.$course_error_toggle.click(function(e) {
e.preventDefault();
# if there are errors if (courseInfo.$course_errors_wrapper.hasClass('open')) {
if @$course_errors_wrapper.length return courseInfo.$course_errors_wrapper.removeClass('open');
@$course_error_toggle = @$course_errors_wrapper.find '.toggle-wrapper' } else {
@$course_error_toggle_text = @$course_error_toggle.find 'h2' return courseInfo.$course_errors_wrapper.addClass('open');
@$course_error_visibility_wrapper = @$course_errors_wrapper.find '.course-errors-visibility-wrapper' }
@$course_errors = @$course_errors_wrapper.find '.course-error' });
}
# append "(34)" to the course errors label }
@$course_error_toggle_text.text @$course_error_toggle_text.text() + " (#{@$course_errors.length})"
CourseInfo.prototype.onClickTitle = function() {
# toggle .open class on errors return this.instructor_tasks.task_poller.start();
# to show and hide them. };
@$course_error_toggle.click (e) =>
e.preventDefault() CourseInfo.prototype.onExit = function() {
if @$course_errors_wrapper.hasClass 'open' return this.instructor_tasks.task_poller.stop();
@$course_errors_wrapper.removeClass 'open' };
else
@$course_errors_wrapper.addClass 'open' return CourseInfo;
}());
# handler for when the section title is clicked.
onClickTitle: -> @instructor_tasks.task_poller.start() window.InstructorDashboard.sections.CourseInfo = InstructorDashboardCourseInfo;
}).call(this);
# handler for when the section is closed
onExit: -> @instructor_tasks.task_poller.stop()
# export for use
# create parent namespaces if they do not already exist.
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
CourseInfo: CourseInfo
### /* globals _ */
Data Download Section
(function() {
imports from other modules. 'use strict';
wrap in (-> ... apply) to defer evaluation var DataDownload, DataDownloadCertificate, PendingInstructorTasks, ReportDownloads, statusAjaxError;
such that the value can be defined later than this assignment (file load order).
### statusAjaxError = function() {
return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
# Load utilities };
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks PendingInstructorTasks = function() {
ReportDownloads = -> window.InstructorDashboard.util.ReportDownloads return window.InstructorDashboard.util.PendingInstructorTasks;
};
# Data Download Certificate issued
class @DataDownload_Certificate ReportDownloads = function() {
constructor: (@$container) -> return window.InstructorDashboard.util.ReportDownloads;
# gather elements };
@$list_issued_certificate_table_btn = @$container.find("input[name='issued-certificates-list']")
@$list_issued_certificate_csv_btn = @$container.find("input[name='issued-certificates-csv']") DataDownloadCertificate = (function() {
@$certificate_display_table = @$container.find '.certificate-data-display-table' function InstructorDashboardDataDownloadCertificate($container) {
@$certificates_request_response_error = @$container.find '.issued-certificates-error.request-response-error' var dataDownloadCert = this;
this.$container = $container;
this.$list_issued_certificate_table_btn = this.$container.find("input[name='issued-certificates-list']");
@$list_issued_certificate_table_btn.click (e) => this.$list_issued_certificate_csv_btn = this.$container.find("input[name='issued-certificates-csv']");
url = @$list_issued_certificate_table_btn.data 'endpoint' this.$certificate_display_table = this.$container.find('.certificate-data-display-table');
# Dynamically generate slickgrid table for displaying issued certificate information. this.$certificates_request_err = this.$container.find('.issued-certificates-error.request-response-error');
@clear_ui() this.$list_issued_certificate_table_btn.click(function() {
@$certificate_display_table.text gettext('Loading data...') var url = dataDownloadCert.$list_issued_certificate_table_btn.data('endpoint');
# fetch user list dataDownloadCert.clear_ui();
$.ajax dataDownloadCert.$certificate_display_table.text(gettext('Loading data...'));
type: 'POST' return $.ajax({
url: url type: 'POST',
error: (std_ajax_err) => url: url,
@clear_ui() error: function() {
@$certificates_request_response_error.text gettext("Error getting issued certificates list.") dataDownloadCert.clear_ui();
$(".issued_certificates .issued-certificates-error.msg-error").css({"display":"block"}) dataDownloadCert.$certificates_request_err.text(
success: (data) => gettext('Error getting issued certificates list.')
@clear_ui() );
# display on a SlickGrid return $('.issued_certificates .issued-certificates-error.msg-error').css({
options = display: 'block'
enableCellNavigation: true });
enableColumnReorder: false },
forceFitColumns: true success: function(data) {
var $tablePlaceholder, columns, feature, gridData, options;
dataDownloadCert.clear_ui();
options = {
enableCellNavigation: true,
enableColumnReorder: false,
forceFitColumns: true,
rowHeight: 35 rowHeight: 35
};
columns = ({id: feature, field: feature, name: data.feature_names[feature]} for feature in data.queried_features) columns = (function() {
grid_data = data.certificates var i, len, ref, results;
ref = data.queried_features;
$table_placeholder = $ '<div/>', class: 'slickgrid' results = [];
@$certificate_display_table.append $table_placeholder for (i = 0, len = ref.length; i < len; i++) {
new Slick.Grid($table_placeholder, grid_data, columns, options) feature = ref[i];
results.push({
@$list_issued_certificate_csv_btn.click (e) => id: feature,
@clear_ui() field: feature,
url = @$list_issued_certificate_csv_btn.data 'endpoint' name: data.feature_names[feature]
location.href = url + '?csv=true' });
}
clear_ui: -> return results;
# Clear any generated tables, warning messages, etc of certificates. }());
@$certificate_display_table.empty() gridData = data.certificates;
@$certificates_request_response_error.empty() $tablePlaceholder = $('<div/>', {
$(".issued-certificates-error.msg-error").css({"display":"none"}) class: 'slickgrid'
});
# Data Download Section dataDownloadCert.$certificate_display_table.append($tablePlaceholder);
class DataDownload return new window.Slick.Grid($tablePlaceholder, gridData, columns, options);
constructor: (@$section) -> }
# attach self to html so that instructor_dashboard.coffee can find });
# this object to call event handlers like 'onClickTitle' });
@$section.data 'wrapper', @ this.$list_issued_certificate_csv_btn.click(function() {
dataDownloadCert.clear_ui();
# isolate # initialize DataDownload_Certificate subsection location.href = dataDownloadCert.$list_issued_certificate_csv_btn.data('endpoint') + '?csv=true';
new DataDownload_Certificate @$section.find '.issued_certificates' });
}
# gather elements
@$list_studs_btn = @$section.find("input[name='list-profiles']") InstructorDashboardDataDownloadCertificate.prototype.clear_ui = function() {
@$list_studs_csv_btn = @$section.find("input[name='list-profiles-csv']") this.$certificate_display_table.empty();
@$list_proctored_exam_results_csv_btn = @$section.find("input[name='proctored-exam-results-report']") this.$certificates_request_err.empty();
@$survey_results_csv_btn = @$section.find("input[name='survey-results-report']") return $('.issued-certificates-error.msg-error').css({
@$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']") display: 'none'
@$list_problem_responses_csv_input = @$section.find("input[name='problem-location']") });
@$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']") };
@$list_anon_btn = @$section.find("input[name='list-anon-ids']")
@$grade_config_btn = @$section.find("input[name='dump-gradeconf']") return InstructorDashboardDataDownloadCertificate;
@$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']") }());
@$problem_grade_report_csv_btn = @$section.find("input[name='problem-grade-report']")
@$async_report_btn = @$section.find("input[class='async-report-btn']") DataDownload = (function() {
function InstructorDashboardDataDownload($section) {
# response areas var dataDownloadObj = this;
@$download = @$section.find '.data-download-container' this.$section = $section;
@$download_display_text = @$download.find '.data-display-text' this.$section.data('wrapper', this);
@$download_request_response_error = @$download.find '.request-response-error' this.ddc = new DataDownloadCertificate(this.$section.find('.issued_certificates'));
@$reports = @$section.find '.reports-download-container' this.$list_studs_btn = this.$section.find("input[name='list-profiles']");
@$download_display_table = @$reports.find '.profile-data-display-table' this.$list_studs_csv_btn = this.$section.find("input[name='list-profiles-csv']");
@$reports_request_response = @$reports.find '.request-response' this.$proctored_exam_csv_btn = this.$section.find("input[name='proctored-exam-results-report']");
@$reports_request_response_error = @$reports.find '.request-response-error' this.$survey_results_csv_btn = this.$section.find("input[name='survey-results-report']");
this.$list_may_enroll_csv_btn = this.$section.find("input[name='list-may-enroll-csv']");
@report_downloads = new (ReportDownloads()) @$section this.$list_problem_responses_csv_input = this.$section.find("input[name='problem-location']");
@instructor_tasks = new (PendingInstructorTasks()) @$section this.$list_problem_responses_csv_btn = this.$section.find("input[name='list-problem-responses-csv']");
@clear_display() this.$list_anon_btn = this.$section.find("input[name='list-anon-ids']");
this.$grade_config_btn = this.$section.find("input[name='dump-gradeconf']");
# attach click handlers this.$calculate_grades_csv_btn = this.$section.find("input[name='calculate-grades-csv']");
# The list-anon case is always CSV this.$problem_grade_report_csv_btn = this.$section.find("input[name='problem-grade-report']");
@$list_anon_btn.click (e) => this.$async_report_btn = this.$section.find("input[class='async-report-btn']");
url = @$list_anon_btn.data 'endpoint' this.$download = this.$section.find('.data-download-container');
location.href = url this.$download_display_text = this.$download.find('.data-display-text');
this.$download_request_response_error = this.$download.find('.request-response-error');
# attach click handlers this.$reports = this.$section.find('.reports-download-container');
# The list_proctored_exam_results case is always CSV this.$download_display_table = this.$reports.find('.profile-data-display-table');
@$list_proctored_exam_results_csv_btn.click (e) => this.$reports_request_response = this.$reports.find('.request-response');
url = @$list_proctored_exam_results_csv_btn.data 'endpoint' this.$reports_request_response_error = this.$reports.find('.request-response-error');
# display html from proctored exam results config endpoint this.report_downloads = new (ReportDownloads())(this.$section);
$.ajax this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
type: 'POST' this.clear_display();
dataType: 'json' this.$list_anon_btn.click(function() {
url: url location.href = dataDownloadObj.$list_anon_btn.data('endpoint');
error: (std_ajax_err) => });
@clear_display() this.$proctored_exam_csv_btn.click(function() {
@$reports_request_response_error.text gettext( var url = dataDownloadObj.$proctored_exam_csv_btn.data('endpoint');
"Error generating proctored exam results. Please try again." return $.ajax({
) type: 'POST',
$(".msg-error").css({"display":"block"}) dataType: 'json',
success: (data) => url: url,
@clear_display() error: function() {
@$reports_request_response.text data['status'] dataDownloadObj.clear_display();
$(".msg-confirm").css({"display":"block"}) dataDownloadObj.$reports_request_response_error.text(
gettext('Error generating proctored exam results. Please try again.')
# attach click handlers );
# The list_proctored_exam_results case is always CSV return $('.msg-error').css({
@$survey_results_csv_btn.click (e) => display: 'block'
url = @$survey_results_csv_btn.data 'endpoint' });
# display html from survey results config endpoint },
$.ajax success: function(data) {
type: 'POST' dataDownloadObj.clear_display();
dataType: 'json' dataDownloadObj.$reports_request_response.text(data.status);
url: url return $('.msg-confirm').css({
error: (std_ajax_err) => display: 'block'
@clear_display() });
@$reports_request_response_error.text gettext( }
"Error generating survey results. Please try again." });
) });
$(".msg-error").css({"display":"block"}) this.$survey_results_csv_btn.click(function() {
success: (data) => var url = dataDownloadObj.$survey_results_csv_btn.data('endpoint');
@clear_display() return $.ajax({
@$reports_request_response.text data['status'] type: 'POST',
$(".msg-confirm").css({"display":"block"}) dataType: 'json',
url: url,
# this handler binds to both the download error: function() {
# and the csv button dataDownloadObj.clear_display();
@$list_studs_csv_btn.click (e) => dataDownloadObj.$reports_request_response_error.text(
@clear_display() gettext('Error generating survey results. Please try again.')
);
url = @$list_studs_csv_btn.data 'endpoint' return $('.msg-error').css({
# handle csv special case display: 'block'
# redirect the document to the csv file. });
url += '/csv' },
success: function(data) {
$.ajax dataDownloadObj.clear_display();
type: 'POST' dataDownloadObj.$reports_request_response.text(data.status);
dataType: 'json' return $('.msg-confirm').css({
url: url display: 'block'
error: (std_ajax_err) => });
@$reports_request_response_error.text gettext("Error generating student profile information. Please try again.") }
$(".msg-error").css({"display":"block"}) });
success: (data) => });
@$reports_request_response.text data['status'] this.$list_studs_csv_btn.click(function() {
$(".msg-confirm").css({"display":"block"}) var url = dataDownloadObj.$list_studs_csv_btn.data('endpoint') + '/csv';
dataDownloadObj.clear_display();
@$list_studs_btn.click (e) => return $.ajax({
url = @$list_studs_btn.data 'endpoint' type: 'POST',
dataType: 'json',
# Dynamically generate slickgrid table for displaying student profile information url: url,
@clear_display() error: function() {
@$download_display_table.text gettext('Loading') dataDownloadObj.$reports_request_response_error.text(
gettext('Error generating student profile information. Please try again.')
# fetch user list );
$.ajax return $('.msg-error').css({
type: 'POST' display: 'block'
dataType: 'json' });
url: url },
error: (std_ajax_err) => success: function(data) {
@clear_display() dataDownloadObj.$reports_request_response.text(data.status);
@$download_request_response_error.text gettext("Error getting student list.") return $('.msg-confirm').css({
success: (data) => display: 'block'
@clear_display() });
}
# display on a SlickGrid });
options = });
enableCellNavigation: true this.$list_studs_btn.click(function() {
enableColumnReorder: false var url = dataDownloadObj.$list_studs_btn.data('endpoint');
forceFitColumns: true dataDownloadObj.clear_display();
dataDownloadObj.$download_display_table.text(gettext('Loading'));
return $.ajax({
type: 'POST',
dataType: 'json',
url: url,
error: function() {
dataDownloadObj.clear_display();
return dataDownloadObj.$download_request_response_error.text(
gettext('Error getting student list.')
);
},
success: function(data) {
var $tablePlaceholder, columns, feature, gridData, options;
dataDownloadObj.clear_display();
options = {
enableCellNavigation: true,
enableColumnReorder: false,
forceFitColumns: true,
rowHeight: 35 rowHeight: 35
};
columns = ({id: feature, field: feature, name: data.feature_names[feature]} for feature in data.queried_features) columns = (function() {
grid_data = data.students var i, len, ref, results;
ref = data.queried_features;
$table_placeholder = $ '<div/>', class: 'slickgrid' results = [];
@$download_display_table.append $table_placeholder for (i = 0, len = ref.length; i < len; i++) {
grid = new Slick.Grid($table_placeholder, grid_data, columns, options) feature = ref[i];
# grid.autosizeColumns() results.push({
id: feature,
@$list_problem_responses_csv_btn.click (e) => field: feature,
@clear_display() name: data.feature_names[feature]
});
url = @$list_problem_responses_csv_btn.data 'endpoint' }
$.ajax return results;
type: 'POST' }());
dataType: 'json' gridData = data.students;
url: url $tablePlaceholder = $('<div/>', {
data: class: 'slickgrid'
problem_location: @$list_problem_responses_csv_input.val() });
error: (std_ajax_err) => dataDownloadObj.$download_display_table.append($tablePlaceholder);
@$reports_request_response_error.text JSON.parse(std_ajax_err['responseText']) return new window.Slick.Grid($tablePlaceholder, gridData, columns, options);
$(".msg-error").css({"display":"block"}) }
success: (data) => });
@$reports_request_response.text data['status'] });
$(".msg-confirm").css({"display":"block"}) this.$list_problem_responses_csv_btn.click(function() {
var url = dataDownloadObj.$list_problem_responses_csv_btn.data('endpoint');
@$list_may_enroll_csv_btn.click (e) => dataDownloadObj.clear_display();
@clear_display() return $.ajax({
type: 'POST',
url = @$list_may_enroll_csv_btn.data 'endpoint' dataType: 'json',
$.ajax url: url,
type: 'POST' data: {
dataType: 'json' problem_location: dataDownloadObj.$list_problem_responses_csv_input.val()
url: url },
error: (std_ajax_err) => error: function(error) {
@$reports_request_response_error.text gettext("Error generating list of students who may enroll. Please try again.") dataDownloadObj.$reports_request_response_error.text(
$(".msg-error").css({"display":"block"}) JSON.parse(error.responseText)
success: (data) => );
@$reports_request_response.text data['status'] return $('.msg-error').css({
$(".msg-confirm").css({"display":"block"}) display: 'block'
});
@$grade_config_btn.click (e) => },
url = @$grade_config_btn.data 'endpoint' success: function(data) {
# display html from grading config endpoint dataDownloadObj.$reports_request_response.text(data.status);
$.ajax return $('.msg-confirm').css({
type: 'POST' display: 'block'
dataType: 'json' });
url: url }
error: (std_ajax_err) => });
@clear_display() });
@$download_request_response_error.text gettext("Error retrieving grading configuration.") this.$list_may_enroll_csv_btn.click(function() {
success: (data) => var url = dataDownloadObj.$list_may_enroll_csv_btn.data('endpoint');
@clear_display() dataDownloadObj.clear_display();
@$download_display_text.html data['grading_config_summary'] return $.ajax({
type: 'POST',
@$async_report_btn.click (e) => dataType: 'json',
# Clear any CSS styling from the request-response areas url: url,
#$(".msg-confirm").css({"display":"none"}) error: function() {
#$(".msg-error").css({"display":"none"}) dataDownloadObj.$reports_request_response_error.text(
@clear_display() gettext('Error generating list of students who may enroll. Please try again.')
url = $(e.target).data 'endpoint' );
$.ajax return $('.msg-error').css({
type: 'POST' display: 'block'
dataType: 'json' });
url: url },
error: std_ajax_err => success: function(data) {
if e.target.name == 'calculate-grades-csv' dataDownloadObj.$reports_request_response.text(data.status);
@$grades_request_response_error.text gettext("Error generating grades. Please try again.") return $('.msg-confirm').css({
else if e.target.name == 'problem-grade-report' display: 'block'
@$grades_request_response_error.text gettext("Error generating problem grade report. Please try again.") });
else if e.target.name == 'export-ora2-data' }
@$grades_request_response_error.text gettext("Error generating ORA data report. Please try again.") });
$(".msg-error").css({"display":"block"}) });
success: (data) => this.$grade_config_btn.click(function() {
@$reports_request_response.text data['status'] var url = dataDownloadObj.$grade_config_btn.data('endpoint');
$(".msg-confirm").css({"display":"block"}) return $.ajax({
type: 'POST',
# handler for when the section title is clicked. dataType: 'json',
onClickTitle: -> url: url,
# Clear display of anything that was here before error: function() {
@clear_display() dataDownloadObj.clear_display();
@instructor_tasks.task_poller.start() return dataDownloadObj.$download_request_response_error.text(
@report_downloads.downloads_poller.start() gettext('Error retrieving grading configuration.')
);
# handler for when the section is closed },
onExit: -> success: function(data) {
@instructor_tasks.task_poller.stop() dataDownloadObj.clear_display();
@report_downloads.downloads_poller.stop() return edx.HtmlUtils.setHtml(
dataDownloadObj.$download_display_text, edx.HtmlUtils.HTML(data.grading_config_summary));
clear_display: -> }
# Clear any generated tables, warning messages, etc. });
@$download_display_text.empty() });
@$download_display_table.empty() this.$async_report_btn.click(function(e) {
@$download_request_response_error.empty() var url = $(e.target).data('endpoint');
@$reports_request_response.empty() dataDownloadObj.clear_display();
@$reports_request_response_error.empty() return $.ajax({
# Clear any CSS styling from the request-response areas type: 'POST',
$(".msg-confirm").css({"display":"none"}) dataType: 'json',
$(".msg-error").css({"display":"none"}) url: url,
error: statusAjaxError(function() {
# export for use if (e.target.name === 'calculate-grades-csv') {
# create parent namespaces if they do not already exist. dataDownloadObj.$grades_request_response_error.text(
_.defaults window, InstructorDashboard: {} gettext('Error generating grades. Please try again.')
_.defaults window.InstructorDashboard, sections: {} );
_.defaults window.InstructorDashboard.sections, } else if (e.target.name === 'problem-grade-report') {
dataDownloadObj.$grades_request_response_error.text(
gettext('Error generating problem grade report. Please try again.')
);
} else if (e.target.name === 'export-ora2-data') {
dataDownloadObj.$grades_request_response_error.text(
gettext('Error generating ORA data report. Please try again.')
);
}
return $('.msg-error').css({
display: 'block'
});
}),
success: function(data) {
dataDownloadObj.$reports_request_response.text(data.status);
return $('.msg-confirm').css({
display: 'block'
});
}
});
});
}
InstructorDashboardDataDownload.prototype.onClickTitle = function() {
this.clear_display();
this.instructor_tasks.task_poller.start();
return this.report_downloads.downloads_poller.start();
};
InstructorDashboardDataDownload.prototype.onExit = function() {
this.instructor_tasks.task_poller.stop();
return this.report_downloads.downloads_poller.stop();
};
InstructorDashboardDataDownload.prototype.clear_display = function() {
this.$download_display_text.empty();
this.$download_display_table.empty();
this.$download_request_response_error.empty();
this.$reports_request_response.empty();
this.$reports_request_response_error.empty();
$('.msg-confirm').css({
display: 'none'
});
return $('.msg-error').css({
display: 'none'
});
};
return InstructorDashboardDataDownload;
}());
_.defaults(window, {
InstructorDashboard: {}
});
_.defaults(window.InstructorDashboard, {
sections: {}
});
_.defaults(window.InstructorDashboard.sections, {
DataDownload: DataDownload DataDownload: DataDownload
});
}).call(this);
### /* globals _ */
E-Commerce Section
### (function() {
'use strict';
# Load utilities var ECommerce, PendingInstructorTasks, ReportDownloads;
PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks
ReportDownloads = -> window.InstructorDashboard.util.ReportDownloads PendingInstructorTasks = function() {
return window.InstructorDashboard.util.PendingInstructorTasks;
class ECommerce };
# E-Commerce Section
constructor: (@$section) -> ReportDownloads = function() {
# attach self to html so that instructor_dashboard.coffee can find return window.InstructorDashboard.util.ReportDownloads;
# this object to call event handlers like 'onClickTitle' };
@$section.data 'wrapper', @
# gather elements ECommerce = (function() {
@$list_sale_csv_btn = @$section.find("input[name='list-sale-csv']") function eCommerce($section) {
@$list_order_sale_csv_btn = @$section.find("input[name='list-order-sale-csv']") var eCom = this;
@$download_company_name = @$section.find("input[name='download_company_name']") this.$section = $section;
@$active_company_name = @$section.find("input[name='active_company_name']") this.$section.data('wrapper', this);
@$spent_company_name = @$section.find('input[name="spent_company_name"]') this.$list_sale_csv_btn = this.$section.find("input[name='list-sale-csv']");
@$download_coupon_codes = @$section.find('input[name="download-coupon-codes-csv"]') this.$list_order_sale_csv_btn = this.$section.find("input[name='list-order-sale-csv']");
this.$download_company_name = this.$section.find("input[name='download_company_name']");
@$download_registration_codes_form = @$section.find("form#download_registration_codes") this.$active_company_name = this.$section.find("input[name='active_company_name']");
@$active_registration_codes_form = @$section.find("form#active_registration_codes") this.$spent_company_name = this.$section.find('input[name="spent_company_name"]');
@$spent_registration_codes_form = @$section.find("form#spent_registration_codes") this.$download_coupon_codes = this.$section.find('input[name="download-coupon-codes-csv"]');
this.$download_registration_codes_form = this.$section.find('form#download_registration_codes');
@$reports = @$section.find '.reports-download-container' this.$active_registration_codes_form = this.$section.find('form#active_registration_codes');
@$reports_request_response = @$reports.find '.request-response' this.$spent_registration_codes_form = this.$section.find('form#spent_registration_codes');
@$reports_request_response_error = @$reports.find '.request-response-error' this.$reports = this.$section.find('.reports-download-container');
this.$reports_request_response = this.$reports.find('.request-response');
@report_downloads = new (ReportDownloads()) @$section this.$reports_request_response_error = this.$reports.find('.request-response-error');
@instructor_tasks = new (PendingInstructorTasks()) @$section this.report_downloads = new (ReportDownloads())(this.$section);
this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
@$error_msg = @$section.find('#error-msg') this.$error_msg = this.$section.find('#error-msg');
this.$list_sale_csv_btn.click(function() {
# attach click handlers location.href = eCom.$list_sale_csv_btn.data('endpoint') + '/csv';
# this handler binds to both the download return location.href;
# and the csv button });
@$list_sale_csv_btn.click (e) => this.$list_order_sale_csv_btn.click(function() {
url = @$list_sale_csv_btn.data 'endpoint' location.href = eCom.$list_order_sale_csv_btn.data('endpoint');
url += '/csv' return location.href;
location.href = url });
this.$download_coupon_codes.click(function() {
@$list_order_sale_csv_btn.click (e) => location.href = eCom.$download_coupon_codes.data('endpoint');
url = @$list_order_sale_csv_btn.data 'endpoint' return location.href;
location.href = url });
this.$download_registration_codes_form.submit(function() {
@$download_coupon_codes.click (e) => eCom.$error_msg.attr('style', 'display: none');
url = @$download_coupon_codes.data 'endpoint' return true;
location.href = url });
this.$active_registration_codes_form.submit(function() {
@$download_registration_codes_form.submit (e) => eCom.$error_msg.attr('style', 'display: none');
@$error_msg.attr('style', 'display: none') return true;
return true });
this.$spent_registration_codes_form.submit(function() {
@$active_registration_codes_form.submit (e) => eCom.$error_msg.attr('style', 'display: none');
@$error_msg.attr('style', 'display: none') return true;
return true });
}
@$spent_registration_codes_form.submit (e) =>
@$error_msg.attr('style', 'display: none') eCommerce.prototype.onClickTitle = function() {
return true this.clear_display();
this.instructor_tasks.task_poller.start();
# handler for when the section title is clicked. return this.report_downloads.downloads_poller.start();
onClickTitle: -> };
@clear_display()
@instructor_tasks.task_poller.start() eCommerce.prototype.onExit = function() {
@report_downloads.downloads_poller.start() this.clear_display();
this.instructor_tasks.task_poller.stop();
# handler for when the section is closed return this.report_downloads.downloads_poller.stop();
onExit: -> };
@clear_display()
@instructor_tasks.task_poller.stop() eCommerce.prototype.clear_display = function() {
@report_downloads.downloads_poller.stop() this.$error_msg.attr('style', 'display: none');
this.$download_company_name.val('');
clear_display: -> this.$reports_request_response.empty();
@$error_msg.attr('style', 'display: none') this.$reports_request_response_error.empty();
@$download_company_name.val('') this.$active_company_name.val('');
@$reports_request_response.empty() return this.$spent_company_name.val('');
@$reports_request_response_error.empty() };
@$active_company_name.val('')
@$spent_company_name.val('') return eCommerce;
}());
isInt = (n) -> return n % 1 == 0;
# Clear any generated tables, warning messages, etc. _.defaults(window, {
InstructorDashboard: {}
# export for use });
# create parent namespaces if they do not already exist.
_.defaults window, InstructorDashboard: {} _.defaults(window.InstructorDashboard, {
_.defaults window.InstructorDashboard, sections: {} sections: {}
_.defaults window.InstructorDashboard.sections, });
_.defaults(window.InstructorDashboard.sections, {
ECommerce: ECommerce ECommerce: ECommerce
});
}).call(this);
### /* globals _ */
Extensions Section
(function() {
imports from other modules. 'use strict';
wrap in (-> ... apply) to defer evaluation var Extensions;
such that the value can be defined later than this assignment (file load order).
### Extensions = (function() {
function extensions($section) {
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments var $gridDisplay,
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments ext = this;
this.$section = $section;
# Extensions Section this.$section.data('wrapper', this);
class Extensions this.$change_due_date = this.$section.find("input[name='change-due-date']");
this.$reset_due_date = this.$section.find("input[name='reset-due-date']");
constructor: (@$section) -> this.$show_unit_ext = this.$section.find("input[name='show-unit-extensions']");
# attach self to html this.$show_student_ext = this.$section.find("input[name='show-student-extensions']");
# so that instructor_dashboard.coffee can find this object this.$section.find('.request-response').hide();
# to call event handlers like 'onClickTitle' this.$section.find('.request-response-error').hide();
@$section.data 'wrapper', @ $gridDisplay = this.$section.find('.data-display');
this.$grid_text = $gridDisplay.find('.data-display-text');
# Gather buttons this.$grid_table = $gridDisplay.find('.data-display-table');
@$change_due_date = @$section.find("input[name='change-due-date']") this.$change_due_date.click(function() {
@$reset_due_date = @$section.find("input[name='reset-due-date']") var sendData;
@$show_unit_extensions = @$section.find("input[name='show-unit-extensions']") ext.clear_display();
@$show_student_extensions = @$section.find("input[name='show-student-extensions']") ext.$student_input = ext.$section.find("#set-extension input[name='student']");
ext.$url_input = ext.$section.find("#set-extension select[name='url']");
# Gather notification areas ext.$due_datetime_input = ext.$section.find("#set-extension input[name='due_datetime']");
@$section.find(".request-response").hide() sendData = {
@$section.find(".request-response-error").hide() student: ext.$student_input.val(),
url: ext.$url_input.val(),
# Gather grid elements due_datetime: ext.$due_datetime_input.val()
$grid_display = @$section.find '.data-display' };
@$grid_text = $grid_display.find '.data-display-text' return $.ajax({
@$grid_table = $grid_display.find '.data-display-table' type: 'POST',
dataType: 'json',
# Click handlers url: ext.$change_due_date.data('endpoint'),
@$change_due_date.click => data: sendData,
@clear_display() success: function(data) {
@$student_input = @$section.find("#set-extension input[name='student']") return ext.display_response('set-extension', data);
@$url_input = @$section.find("#set-extension select[name='url']") },
@$due_datetime_input = @$section.find("#set-extension input[name='due_datetime']") error: function(xhr) {
send_data = return ext.fail_with_error('set-extension', 'Error changing due date', xhr);
student: @$student_input.val() }
url: @$url_input.val() });
due_datetime: @$due_datetime_input.val() });
this.$reset_due_date.click(function() {
$.ajax var sendData;
type: 'POST' ext.clear_display();
dataType: 'json' ext.$student_input = ext.$section.find("#reset-extension input[name='student']");
url: @$change_due_date.data 'endpoint' ext.$url_input = ext.$section.find("#reset-extension select[name='url']");
data: send_data sendData = {
success: (data) => @display_response "set-extension", data student: ext.$student_input.val(),
error: (xhr) => @fail_with_error "set-extension", "Error changing due date", xhr url: ext.$url_input.val()
};
@$reset_due_date.click => return $.ajax({
@clear_display() type: 'POST',
@$student_input = @$section.find("#reset-extension input[name='student']") dataType: 'json',
@$url_input = @$section.find("#reset-extension select[name='url']") url: ext.$reset_due_date.data('endpoint'),
send_data = data: sendData,
student: @$student_input.val() success: function(data) {
url: @$url_input.val() return ext.display_response('reset-extension', data);
},
$.ajax error: function(xhr) {
type: 'POST' return ext.fail_with_error('reset-extension', 'Error reseting due date', xhr);
dataType: 'json' }
url: @$reset_due_date.data 'endpoint' });
data: send_data });
success: (data) => @display_response "reset-extension", data this.$show_unit_ext.click(function() {
error: (xhr) => @fail_with_error "reset-extension", "Error reseting due date", xhr var sendData, url;
ext.clear_display();
@$show_unit_extensions.click => ext.$grid_table.text('Loading');
@clear_display() ext.$url_input = ext.$section.find("#view-granted-extensions select[name='url']");
@$grid_table.text 'Loading' url = ext.$show_unit_ext.data('endpoint');
sendData = {
@$url_input = @$section.find("#view-granted-extensions select[name='url']") url: ext.$url_input.val()
url = @$show_unit_extensions.data 'endpoint' };
send_data = return $.ajax({
url: @$url_input.val() type: 'POST',
$.ajax dataType: 'json',
type: 'POST' url: url,
dataType: 'json' data: sendData,
url: url error: function(xhr) {
data: send_data return ext.fail_with_error('view-granted-extensions', 'Error getting due dates', xhr);
error: (xhr) => @fail_with_error "view-granted-extensions", "Error getting due dates", xhr },
success: (data) => @display_grid data success: function(data) {
return ext.display_grid(data);
@$show_student_extensions.click => }
@clear_display() });
@$grid_table.text 'Loading' });
this.$show_student_ext.click(function() {
url = @$show_student_extensions.data 'endpoint' var sendData, url;
@$student_input = @$section.find("#view-granted-extensions input[name='student']") ext.clear_display();
send_data = ext.$grid_table.text('Loading');
student: @$student_input.val() url = ext.$show_student_ext.data('endpoint');
$.ajax ext.$student_input = ext.$section.find("#view-granted-extensions input[name='student']");
type: 'POST' sendData = {
dataType: 'json' student: ext.$student_input.val()
url: url };
data: send_data return $.ajax({
error: (xhr) => @fail_with_error "view-granted-extensions", "Error getting due dates", xhr type: 'POST',
success: (data) => @display_grid data dataType: 'json',
url: url,
# handler for when the section title is clicked. data: sendData,
onClickTitle: -> error: function(xhr) {
return ext.fail_with_error('view-granted-extensions', 'Error getting due dates', xhr);
fail_with_error: (id, msg, xhr) -> },
$task_error = @$section.find("#" + id + " .request-response-error") success: function(data) {
$task_response = @$section.find("#" + id + " .request-response") return ext.display_grid(data);
@clear_display() }
data = $.parseJSON xhr.responseText });
msg += ": " + data['error'] });
console.warn msg }
$task_response.empty()
$task_error.empty() extensions.prototype.onClickTitle = function() {};
$task_error.text msg
$task_error.show() extensions.prototype.fail_with_error = function(id, msg, xhr) {
var $taskError, $taskResponse, data,
display_response: (id, data) -> message = msg;
$task_error = @$section.find("#" + id + " .request-response-error") $taskError = this.$section.find('#' + id + ' .request-response-error');
$task_response = @$section.find("#" + id + " .request-response") $taskResponse = this.$section.find('#' + id + ' .request-response');
$task_error.empty().hide() this.clear_display();
$task_response.empty().text data data = $.parseJSON(xhr.responseText);
$task_response.show() message += ': ' + data.error;
$taskResponse.empty();
display_grid: (data) -> $taskError.empty();
@clear_display() $taskError.text(message);
@$grid_text.text data.title return $taskError.show();
};
# display on a SlickGrid
options = extensions.prototype.display_response = function(id, data) {
enableCellNavigation: true var $taskError, $taskResponse;
enableColumnReorder: false $taskError = this.$section.find('#' + id + ' .request-response-error');
$taskResponse = this.$section.find('#' + id + ' .request-response');
$taskError.empty().hide();
$taskResponse.empty().text(data);
return $taskResponse.show();
};
extensions.prototype.display_grid = function(data) {
var $tablePlaceholder, col, columns, gridData, options;
this.clear_display();
this.$grid_text.text(data.title);
options = {
enableCellNavigation: true,
enableColumnReorder: false,
forceFitColumns: true forceFitColumns: true
};
columns = ({id: col, field: col, name: col} for col in data.header) columns = (function() {
grid_data = data.data var i, len, ref, results;
ref = data.header;
$table_placeholder = $ '<div/>', class: 'slickgrid', style: 'min-height: 400px' results = [];
@$grid_table.append $table_placeholder for (i = 0, len = ref.length; i < len; i++) {
grid = new Slick.Grid($table_placeholder, grid_data, columns, options) col = ref[i];
results.push({
clear_display: -> id: col,
@$grid_text.empty() field: col,
@$grid_table.empty() name: col
@$section.find(".request-response-error").empty().hide() });
@$section.find(".request-response").empty().hide() }
return results;
# export for use }());
# create parent namespaces if they do not already exist. gridData = data.data;
# abort if underscore can not be found. $tablePlaceholder = $('<div/>', {
if _? class: 'slickgrid',
_.defaults window, InstructorDashboard: {} style: 'min-height: 400px'
_.defaults window.InstructorDashboard, sections: {} });
_.defaults window.InstructorDashboard.sections, this.$grid_table.append($tablePlaceholder);
return new window.Slick.Grid($tablePlaceholder, gridData, columns, options);
};
extensions.prototype.clear_display = function() {
this.$grid_text.empty();
this.$grid_table.empty();
this.$section.find('.request-response-error').empty().hide();
return this.$section.find('.request-response').empty().hide();
};
return extensions;
}());
_.defaults(window, {
InstructorDashboard: {}
});
_.defaults(window.InstructorDashboard, {
sections: {}
});
_.defaults(window.InstructorDashboard.sections, {
Extensions: Extensions Extensions: Extensions
});
}).call(this);
### /*
Instructor Dashboard Tab Manager Instructor Dashboard Tab Manager
The instructor dashboard is broken into sections. The instructor dashboard is broken into sections.
...@@ -24,179 +24,192 @@ NOTE: For an example of what a section object should look like ...@@ -24,179 +24,192 @@ NOTE: For an example of what a section object should look like
imports from other modules imports from other modules
wrap in (-> ... apply) to defer evaluation wrap in (-> ... apply) to defer evaluation
such that the value can be defined later than this assignment (file load order). such that the value can be defined later than this assignment (file load order).
### */
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments (function() {
'use strict';
# CSS classes var $activeSection,
CSS_INSTRUCTOR_CONTENT = 'instructor-dashboard-content-2' CSS_ACTIVE_SECTION, CSS_IDASH_SECTION, CSS_INSTRUCTOR_CONTENT, CSS_INSTRUCTOR_NAV, HASH_LINK_PREFIX,
CSS_ACTIVE_SECTION = 'active-section' SafeWaiter, plantTimeout, sectionsHaveLoaded, setupInstructorDashboard,
CSS_IDASH_SECTION = 'idash-section' setupInstructorDashboardSections;
CSS_INSTRUCTOR_NAV = 'instructor-nav'
plantTimeout = function() {
# prefix for deep-linking return window.InstructorDashboard.util.plantTimeout.apply(this, arguments);
HASH_LINK_PREFIX = '#view-' };
$active_section = null CSS_INSTRUCTOR_CONTENT = 'instructor-dashboard-content-2';
# helper class for queueing and fault isolation. CSS_ACTIVE_SECTION = 'active-section';
# Will execute functions marked by waiter.after only after all functions marked by
# waiter.waitFor have been called. CSS_IDASH_SECTION = 'idash-section';
# To guarantee this functionality, waitFor and after must be called
# before the functions passed to waitFor are called. CSS_INSTRUCTOR_NAV = 'instructor-nav';
class SafeWaiter
constructor: -> HASH_LINK_PREFIX = '#view-';
@after_handlers = []
@waitFor_handlers = [] $activeSection = null;
@fired = false
SafeWaiter = (function() {
after: (f) -> function safeWaiter() {
if @fired this.after_handlers = [];
f() this.waitFor_handlers = [];
else this.fired = false;
@after_handlers.push f }
waitFor: (f) -> safeWaiter.prototype.afterFor = function(f) {
return if @fired if (this.fired) {
@waitFor_handlers.push f return f();
} else {
# wrap the function so that it notifies the waiter return this.after_handlers.push(f);
# and can fire the after handlers. }
=> };
@waitFor_handlers = @waitFor_handlers.filter (g) -> g isnt f
if @waitFor_handlers.length is 0 safeWaiter.prototype.waitFor = function(f) {
@fired = true var safeWait = this;
@after_handlers.map (cb) -> plantTimeout 0, cb if (!this.fired) {
this.waitFor_handlers.push(f);
f.apply this, arguments return function() {
safeWait.waitFor_handlers = safeWait.waitFor_handlers.filter(function(g) {
return g !== f;
# waiter for dashboard sections. });
# Will only execute after all sections have at least attempted to load. if (safeWait.waitFor_handlers.length === 0) {
# This is here to facilitate section constructors isolated by setTimeout safeWait.fired = true;
# while still being able to interact with them under the guarantee safeWait.after_handlers.map(function(cb) {
# that the sections will be initialized at call time. return plantTimeout(0, cb);
sections_have_loaded = new SafeWaiter });
}
# once we're ready, check if this page is the instructor dashboard return f.apply(safeWait, arguments);
$ => };
instructor_dashboard_content = $ ".#{CSS_INSTRUCTOR_CONTENT}" } else {
if instructor_dashboard_content.length > 0 return false;
setup_instructor_dashboard instructor_dashboard_content }
setup_instructor_dashboard_sections instructor_dashboard_content };
return safeWaiter;
# enable navigation bar }());
# handles hiding and showing sections
setup_instructor_dashboard = (idash_content) => sectionsHaveLoaded = new SafeWaiter;
# clickable section titles
$links = idash_content.find(".#{CSS_INSTRUCTOR_NAV}").find('.btn-link') $(function() {
var $instructorDashboardContent;
# attach link click handlers $instructorDashboardContent = $('.' + CSS_INSTRUCTOR_CONTENT);
$links.each (i, link) -> if ($instructorDashboardContent.length > 0) {
$(link).click (e) -> setupInstructorDashboard($instructorDashboardContent);
e.preventDefault() return setupInstructorDashboardSections($instructorDashboardContent);
}
# deactivate all link & section styles return setupInstructorDashboardSections($instructorDashboardContent);
idash_content.find(".#{CSS_INSTRUCTOR_NAV} li").children().removeClass CSS_ACTIVE_SECTION });
idash_content.find(".#{CSS_INSTRUCTOR_NAV} li").children().attr('aria-pressed', 'false')
idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION setupInstructorDashboard = function(idashContent) {
var $links, clickFirstLink, link, rmatch, sectionName;
# discover section paired to link $links = idashContent.find('.' + CSS_INSTRUCTOR_NAV).find('.btn-link');
section_name = $(this).data 'section' $links.each(function(i, linkItem) {
$section = idash_content.find "##{section_name}" return $(linkItem).click(function(e) {
var $section, itemSectionName, ref;
# activate link & section styling e.preventDefault();
$(this).addClass CSS_ACTIVE_SECTION idashContent.find('.' + CSS_INSTRUCTOR_NAV + ' li').children().removeClass(CSS_ACTIVE_SECTION);
$(this).attr('aria-pressed','true') idashContent.find('.' + CSS_INSTRUCTOR_NAV + ' li').children().attr('aria-pressed', 'false');
$section.addClass CSS_ACTIVE_SECTION idashContent.find('.' + CSS_IDASH_SECTION).removeClass(CSS_ACTIVE_SECTION);
itemSectionName = $(this).data('section');
# tracking $section = idashContent.find('#' + itemSectionName);
analytics.pageview "instructor_section:#{section_name}" $(this).addClass(CSS_ACTIVE_SECTION);
$(this).attr('aria-pressed', 'true');
# deep linking $section.addClass(CSS_ACTIVE_SECTION);
# write to url window.analytics.pageview('instructor_section:' + itemSectionName);
location.hash = "#{HASH_LINK_PREFIX}#{section_name}" location.hash = '' + HASH_LINK_PREFIX + itemSectionName;
sectionsHaveLoaded.afterFor(function() {
sections_have_loaded.after -> return $section.data('wrapper').onClickTitle();
$section.data('wrapper').onClickTitle() });
if (!$section.is($activeSection)) {
# call onExit handler if exiting a section to a different section. if ($activeSection != null) {
unless $section.is $active_section ref = $activeSection.data('wrapper') != null;
$active_section?.data('wrapper')?.onExit?() if (ref) {
$active_section = $section if (typeof ref.onExit === 'function') {
ref.onExit();
# TODO enable onExit handler }
}
}
# activate an initial section by 'clicking' on it. }
# check for a deep-link, or click the first link. $activeSection = $section;
click_first_link = -> return $activeSection;
link = $links.eq(0) });
link.click() });
clickFirstLink = function() {
if (new RegExp "^#{HASH_LINK_PREFIX}").test location.hash var firstLink;
rmatch = (new RegExp "^#{HASH_LINK_PREFIX}(.*)").exec location.hash firstLink = $links.eq(0);
section_name = rmatch[1] return firstLink.click();
link = $links.filter "[data-section='#{section_name}']" };
if link.length == 1 if ((new RegExp('^' + HASH_LINK_PREFIX)).test(location.hash)) {
link.click() rmatch = (new RegExp('^' + HASH_LINK_PREFIX + '(.*)')).exec(location.hash);
else sectionName = rmatch[1];
click_first_link() link = $links.filter("[data-section='" + sectionName + "']");
else if (link.length === 1) {
click_first_link() return link.click();
} else {
return clickFirstLink();
}
# enable sections } else {
setup_instructor_dashboard_sections = (idash_content) -> return clickFirstLink();
sections_to_initialize = [ }
constructor: window.InstructorDashboard.sections.CourseInfo };
$element: idash_content.find ".#{CSS_IDASH_SECTION}#course_info"
, setupInstructorDashboardSections = function(idashContent) {
constructor: window.InstructorDashboard.sections.DataDownload var sectionsToInitialize;
$element: idash_content.find ".#{CSS_IDASH_SECTION}#data_download" sectionsToInitialize = [
, {
constructor: window.InstructorDashboard.sections.ECommerce constructor: window.InstructorDashboard.sections.CourseInfo,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#e-commerce" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#course_info')
, }, {
constructor: window.InstructorDashboard.sections.Membership constructor: window.InstructorDashboard.sections.DataDownload,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#membership" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#data_download')
, }, {
constructor: window.InstructorDashboard.sections.StudentAdmin constructor: window.InstructorDashboard.sections.ECommerce,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#student_admin" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#e-commerce')
, }, {
constructor: window.InstructorDashboard.sections.Extensions constructor: window.InstructorDashboard.sections.Membership,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#extensions" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#membership')
, }, {
constructor: window.InstructorDashboard.sections.Email constructor: window.InstructorDashboard.sections.StudentAdmin,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#send_email" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#student_admin')
, }, {
constructor: window.InstructorDashboard.sections.InstructorAnalytics constructor: window.InstructorDashboard.sections.Extensions,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#instructor_analytics" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#extensions')
, }, {
constructor: window.InstructorDashboard.sections.Metrics constructor: window.InstructorDashboard.sections.Email,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#metrics" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#send_email')
, }, {
constructor: window.InstructorDashboard.sections.CohortManagement constructor: window.InstructorDashboard.sections.InstructorAnalytics,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#cohort_management" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#instructor_analytics')
, }, {
constructor: window.InstructorDashboard.sections.Certificates constructor: window.InstructorDashboard.sections.Metrics,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#certificates" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#metrics')
] }, {
# proctoring can be feature disabled constructor: window.InstructorDashboard.sections.CohortManagement,
if edx.instructor_dashboard.proctoring != undefined $element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management')
sections_to_initialize = sections_to_initialize.concat [ }, {
constructor: edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView constructor: window.InstructorDashboard.sections.Certificates,
$element: idash_content.find ".#{CSS_IDASH_SECTION}#special_exams" $element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates')
, }
constructor: edx.instructor_dashboard.proctoring.ProctoredExamAttemptView ];
$element: idash_content.find ".#{CSS_IDASH_SECTION}#special_exams" if (edx.instructor_dashboard.proctoring !== void 0) {
] sectionsToInitialize = sectionsToInitialize.concat([
{
sections_to_initialize.map ({constructor, $element}) -> constructor: edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView,
# See fault isolation NOTE at top of file. $element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams')
# If an error is thrown in one section, it will not stop other sections from exectuing. }, {
plantTimeout 0, sections_have_loaded.waitFor -> constructor: edx.instructor_dashboard.proctoring.ProctoredExamAttemptView,
new constructor $element $element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams')
}
]);
}
return sectionsToInitialize.map(function(_arg) {
var $element, constructor;
constructor = _arg.constructor;
$element = _arg.$element;
return plantTimeout(0, sectionsHaveLoaded.waitFor(function() {
return new constructor($element);
}));
});
};
}).call(this);
### /* globals _, AutoEnrollmentViaCsv, NotificationModel, NotificationView */
/*
Membership Section Membership Section
imports from other modules. imports from other modules.
wrap in (-> ... apply) to defer evaluation wrap in (-> ... apply) to defer evaluation
such that the value can be defined later than this assignment (file load order). such that the value can be defined later than this assignment (file load order).
### */
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments (function() {
emailStudents = false 'use strict';
var AuthListWidget, BatchEnrollment, BetaTesterBulkAddition,
MemberListWidget, Membership, emailStudents, plantTimeout, statusAjaxError;
class MemberListWidget
# create a MemberListWidget `$container` is a jquery object to embody. plantTimeout = function() {
# `params` holds template parameters. `params` should look like the defaults below. return window.InstructorDashboard.util.plantTimeout.apply(this, arguments);
constructor: (@$container, params={}) -> };
params = _.defaults params,
title: "Member List" statusAjaxError = function() {
info: """ return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
Use this list to manage members. };
"""
labels: ["field1", "field2", "field3"] emailStudents = false;
add_placeholder: "Enter name"
add_btn_label: "Add Member" MemberListWidget = (function() {
add_handler: (input) -> function memberListWidget($container, params) {
var templateHtml, condition,
template_html = $("#member-list-widget-template").html() memberListParams = params || {},
@$container.html Mustache.render template_html, params memberlistwidget = this;
this.$container = $container;
# bind add button memberListParams = _.defaults(memberListParams, {
@$('input[type="button"].add').click => title: 'Member List',
params.add_handler? @$('.add-field').val() info: 'Use this list to manage members.',
labels: ['field1', 'field2', 'field3'],
# clear the input text field add_placeholder: 'Enter name',
clear_input: -> @$('.add-field').val '' add_btn_label: 'Add Member',
add_handler: function() {}
# clear all table rows });
clear_rows: -> @$('table tbody').empty() templateHtml = $('#member-list-widget-template').html();
edx.HtmlUtils.setHtml(
# takes a table row as an array items are inserted as text, unless detected this.$container, window.Mustache.render(templateHtml, edx.HtmlUtils.HTML(memberListParams))
# as a jquery objects in which case they are inserted directly. if an );
# element is a jquery object this.$('input[type="button"].add').click(function() {
add_row: (row_array) -> condition = typeof memberListParams.add_handler === 'function';
$tbody = @$('table tbody') return condition ? memberListParams.add_handler(memberlistwidget.$('.add-field').val()) : undefined;
$tr = $ '<tr>' });
for item in row_array }
$td = $ '<td>'
if item instanceof jQuery memberListWidget.prototype.clear_input = function() {
$td.append item return this.$('.add-field').val('');
else };
$td.text item
$tr.append $td memberListWidget.prototype.clear_rows = function() {
$tbody.append $tr return this.$('table tbody').empty();
};
# local selector
$: (selector) -> memberListWidget.prototype.add_row = function(rowArray) {
if @debug? var $tbody, $td, $tr, item, i, len;
s = @$container.find selector $tbody = this.$('table tbody');
if s?.length != 1 $tr = $('<tr>');
console.warn "local selector '#{selector}' found (#{s.length}) results" for (i = 0, len = rowArray.length; i < len; i++) {
s item = rowArray[i];
else $td = $('<td>');
@$container.find selector if (item instanceof jQuery) {
edx.HtmlUtils.append($td, item);
} else {
class AuthListWidget extends MemberListWidget $td.text(item);
constructor: ($container, @rolename, @$error_section) -> }
super $container, $tr.append($td);
title: $container.data 'display-name' }
info: $container.data 'info-text' return $tbody.append($tr);
labels: [gettext("Username"), gettext("Email"), gettext("Revoke access")] };
add_placeholder: gettext("Enter username or email")
add_btn_label: $container.data 'add-button-label' memberListWidget.prototype.$ = function(selector) {
add_handler: (input) => @add_handler input var s;
if (this.debug != null) {
@debug = true s = this.$container.find(selector);
@list_endpoint = $container.data 'list-endpoint' return s;
@modify_endpoint = $container.data 'modify-endpoint' } else {
unless @rolename? return this.$container.find(selector);
throw "AuthListWidget missing @rolename" }
};
@reload_list()
return memberListWidget;
# action to do when is reintroduced into user's view }());
re_view: ->
@clear_errors() AuthListWidget = (function() {
@clear_input() function authListWidget($container, rolename, $errorSection) {
@reload_list() var msg,
authlistwidget = this;
# handle clicks on the add button this.rolename = rolename;
add_handler: (input) -> this.$errorSection = $errorSection;
if input? and input isnt '' authListWidget.super.constructor.call(this, $container, {
@modify_member_access input, 'allow', (error) => title: $container.data('display-name'),
# abort on error info: $container.data('info-text'),
return @show_errors error unless error is null labels: [gettext('Username'), gettext('Email'), gettext('Revoke access')],
@clear_errors() add_placeholder: gettext('Enter username or email'),
@clear_input() add_btn_label: $container.data('add-button-label'),
@reload_list() add_handler: function(input) {
else return authlistwidget.add_handler(input);
@show_errors gettext "Please enter a username or email." }
});
# reload the list of members this.debug = true;
reload_list: -> this.list_endpoint = $container.data('list-endpoint');
# @clear_rows() this.modify_endpoint = $container.data('modify-endpoint');
@get_member_list (error, member_list) => if (this.rolename == null) {
# abort on error msg = 'AuthListWidget missing @rolename';
return @show_errors error unless error is null throw msg;
}
# only show the list of there are members this.reload_list();
@clear_rows() }
# use _.each instead of 'for' so that member authListWidget.prototype.re_view = function() {
# is bound in the button callback. this.clear_errors();
_.each member_list, (member) => this.clear_input();
# if there are members, show the list return this.reload_list();
};
# create revoke button and insert it into the row
label_trans = gettext("Revoke access") authListWidget.prototype.add_handler = function(input) {
$revoke_btn = $ _.template('<div class="revoke"><span class="icon fa fa-times-circle" aria-hidden="true"></span> <%- label %></div>')({label: label_trans}), var authlistwidgetaddhandler = this;
if ((input != null) && input !== '') {
return this.modify_member_access(input, 'allow', function(error) {
if (error !== null) {
return authlistwidgetaddhandler.show_errors(error);
}
authlistwidgetaddhandler.clear_errors();
authlistwidgetaddhandler.clear_input();
return authlistwidgetaddhandler.reload_list();
});
} else {
return this.show_errors(gettext('Please enter a username or email.'));
}
};
authListWidget.prototype.reload_list = function() {
var authlistwidgetreloadlist = this;
return this.get_member_list(function(error, memberList) {
if (error !== null) {
return authlistwidgetreloadlist.show_errors(error);
}
authlistwidgetreloadlist.clear_rows();
return _.each(memberList, function(member) {
var $revokeBtn, labelTrans;
labelTrans = gettext('Revoke access');
$revokeBtn = $(_.template('<div class="revoke"><span class="icon fa fa-times-circle" aria-hidden="true"></span> <%- label %></div>')({ // eslint-disable-line max-len
label: labelTrans
}), {
class: 'revoke' class: 'revoke'
$revoke_btn.click => });
@modify_member_access member.email, 'revoke', (error) => $revokeBtn.click(function() {
# abort on error return authlistwidgetreloadlist.modify_member_access(member.email, 'revoke', function(err) {
return @show_errors error unless error is null if (err !== null) {
@clear_errors() return authlistwidgetreloadlist.show_errors(err);
@reload_list() }
@add_row [member.username, member.email, $revoke_btn] authlistwidgetreloadlist.clear_errors();
return authlistwidgetreloadlist.reload_list();
# clear error display });
clear_errors: -> @$error_section?.text '' });
return authlistwidgetreloadlist.add_row([member.username, member.email, $revokeBtn]);
# set error display });
show_errors: (msg) -> @$error_section?.text msg });
};
# send ajax request to list members
# `cb` is called with cb(error, member_list) authListWidget.prototype.clear_errors = function() {
get_member_list: (cb) -> var ref, result;
$.ajax result = (this.$error_section) != null ? ref.text('') : undefined;
type: 'POST' return result;
dataType: 'json' };
url: @list_endpoint
data: rolename: @rolename authListWidget.prototype.show_errors = function(msg) {
success: (data) => cb? null, data[@rolename] var ref, result;
error: std_ajax_err => result = (this.$error_section) != null ? ref.text(msg) : undefined;
`// Translators: A rolename appears this sentence. A rolename is something like "staff" or "beta tester".` return result;
cb? gettext("Error fetching list for role") + " '#{@rolename}'" };
# send ajax request to modify access authListWidget.prototype.get_member_list = function(cb) {
# (add or remove them from the list) var authlistwidgetgetmemberlist = this;
# `action` can be 'allow' or 'revoke' return $.ajax({
# `cb` is called with cb(error, data) type: 'POST',
modify_member_access: (unique_student_identifier, action, cb) -> dataType: 'json',
$.ajax url: this.list_endpoint,
type: 'POST' data: {
dataType: 'json' rolename: this.rolename
url: @modify_endpoint },
data: success: function(data) {
unique_student_identifier: unique_student_identifier return typeof cb === 'function' ? cb(null, data[authlistwidgetgetmemberlist.rolename]) : undefined;
rolename: @rolename }
});
};
authListWidget.prototype.modify_member_access = function(uniqueStudentIdentifier, action, cb) {
var authlistwidgetmemberaccess = this;
return $.ajax({
type: 'POST',
dataType: 'json',
url: this.modify_endpoint,
data: {
unique_student_identifier: uniqueStudentIdentifier,
rolename: this.rolename,
action: action action: action
success: (data) => @member_response data },
error: std_ajax_err => cb? gettext "Error changing user's permissions." success: function(data) {
return authlistwidgetmemberaccess.member_response(data);
member_response: (data) -> },
@clear_errors() error: statusAjaxError(function() {
@clear_input() return typeof cb === 'function' ? cb(gettext("Error changing user's permissions.")) : undefined;
if data.userDoesNotExist })
msg = gettext("Could not find a user with username or email address '<%- identifier %>'.") });
@show_errors _.template(msg, {identifier: data.unique_student_identifier}) };
else if data.inactiveUser
msg = gettext("Error: User '<%- username %>' has not yet activated their account. Users must create and activate their accounts before they can be assigned a role.") authListWidget.prototype.member_response = function(data) {
@show_errors _.template(msg, {username: data.unique_student_identifier}) var msg;
else if data.removingSelfAsInstructor this.clear_errors();
@show_errors gettext "Error: You cannot remove yourself from the Instructor group!" this.clear_input();
else if (data.userDoesNotExist) {
@reload_list() msg = gettext("Could not find a user with username or email address '<%- identifier %>'.");
return this.show_errors(_.template(msg, {
class @AutoEnrollmentViaCsv identifier: data.unique_student_identifier
constructor: (@$container) -> }));
# Wrapper for the AutoEnrollmentViaCsv subsection. } else if (data.inactiveUser) {
# This object handles buttons, success and failure reporting, msg = gettext("Error: User '<%- username %>' has not yet activated their account. Users must create and activate their accounts before they can be assigned a role."); // eslint-disable-line max-len
# and server communication. return this.show_errors(_.template(msg, {
@$student_enrollment_form = @$container.find("#student-auto-enroll-form") username: data.unique_student_identifier
@$enrollment_signup_button = @$container.find("#submitBtn-auto_enroll_csv") }));
@$students_list_file = @$container.find("input[name='students_list']") } else if (data.removingSelfAsInstructor) {
@$csrf_token = @$container.find("input[name='csrfmiddlewaretoken']") return this.show_errors(
@$results = @$container.find("div.results") gettext('Error: You cannot remove yourself from the Instructor group!')
@$browse_button = @$container.find("#browseBtn-auto-enroll") );
@$browse_file = @$container.find("#browseFile") } else {
return this.reload_list();
@processing = false }
};
@$browse_button.on "change", (event) =>
if event.currentTarget.files.length == 1 return authListWidget;
@$browse_file.val(event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf("\\") + 1)) }(MemberListWidget));
# attach click handler for @$enrollment_signup_button this.AutoEnrollmentViaCsv = (function() {
@$enrollment_signup_button.click => function AutoEnrollmentViaCsv($container) {
@$student_enrollment_form.submit (event) => var autoenrollviacsv = this;
if @processing this.$container = $container;
return false this.$student_enrollment_form = this.$container.find('#student-auto-enroll-form');
this.$enrollment_signup_button = this.$container.find('#submitBtn-auto_enroll_csv');
@processing = true this.$students_list_file = this.$container.find("input[name='students_list']");
this.$csrf_token = this.$container.find("input[name='csrfmiddlewaretoken']");
event.preventDefault() this.$results = this.$container.find('div.results');
data = new FormData(event.currentTarget) this.$browse_button = this.$container.find('#browseBtn-auto-enroll');
$.ajax this.$browse_file = this.$container.find('#browseFile');
dataType: 'json' this.processing = false;
type: 'POST' this.$browse_button.on('change', function(event) {
url: event.currentTarget.action if (event.currentTarget.files.length === 1) {
data: data return autoenrollviacsv.$browse_file.val(
processData: false event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf('\\') + 1)
contentType: false );
success: (data) => }
@processing = false return false;
@display_response data });
this.$enrollment_signup_button.click(function() {
return false return autoenrollviacsv.$student_enrollment_form.submit(function(event) {
var data;
display_response: (data_from_server) -> if (autoenrollviacsv.processing) {
@$results.empty() return false;
errors = [] }
warnings = [] autoenrollviacsv.processing = true;
result_from_server_is_success = true event.preventDefault();
data = new FormData(event.currentTarget);
if data_from_server.general_errors.length $.ajax({
result_from_server_is_success = false dataType: 'json',
for general_error in data_from_server.general_errors type: 'POST',
general_error['is_general_error'] = true url: event.currentTarget.action,
errors.push general_error data: data,
processData: false,
if data_from_server.row_errors.length contentType: false,
result_from_server_is_success = false success: function(responsedata) {
for error in data_from_server.row_errors autoenrollviacsv.processing = false;
error['is_general_error'] = false return autoenrollviacsv.display_response(responsedata);
errors.push error }
});
if data_from_server.warnings.length return false;
result_from_server_is_success = false });
for warning in data_from_server.warnings });
warning['is_general_error'] = false }
warnings.push warning
AutoEnrollmentViaCsv.prototype.display_response = function(dataFromServer) {
render_response = (title, message, type, student_results) => var error, errors, generalError, renderResponse,
details = [] resultFromServerIsSuccess, warning, warnings,
for student_result in student_results i, j, k, len, len1, len2, ref, ref1, ref2,
if student_result.is_general_error displayResponse = this;
details.push student_result.response this.$results.empty();
else errors = [];
response_message = student_result.username + ' ('+ student_result.email + '): ' + ' (' + student_result.response + ')' warnings = [];
details.push response_message resultFromServerIsSuccess = true;
if (dataFromServer.general_errors.length) {
@$results.append @render_notification_view type, title, message, details resultFromServerIsSuccess = false;
ref = dataFromServer.general_errors;
if errors.length for (i = 0, len = ref.length; i < len; i++) {
render_response gettext('Errors'), gettext("The following errors were generated:"), 'error', errors generalError = ref[i];
if warnings.length generalError.is_general_error = true;
render_response gettext('Warnings'), gettext("The following warnings were generated:"), 'warning', warnings errors.push(generalError);
if result_from_server_is_success }
render_response gettext('Success'), gettext("All accounts were created successfully."), 'confirmation', [] }
if (dataFromServer.row_errors.length) {
render_notification_view: (type, title, message, details) -> resultFromServerIsSuccess = false;
notification_model = new NotificationModel() ref1 = dataFromServer.row_errors;
notification_model.set({ for (j = 0, len1 = ref1.length; j < len1; j++) {
'type': type, error = ref1[j];
'title': title, error.is_general_error = false;
'message': message, errors.push(error);
'details': details, }
}); }
view = new NotificationView(model:notification_model); if (dataFromServer.warnings.length) {
view.render() resultFromServerIsSuccess = false;
return view.$el.html() ref2 = dataFromServer.warnings;
for (k = 0, len2 = ref2.length; k < len2; k++) {
class BetaTesterBulkAddition warning = ref2[k];
constructor: (@$container) -> warning.is_general_error = false;
# gather elements warnings.push(warning);
@$identifier_input = @$container.find("textarea[name='student-ids-for-beta']") }
@$btn_beta_testers = @$container.find("input[name='beta-testers']") }
@$checkbox_autoenroll = @$container.find("input[name='auto-enroll']") renderResponse = function(title, message, type, studentResults) {
@$checkbox_emailstudents = @$container.find("input[name='email-students-beta']") var details, responseMessage, studentResult, l, len3;
@$task_response = @$container.find(".request-response") details = [];
@$request_response_error = @$container.find(".request-response-error") for (l = 0, len3 = studentResults.length; l < len3; l++) {
studentResult = studentResults[l];
# click handlers if (studentResult.is_general_error) {
@$btn_beta_testers.click (event) => details.push(studentResult.response);
emailStudents = @$checkbox_emailstudents.is(':checked') } else {
autoEnroll = @$checkbox_autoenroll.is(':checked') responseMessage = studentResult.username + ' (' + studentResult.email + '): ' + ' (' + studentResult.response + ')'; // eslint-disable-line max-len, no-useless-concat
send_data = details.push(responseMessage);
action: $(event.target).data('action') # 'add' or 'remove' }
identifiers: @$identifier_input.val() }
email_students: emailStudents return edx.HtmlUtils.append(displayResponse.$results,
edx.HtmlUtils.HTML(displayResponse.render_notification_view(type, title, message, details))
);
};
if (errors.length) {
renderResponse(gettext('Errors'),
gettext('The following errors were generated:'), 'error', errors
);
}
if (warnings.length) {
renderResponse(gettext('Warnings'),
gettext('The following warnings were generated:'), 'warning', warnings
);
}
if (resultFromServerIsSuccess) {
return renderResponse(gettext('Success'),
gettext('All accounts were created successfully.'), 'confirmation', []
);
}
return renderResponse();
};
AutoEnrollmentViaCsv.prototype.render_notification_view = function(type, title, message, details) {
var notificationModel, view;
notificationModel = new NotificationModel();
notificationModel.set({
type: type,
title: title,
message: message,
details: details
});
view = new NotificationView({
model: notificationModel
});
view.render();
return view.$el.html();
};
return AutoEnrollmentViaCsv;
}());
BetaTesterBulkAddition = (function() {
function betaTesterBulkAddition($container) {
var betatest = this;
this.$container = $container;
this.$identifier_input = this.$container.find("textarea[name='student-ids-for-beta']");
this.$btn_beta_testers = this.$container.find("input[name='beta-testers']");
this.$checkbox_autoenroll = this.$container.find("input[name='auto-enroll']");
this.$checkbox_emailstudents = this.$container.find("input[name='email-students-beta']");
this.$task_response = this.$container.find('.request-response');
this.$request_response_error = this.$container.find('.request-response-error');
this.$btn_beta_testers.click(function(event) {
var autoEnroll, sendData;
emailStudents = betatest.$checkbox_emailstudents.is(':checked');
autoEnroll = betatest.$checkbox_autoenroll.is(':checked');
sendData = {
action: $(event.target).data('action'),
identifiers: betatest.$identifier_input.val(),
email_students: emailStudents,
auto_enroll: autoEnroll auto_enroll: autoEnroll
};
$.ajax return $.ajax({
dataType: 'json' dataType: 'json',
type: 'POST' type: 'POST',
url: @$btn_beta_testers.data 'endpoint' url: betatest.$btn_beta_testers.data('endpoint'),
data: send_data data: sendData,
success: (data) => @display_response data success: function(data) {
error: std_ajax_err => @fail_with_error gettext "Error adding/removing users as beta testers." return betatest.display_response(data);
},
# clear the input text field error: statusAjaxError(function() {
clear_input: -> return betatest.fail_with_error(gettext('Error adding/removing users as beta testers.'));
@$identifier_input.val '' })
# default for the checkboxes should be checked });
@$checkbox_emailstudents.attr('checked', true) });
@$checkbox_autoenroll.attr('checked', true) }
fail_with_error: (msg) -> betaTesterBulkAddition.prototype.clear_input = function() {
console.warn msg this.$identifier_input.val('');
@clear_input() this.$checkbox_emailstudents.attr('checked', true);
@$task_response.empty() return this.$checkbox_autoenroll.attr('checked', true);
@$request_response_error.empty() };
@$request_response_error.text msg
betaTesterBulkAddition.prototype.fail_with_error = function(msg) {
display_response: (data_from_server) -> this.clear_input();
@clear_input() this.$task_response.empty();
@$task_response.empty() this.$request_response_error.empty();
@$request_response_error.empty() return this.$request_response_error.text(msg);
errors = [] };
successes = []
no_users = [] betaTesterBulkAddition.prototype.display_response = function(dataFromServer) {
for student_results in data_from_server.results var errors, noUsers, renderList, sr, studentResults, successes, i, len, ref,
if student_results.userDoesNotExist displayResponse = this;
no_users.push student_results this.clear_input();
else if student_results.error this.$task_response.empty();
errors.push student_results this.$request_response_error.empty();
else errors = [];
successes.push student_results successes = [];
noUsers = [];
render_list = (label, ids) => ref = dataFromServer.results;
task_res_section = $ '<div/>', class: 'request-res-section' for (i = 0, len = ref.length; i < len; i++) {
task_res_section.append $ '<h3/>', text: label studentResults = ref[i];
ids_list = $ '<ul/>' if (studentResults.userDoesNotExist) {
task_res_section.append ids_list noUsers.push(studentResults);
} else if (studentResults.error) {
for identifier in ids errors.push(studentResults);
ids_list.append $ '<li/>', text: identifier } else {
successes.push(studentResults);
@$task_response.append task_res_section }
}
if successes.length and data_from_server.action is 'add' renderList = function(label, ids) {
`// Translators: A list of users appears after this sentence` var identifier, $idsList, $taskResSection, j, len1;
render_list gettext("These users were successfully added as beta testers:"), (sr.identifier for sr in successes) $taskResSection = $('<div/>', {
class: 'request-res-section'
if successes.length and data_from_server.action is 'remove' });
`// Translators: A list of users appears after this sentence` $taskResSection.append($('<h3/>', {
render_list gettext("These users were successfully removed as beta testers:"), (sr.identifier for sr in successes) text: label
}));
if errors.length and data_from_server.action is 'add' $idsList = $('<ul/>');
`// Translators: A list of users appears after this sentence` $taskResSection.append($idsList);
render_list gettext("These users were not added as beta testers:"), (sr.identifier for sr in errors) for (j = 0, len1 = ids.length; j < len1; j++) {
identifier = ids[j];
if errors.length and data_from_server.action is 'remove' $idsList.append($('<li/>', {
`// Translators: A list of users appears after this sentence` text: identifier
render_list gettext("These users were not removed as beta testers:"), (sr.identifier for sr in errors) }));
}
if no_users.length return displayResponse.$task_response.append($taskResSection);
no_users.push $ gettext("Users must create and activate their account before they can be promoted to beta tester.") };
`// Translators: A list of identifiers (which are email addresses and/or usernames) appears after this sentence` if (successes.length && dataFromServer.action === 'add') {
render_list gettext("Could not find users associated with the following identifiers:"), (sr.identifier for sr in no_users) // Translators: A list of users appears after this sentence;
renderList(gettext('These users were successfully added as beta testers:'), (function() {
# Wrapper for the batch enrollment subsection. var j, len1, results;
# This object handles buttons, success and failure reporting, results = [];
# and server communication. for (j = 0, len1 = successes.length; j < len1; j++) {
class BatchEnrollment sr = successes[j];
constructor: (@$container) -> results.push(sr.identifier);
# gather elements }
@$identifier_input = @$container.find("textarea[name='student-ids']") return results;
@$enrollment_button = @$container.find(".enrollment-button") }()));
@$is_course_white_label = @$container.find("#is_course_white_label").val() }
@$reason_field = @$container.find("textarea[name='reason-field']") if (successes.length && dataFromServer.action === 'remove') {
@$checkbox_autoenroll = @$container.find("input[name='auto-enroll']") // Translators: A list of users appears after this sentence;
@$checkbox_emailstudents = @$container.find("input[name='email-students']") renderList(gettext('These users were successfully removed as beta testers:'), (function() {
@$task_response = @$container.find(".request-response") var j, len1, results;
@$request_response_error = @$container.find(".request-response-error") results = [];
for (j = 0, len1 = successes.length; j < len1; j++) {
# attach click handler for enrollment buttons sr = successes[j];
@$enrollment_button.click (event) => results.push(sr.identifier);
if @$is_course_white_label == 'True' }
if not @$reason_field.val() return results;
@fail_with_error gettext "Reason field should not be left blank." }()));
return false }
if (errors.length && dataFromServer.action === 'add') {
emailStudents = @$checkbox_emailstudents.is(':checked') // Translators: A list of users appears after this sentence;
send_data = renderList(gettext('These users were not added as beta testers:'), (function() {
action: $(event.target).data('action') # 'enroll' or 'unenroll' var j, len1, results;
identifiers: @$identifier_input.val() results = [];
auto_enroll: @$checkbox_autoenroll.is(':checked') for (j = 0, len1 = errors.length; j < len1; j++) {
email_students: emailStudents sr = errors[j];
reason: @$reason_field.val() results.push(sr.identifier);
}
$.ajax return results;
dataType: 'json' }()));
type: 'POST' }
url: $(event.target).data 'endpoint' if (errors.length && dataFromServer.action === 'remove') {
data: send_data // Translators: A list of users appears after this sentence;
success: (data) => @display_response data renderList(gettext('These users were not removed as beta testers:'), (function() {
error: std_ajax_err => @fail_with_error gettext "Error enrolling/unenrolling users." var j, len1, results;
results = [];
for (j = 0, len1 = errors.length; j < len1; j++) {
# clear the input text field sr = errors[j];
clear_input: -> results.push(sr.identifier);
@$identifier_input.val '' }
@$reason_field.val '' return results;
# default for the checkboxes should be checked }()));
@$checkbox_emailstudents.attr('checked', true) }
@$checkbox_autoenroll.attr('checked', true) if (noUsers.length) {
noUsers.push($(
fail_with_error: (msg) -> gettext('Users must create and activate their account before they can be promoted to beta tester.'))
console.warn msg );
@clear_input() return renderList(gettext('Could not find users associated with the following identifiers:'), (function() { // eslint-disable-line max-len
@$task_response.empty() var j, len1, results;
@$request_response_error.empty() results = [];
@$request_response_error.text msg for (j = 0, len1 = noUsers.length; j < len1; j++) {
sr = noUsers[j];
display_response: (data_from_server) -> results.push(sr.identifier);
@clear_input() }
@$task_response.empty() return results;
@$request_response_error.empty() }()));
}
# these results arrays contain student_results return renderList();
# only populated arrays will be rendered };
#
# invalid identifiers return betaTesterBulkAddition;
invalid_identifier = [] }());
# students for which there was an error during the action
errors = [] BatchEnrollment = (function() {
# students who are now enrolled in the course function batchEnrollment($container) {
enrolled = [] var batchEnroll = this;
# students who are now allowed to enroll in the course this.$container = $container;
allowed = [] this.$identifier_input = this.$container.find("textarea[name='student-ids']");
# students who will be autoenrolled on registration this.$enrollment_button = this.$container.find('.enrollment-button');
autoenrolled = [] this.$is_course_white_label = this.$container.find('#is_course_white_label').val();
# students who are now not enrolled in the course this.$reason_field = this.$container.find("textarea[name='reason-field']");
notenrolled = [] this.$checkbox_autoenroll = this.$container.find("input[name='auto-enroll']");
# students who were not enrolled or allowed prior to unenroll action this.$checkbox_emailstudents = this.$container.find("input[name='email-students']");
notunenrolled = [] this.$task_response = this.$container.find('.request-response');
this.$request_response_error = this.$container.find('.request-response-error');
# categorize student results into the above arrays. this.$enrollment_button.click(function(event) {
for student_results in data_from_server.results var sendData;
# for a successful action. if (batchEnroll.$is_course_white_label === 'True') {
# student_results is of the form { if (!batchEnroll.$reason_field.val()) {
# "identifier": "jd405@edx.org", batchEnroll.fail_with_error(gettext('Reason field should not be left blank.'));
# "before": { return false;
# "enrollment": true, }
# "auto_enroll": false, }
# "user": true, emailStudents = batchEnroll.$checkbox_emailstudents.is(':checked');
# "allowed": false sendData = {
# } action: $(event.target).data('action'),
# "after": { identifiers: batchEnroll.$identifier_input.val(),
# "enrollment": true, auto_enroll: batchEnroll.$checkbox_autoenroll.is(':checked'),
# "auto_enroll": false, email_students: emailStudents,
# "user": true, reason: batchEnroll.$reason_field.val()
# "allowed": false };
# }, return $.ajax({
# } dataType: 'json',
# type: 'POST',
# for an action error. url: $(event.target).data('endpoint'),
# student_results is of the form { data: sendData,
# 'identifier': identifier, success: function(data) {
# # then one of: return batchEnroll.display_response(data);
# 'error': True, },
# 'invalidIdentifier': True # if identifier can't find a valid User object and doesn't pass validate_email error: statusAjaxError(function() {
# } return batchEnroll.fail_with_error(gettext('Error enrolling/unenrolling users.'));
})
if student_results.invalidIdentifier });
invalid_identifier.push student_results });
}
else if student_results.error
errors.push student_results batchEnrollment.prototype.clear_input = function() {
this.$identifier_input.val('');
else if student_results.after.enrollment this.$reason_field.val('');
enrolled.push student_results this.$checkbox_emailstudents.attr('checked', true);
return this.$checkbox_autoenroll.attr('checked', true);
else if student_results.after.allowed };
if student_results.after.auto_enroll
autoenrolled.push student_results batchEnrollment.prototype.fail_with_error = function(msg) {
else this.clear_input();
allowed.push student_results this.$task_response.empty();
this.$request_response_error.empty();
# The instructor is trying to unenroll someone who is not enrolled or allowed to enroll; non-sensical action. return this.$request_response_error.text(msg);
else if data_from_server.action is 'unenroll' and not (student_results.before.enrollment) and not (student_results.before.allowed) };
notunenrolled.push student_results
batchEnrollment.prototype.display_response = function(dataFromServer) {
else if not student_results.after.enrollment var allowed, autoenrolled, enrolled, errors, errorsLabel,
notenrolled.push student_results invalidIdentifier, notenrolled, notunenrolled, renderList, sr, studentResults,
i, j, len, len1, ref, renderIdsLists,
else displayResponse = this;
console.warn 'student results not reported to user' this.clear_input();
console.warn student_results this.$task_response.empty();
this.$request_response_error.empty();
# render populated result arrays invalidIdentifier = [];
render_list = (label, ids) => errors = [];
task_res_section = $ '<div/>', class: 'request-res-section' enrolled = [];
task_res_section.append $ '<h3/>', text: label allowed = [];
ids_list = $ '<ul/>' autoenrolled = [];
task_res_section.append ids_list notenrolled = [];
notunenrolled = [];
for identifier in ids ref = dataFromServer.results;
ids_list.append $ '<li/>', text: identifier for (i = 0, len = ref.length; i < len; i++) {
studentResults = ref[i];
@$task_response.append task_res_section if (studentResults.invalidIdentifier) {
invalidIdentifier.push(studentResults);
if invalid_identifier.length } else if (studentResults.error) {
render_list gettext("The following email addresses and/or usernames are invalid:"), (sr.identifier for sr in invalid_identifier) errors.push(studentResults);
} else if (studentResults.after.enrollment) {
if errors.length enrolled.push(studentResults);
errors_label = do -> } else if (studentResults.after.allowed) {
if data_from_server.action is 'enroll' if (studentResults.after.auto_enroll) {
"There was an error enrolling:" autoenrolled.push(studentResults);
else if data_from_server.action is 'unenroll' } else {
"There was an error unenrolling:" allowed.push(studentResults);
else }
console.warn "unknown action from server '#{data_from_server.action}'" } else if (dataFromServer.action === 'unenroll' &&
"There was an error processing:" !studentResults.before.enrollment &&
!studentResults.before.allowed) {
for student_results in errors notunenrolled.push(studentResults);
render_list errors_label, (sr.identifier for sr in errors) } else if (!studentResults.after.enrollment) {
notenrolled.push(studentResults);
if enrolled.length and emailStudents } else {
render_list gettext("Successfully enrolled and sent email to the following users:"), (sr.identifier for sr in enrolled) console.warn('student results not reported to user'); // eslint-disable-line no-console
}
if enrolled.length and not emailStudents }
`// Translators: A list of users appears after this sentence` renderList = function(label, ids) {
render_list gettext("Successfully enrolled the following users:"), (sr.identifier for sr in enrolled) var identifier, $idsList, $taskResSection, h, len3;
$taskResSection = $('<div/>', {
# Student hasn't registered so we allow them to enroll class: 'request-res-section'
if allowed.length and emailStudents });
`// Translators: A list of users appears after this sentence` $taskResSection.append($('<h3/>', {
render_list gettext("Successfully sent enrollment emails to the following users. They will be allowed to enroll once they register:"), text: label
(sr.identifier for sr in allowed) }));
$idsList = $('<ul/>');
# Student hasn't registered so we allow them to enroll $taskResSection.append($idsList);
if allowed.length and not emailStudents for (h = 0, len3 = ids.length; h < len3; h++) {
`// Translators: A list of users appears after this sentence` identifier = ids[h];
render_list gettext("These users will be allowed to enroll once they register:"), $idsList.append($('<li/>', {
(sr.identifier for sr in allowed) text: identifier
}));
# Student hasn't registered so we allow them to enroll with autoenroll }
if autoenrolled.length and emailStudents return displayResponse.$task_response.append($taskResSection);
`// Translators: A list of users appears after this sentence` };
render_list gettext("Successfully sent enrollment emails to the following users. They will be enrolled once they register:"), if (invalidIdentifier.length) {
(sr.identifier for sr in autoenrolled) renderList(gettext('The following email addresses and/or usernames are invalid:'), (function() {
var m, len4, results;
# Student hasn't registered so we allow them to enroll with autoenroll results = [];
if autoenrolled.length and not emailStudents for (m = 0, len4 = invalidIdentifier.length; m < len4; m++) {
`// Translators: A list of users appears after this sentence` sr = invalidIdentifier[m];
render_list gettext("These users will be enrolled once they register:"), results.push(sr.identifier);
(sr.identifier for sr in autoenrolled) }
return results;
if notenrolled.length and emailStudents }()));
`// Translators: A list of users appears after this sentence` }
render_list gettext("Emails successfully sent. The following users are no longer enrolled in the course:"), if (errors.length) {
(sr.identifier for sr in notenrolled) errorsLabel = (function() {
if (dataFromServer.action === 'enroll') {
if notenrolled.length and not emailStudents return 'There was an error enrolling:';
`// Translators: A list of users appears after this sentence` } else if (dataFromServer.action === 'unenroll') {
render_list gettext("The following users are no longer enrolled in the course:"), return 'There was an error unenrolling:';
(sr.identifier for sr in notenrolled) } else {
console.warn("unknown action from server '" + dataFromServer.action + "'"); // eslint-disable-line no-console, max-len
if notunenrolled.length return 'There was an error processing:';
`// Translators: A list of users appears after this sentence. This situation arises when a staff member tries to unenroll a user who is not currently enrolled in this course.` }
render_list gettext("These users were not affiliated with the course so could not be unenrolled:"), }());
(sr.identifier for sr in notunenrolled) renderIdsLists = function(errs) {
var srItem,
# Wrapper for auth list subsection. k = 0,
# manages a list of users who have special access. results = [];
# these could be instructors, staff, beta users, or forum roles. for (k = 0, len = errs.length; k < len; k++) {
# uses slickgrid to display list. srItem = errs[k];
class AuthList results.push(srItem.identifier);
# rolename is one of ['instructor', 'staff'] for instructor_staff endpoints }
# rolename is the name of Role for forums for the forum endpoints return results;
constructor: (@$container, @rolename) -> };
# gather elements for (j = 0, len1 = errors.length; j < len1; j++) {
@$display_table = @$container.find('.auth-list-table') studentResults = errors[j];
@$request_response_error = @$container.find('.request-response-error') renderList(errorsLabel, renderIdsLists(errors));
@$add_section = @$container.find('.auth-list-add') }
@$allow_field = @$add_section.find("input[name='email']") }
@$allow_button = @$add_section.find("input[name='allow']") if (enrolled.length && emailStudents) {
renderList(gettext('Successfully enrolled and sent email to the following users:'), (function() {
# attach click handler var k, len2, results;
@$allow_button.click => results = [];
@access_change @$allow_field.val(), 'allow', => @reload_auth_list() for (k = 0, len2 = enrolled.length; k < len2; k++) {
@$allow_field.val '' sr = enrolled[k];
results.push(sr.identifier);
@reload_auth_list() }
return results;
# fetch and display list of users who match criteria }()));
reload_auth_list: -> }
# helper function to display server data in the list if (enrolled.length && !emailStudents) {
load_auth_list = (data) => // Translators: A list of users appears after this sentence;
# clear existing data renderList(gettext('Successfully enrolled the following users:'), (function() {
@$request_response_error.empty() var k, len2, results;
@$display_table.empty() results = [];
for (k = 0, len2 = enrolled.length; k < len2; k++) {
# setup slickgrid sr = enrolled[k];
options = results.push(sr.identifier);
enableCellNavigation: true }
enableColumnReorder: false return results;
# autoHeight: true }()));
}
if (allowed.length && emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('Successfully sent enrollment emails to the following users. They will be allowed to enroll once they register:'), (function() { // eslint-disable-line max-len
var k, len2, results;
results = [];
for (k = 0, len2 = allowed.length; k < len2; k++) {
sr = allowed[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (allowed.length && !emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('These users will be allowed to enroll once they register:'), (function() {
var k, len2, results;
results = [];
for (k = 0, len2 = allowed.length; k < len2; k++) {
sr = allowed[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (autoenrolled.length && emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('Successfully sent enrollment emails to the following users. They will be enrolled once they register:'), (function() { // eslint-disable-line max-len
var k, len2, results;
results = [];
for (k = 0, len2 = autoenrolled.length; k < len2; k++) {
sr = autoenrolled[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (autoenrolled.length && !emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('These users will be enrolled once they register:'), (function() {
var k, len2, results;
results = [];
for (k = 0, len2 = autoenrolled.length; k < len2; k++) {
sr = autoenrolled[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (notenrolled.length && emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('Emails successfully sent. The following users are no longer enrolled in the course:'), (function() { // eslint-disable-line max-len
var k, len2, results;
results = [];
for (k = 0, len2 = notenrolled.length; k < len2; k++) {
sr = notenrolled[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (notenrolled.length && !emailStudents) {
// Translators: A list of users appears after this sentence;
renderList(gettext('The following users are no longer enrolled in the course:'), (function() {
var k, len2, results;
results = [];
for (k = 0, len2 = notenrolled.length; k < len2; k++) {
sr = notenrolled[k];
results.push(sr.identifier);
}
return results;
}()));
}
if (notunenrolled.length) {
return renderList(gettext('These users were not affiliated with the course so could not be unenrolled:'), (function() { // eslint-disable-line max-len
var k, len2, results;
results = [];
for (k = 0, len2 = notunenrolled.length; k < len2; k++) {
sr = notunenrolled[k];
results.push(sr.identifier);
}
return results;
}()));
}
return renderList();
};
return batchEnrollment;
}());
this.AuthList = (function() {
function authList($container, rolename) {
var authlist = this;
this.$container = $container;
this.rolename = rolename;
this.$display_table = this.$container.find('.auth-list-table');
this.$request_response_error = this.$container.find('.request-response-error');
this.$add_section = this.$container.find('.auth-list-add');
this.$allow_field = this.$add_section.find("input[name='email']");
this.$allow_button = this.$add_section.find("input[name='allow']");
this.$allow_button.click(function() {
authlist.access_change(authlist.$allow_field.val(), 'allow', function() {
return authlist.reload_auth_list();
});
return authlist.$allow_field.val('');
});
this.reload_auth_list();
}
authList.prototype.reload_auth_list = function() {
var loadAuthList,
ths = this;
loadAuthList = function(data) {
var $tablePlaceholder, WHICH_CELL_IS_REVOKE, columns, grid, options, tableData;
ths.$request_response_error.empty();
ths.$display_table.empty();
options = {
enableCellNavigation: true,
enableColumnReorder: false,
forceFitColumns: true forceFitColumns: true
};
# this is a hack to put a button/link in a slick grid cell WHICH_CELL_IS_REVOKE = 3;
# if you change columns, then you must update
# WHICH_CELL_IS_REVOKE to have the index
# of the revoke column (left to right).
WHICH_CELL_IS_REVOKE = 3
columns = [ columns = [
id: 'username' {
field: 'username' id: 'username',
field: 'username',
name: 'Username' name: 'Username'
, }, {
id: 'email' id: 'email',
field: 'email' field: 'email',
name: 'Email' name: 'Email'
, }, {
id: 'first_name' id: 'first_name',
field: 'first_name' field: 'first_name',
name: 'First Name' name: 'First Name'
, }, {
# id: 'last_name' id: 'revoke',
# field: 'last_name' field: 'revoke',
# name: 'Last Name' name: 'Revoke',
# , formatter: function() {
id: 'revoke' return "<span class='revoke-link'>Revoke Access</span>";
field: 'revoke' }
name: 'Revoke' }
formatter: (row, cell, value, columnDef, dataContext) -> ];
"<span class='revoke-link'>Revoke Access</span>" tableData = data[ths.rolename];
] $tablePlaceholder = $('<div/>', {
class: 'slickgrid'
table_data = data[@rolename] });
ths.$display_table.append($tablePlaceholder);
$table_placeholder = $ '<div/>', class: 'slickgrid' grid = new window.Slick.Grid($tablePlaceholder, tableData, columns, options);
@$display_table.append $table_placeholder return grid.onClick.subscribe(function(e, args) {
grid = new Slick.Grid($table_placeholder, table_data, columns, options) var item;
item = args.grid.getDataItem(args.row);
# click handler part of the revoke button/link hack. if (args.cell === WHICH_CELL_IS_REVOKE) {
grid.onClick.subscribe (e, args) => return ths.access_change(item.email, 'revoke', function() {
item = args.grid.getDataItem(args.row) return ths.reload_auth_list();
if args.cell is WHICH_CELL_IS_REVOKE });
@access_change item.email, 'revoke', => @reload_auth_list() }
return false;
# fetch data from the endpoint });
# the endpoint comes from data-endpoint of the table };
$.ajax return $.ajax({
dataType: 'json' dataType: 'json',
type: 'POST' type: 'POST',
url: @$display_table.data 'endpoint' url: this.$display_table.data('endpoint'),
data: rolename: @rolename data: {
success: load_auth_list rolename: this.rolename
error: std_ajax_err => @$request_response_error.text "Error fetching list for '#{@rolename}'" },
success: loadAuthList,
error: statusAjaxError(function() {
# slickgrid's layout collapses when rendered return ths.$request_response_error.text("Error fetching list for '" + ths.rolename + "'");
# in an invisible div. use this method to reload })
# the AuthList widget });
refresh: -> };
@$display_table.empty()
@reload_auth_list() authList.prototype.refresh = function() {
this.$display_table.empty();
# update the access of a user. return this.reload_auth_list();
# (add or remove them from the list) };
# action should be one of ['allow', 'revoke']
access_change: (email, action, cb) -> authList.prototype.access_change = function(email, action, cb) {
$.ajax var ths = this;
dataType: 'json' return $.ajax({
type: 'POST' dataType: 'json',
url: @$add_section.data 'endpoint' type: 'POST',
data: url: this.$add_section.data('endpoint'),
email: email data: {
rolename: @rolename email: email,
rolename: this.rolename,
action: action action: action
success: (data) -> cb?(data) },
error: std_ajax_err => @$request_response_error.text gettext "Error changing user's permissions." success: function(data) {
return typeof cb === 'function' ? cb(data) : undefined;
},
# Membership Section error: statusAjaxError(function() {
class Membership return ths.$request_response_error.text(gettext("Error changing user's permissions."));
# enable subsections. })
constructor: (@$section) -> });
# attach self to html };
# so that instructor_dashboard.coffee can find this object
# to call event handlers like 'onClickTitle' return authList;
@$section.data 'wrapper', @ }());
# isolate # initialize BatchEnrollment subsection Membership = (function() {
plantTimeout 0, => new BatchEnrollment @$section.find '.batch-enrollment' function membership($section) {
var authList, i, len, ref,
# isolate # initialize AutoEnrollmentViaCsv subsection thismembership = this;
plantTimeout 0, => new AutoEnrollmentViaCsv @$section.find '.auto_enroll_csv' this.$section = $section;
this.$section.data('wrapper', this);
# initialize BetaTesterBulkAddition subsection plantTimeout(0, function() {
plantTimeout 0, => new BetaTesterBulkAddition @$section.find '.batch-beta-testers' return new BatchEnrollment(thismembership.$section.find('.batch-enrollment'));
});
# gather elements plantTimeout(0, function() {
@$list_selector = @$section.find 'select#member-lists-selector' return new AutoEnrollmentViaCsv(thismembership.$section.find('.auto_enroll_csv'));
@$auth_list_containers = @$section.find '.auth-list-container' });
@$auth_list_errors = @$section.find '.member-lists-management .request-response-error' plantTimeout(0, function() {
return new BetaTesterBulkAddition(thismembership.$section.find('.batch-beta-testers'));
# initialize & store AuthList subsections });
# one for each .auth-list-container in the section. this.$list_selector = this.$section.find('select#member-lists-selector');
@auth_lists = _.map (@$auth_list_containers), (auth_list_container) => this.$auth_list_containers = this.$section.find('.auth-list-container');
rolename = $(auth_list_container).data 'rolename' this.$auth_list_errors = this.$section.find('.member-lists-management .request-response-error');
new AuthListWidget $(auth_list_container), rolename, @$auth_list_errors this.auth_lists = _.map(this.$auth_list_containers, function(authListContainer) {
var rolename;
# populate selector rolename = $(authListContainer).data('rolename');
@$list_selector.empty() return new AuthListWidget($(authListContainer), rolename, thismembership.$auth_list_errors);
for auth_list in @auth_lists });
@$list_selector.append $ '<option/>', this.$list_selector.empty();
text: auth_list.$container.data 'display-name' ref = this.auth_lists;
data: for (i = 0, len = ref.length; i < len; i++) {
auth_list: auth_list authList = ref[i];
if @auth_lists.length is 0 this.$list_selector.append($('<option/>', {
@$list_selector.hide() text: authList.$container.data('display-name'),
data: {
@$list_selector.change => auth_list: authList
$opt = @$list_selector.children('option:selected') }
return unless $opt.length > 0 }));
for auth_list in @auth_lists }
auth_list.$container.removeClass 'active' if (this.auth_lists.length === 0) {
auth_list = $opt.data('auth_list') this.$list_selector.hide();
auth_list.$container.addClass 'active' }
auth_list.re_view() this.$list_selector.change(function() {
var $opt, j, len1, ref1;
# one-time first selection of top list. $opt = thismembership.$list_selector.children('option:selected');
@$list_selector.change() if (!($opt.length > 0)) {
return;
# handler for when the section title is clicked. }
onClickTitle: -> ref1 = thismembership.auth_lists;
for (j = 0, len1 = ref1.length; j < len1; j++) {
authList = ref1[j];
# export for use authList.$container.removeClass('active');
# create parent namespaces if they do not already exist. }
_.defaults window, InstructorDashboard: {} authList = $opt.data('auth_list');
_.defaults window.InstructorDashboard, sections: {} authList.$container.addClass('active');
_.defaults window.InstructorDashboard.sections, authList.re_view();
});
this.$list_selector.change();
}
membership.prototype.onClickTitle = function() {};
return membership;
}());
_.defaults(window, {
InstructorDashboard: {}
});
_.defaults(window.InstructorDashboard, {
sections: {}
});
_.defaults(window.InstructorDashboard.sections, {
Membership: Membership Membership: Membership
});
}).call(this);
# METRICS Section (function() {
'use strict';
var Metrics;
# imports from other modules. Metrics = (function() {
# wrap in (-> ... apply) to defer evaluation function metrics($section) {
# such that the value can be defined later than this assignment (file load order). this.$section = $section;
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments this.$section.data('wrapper', this);
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments }
#Metrics Section metrics.prototype.onClickTitle = function() {};
class Metrics
constructor: (@$section) ->
@$section.data 'wrapper', @
return metrics;
}());
# handler for when the section title is clicked. window.InstructorDashboard.sections.Metrics = Metrics;
onClickTitle: -> }).call(this);
# export for use
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Metrics: Metrics
### /* globals _, SendEmail */
Email Section
(function() {
imports from other modules. 'use strict';
wrap in (-> ... apply) to defer evaluation var KeywordValidator, PendingInstructorTasks,
such that the value can be defined later than this assignment (file load order). createEmailContentTable, createEmailMessageViews, createTaskListTable,
### plantTimeout, statusAjaxError;
# Load utilities plantTimeout = function() {
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments return window.InstructorDashboard.util.plantTimeout.apply(this, arguments);
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments };
PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments statusAjaxError = function() {
create_email_content_table = -> window.InstructorDashboard.util.create_email_content_table.apply this, arguments return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
create_email_message_views = -> window.InstructorDashboard.util.create_email_message_views.apply this, arguments };
KeywordValidator = -> window.InstructorDashboard.util.KeywordValidator
PendingInstructorTasks = function() {
class @SendEmail return window.InstructorDashboard.util.PendingInstructorTasks;
constructor: (@$container) -> };
# gather elements
@$emailEditor = XBlock.initializeBlock($('.xblock-studio_view')); createTaskListTable = function() {
@$send_to = @$container.find("input[name='send_to']") return window.InstructorDashboard.util.createTaskListTable.apply(this, arguments);
@$cohort_targets = @$send_to.filter('[value^="cohort:"]') };
@$subject = @$container.find("input[name='subject']")
@$btn_send = @$container.find("input[name='send']") createEmailContentTable = function() {
@$task_response = @$container.find(".request-response") return window.InstructorDashboard.util.createEmailContentTable.apply(this, arguments);
@$request_response_error = @$container.find(".request-response-error") };
@$content_request_response_error = @$container.find(".content-request-response-error")
@$history_request_response_error = @$container.find(".history-request-response-error") createEmailMessageViews = function() {
@$btn_task_history_email = @$container.find("input[name='task-history-email']") return window.InstructorDashboard.util.createEmailMessageViews.apply(this, arguments);
@$btn_task_history_email_content = @$container.find("input[name='task-history-email-content']") };
@$table_task_history_email = @$container.find(".task-history-email-table")
@$table_email_content_history = @$container.find(".content-history-email-table") KeywordValidator = function() {
@$email_content_table_inner = @$container.find(".content-history-table-inner") return window.InstructorDashboard.util.KeywordValidator;
@$email_messages_wrapper = @$container.find(".email-messages-wrapper") };
# attach click handlers this.SendEmail = (function() {
function SendEmail($container) {
@$btn_send.click => var sendemail = this;
subject = @$subject.val() this.$container = $container;
body = @$emailEditor.save()['data'] this.$emailEditor = XBlock.initializeBlock($('.xblock-studio_view'));
targets = [] this.$send_to = this.$container.find("input[name='send_to']");
@$send_to.filter(':checked').each -> this.$cohort_targets = this.$send_to.filter('[value^="cohort:"]');
targets.push(this.value) this.$subject = this.$container.find("input[name='subject']");
this.$btn_send = this.$container.find("input[name='send']");
if subject == "" this.$task_response = this.$container.find('.request-response');
alert gettext("Your message must have a subject.") this.$request_response_error = this.$container.find('.request-response-error');
this.$content_request_response_error = this.$container.find('.content-request-response-error');
else if body == "" this.$history_request_response_error = this.$container.find('.history-request-response-error');
alert gettext("Your message cannot be blank.") this.$btn_task_history_email = this.$container.find("input[name='task-history-email']");
this.$btn_task_history_email_content = this.$container.find("input[name='task-history-email-content']");
else if targets.length == 0 this.$table_task_history_email = this.$container.find('.task-history-email-table');
alert gettext("Your message must have at least one target.") this.$table_email_content_history = this.$container.find('.content-history-email-table');
this.$email_content_table_inner = this.$container.find('.content-history-table-inner');
else this.$email_messages_wrapper = this.$container.find('.email-messages-wrapper');
# Validation for keyword substitution this.$btn_send.click(function() {
validation = KeywordValidator().validate_string body var body, confirmMessage, displayTarget, fullConfirmMessage, message,
if not validation.is_valid sendData, subject, successMessage, target, targets, validation, i, len;
message = gettext("There are invalid keywords in your email. Check the following keywords and try again.") subject = sendemail.$subject.val();
message += "\n" + validation.invalid_keywords.join('\n') body = sendemail.$emailEditor.save().data;
alert message targets = [];
return sendemail.$send_to.filter(':checked').each(function() {
return targets.push(this.value);
display_target = (value) -> });
if value == "myself" if (subject === '') {
gettext("Yourself") return alert(gettext('Your message must have a subject.')); // eslint-disable-line no-alert
else if value == "staff" } else if (body === '') {
gettext("Everyone who has staff privileges in this course") return alert(gettext('Your message cannot be blank.')); // eslint-disable-line no-alert
else if value == "learners" } else if (targets.length === 0) {
gettext("All learners who are enrolled in this course") return alert(gettext( // eslint-disable-line no-alert
else 'Your message must have at least one target.'));
gettext("All learners in the {cohort_name} cohort").replace('{cohort_name}', value.slice(value.indexOf(':')+1)) } else {
success_message = gettext("Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.") validation = KeywordValidator().validate_string(body);
confirm_message = gettext("You are sending an email message with the subject {subject} to the following recipients.") if (!validation.isValid) {
for target in targets message = gettext(
confirm_message += "\n-" + display_target(target) 'There are invalid keywords in your email. Check the following keywords and try again.');
confirm_message += "\n\n" + gettext("Is this OK?") message += '\n' + validation.invalidKeywords.join('\n');
full_confirm_message = confirm_message.replace('{subject}', subject) alert(message); // eslint-disable-line no-alert
return false;
if confirm full_confirm_message }
displayTarget = function(value) {
send_data = if (value === 'myself') {
action: 'send' return gettext('Yourself');
send_to: JSON.stringify(targets) } else if (value === 'staff') {
subject: subject return gettext('Everyone who has staff privileges in this course');
} else if (value === 'learners') {
return gettext('All learners who are enrolled in this course');
} else {
return gettext('All learners in the {cohort_name} cohort')
.replace('{cohort_name}', value.slice(value.indexOf(':') + 1));
}
};
successMessage = gettext('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.'); // eslint-disable-line max-len
confirmMessage = gettext(
'You are sending an email message with the subject {subject} to the following recipients.');
for (i = 0, len = targets.length; i < len; i++) {
target = targets[i];
confirmMessage += '\n-' + displayTarget(target);
}
confirmMessage += '\n\n' + gettext('Is this OK?');
fullConfirmMessage = confirmMessage.replace('{subject}', subject);
if (confirm(fullConfirmMessage)) { // eslint-disable-line no-alert
sendData = {
action: 'send',
send_to: JSON.stringify(targets),
subject: subject,
message: body message: body
};
return $.ajax({
type: 'POST',
dataType: 'json',
url: sendemail.$btn_send.data('endpoint'),
data: sendData,
success: function() {
return sendemail.display_response(successMessage);
},
error: statusAjaxError(function() {
return sendemail.fail_with_error(gettext('Error sending email.'));
})
});
} else {
sendemail.task_response.empty();
return sendemail.$request_response_error.empty();
}
}
});
this.$btn_task_history_email.click(function() {
var url = sendemail.$btn_task_history_email.data('endpoint');
return $.ajax({
type: 'POST',
dataType: 'json',
url: url,
success: function(data) {
if (data.tasks.length) {
return createTaskListTable(sendemail.$table_task_history_email, data.tasks);
} else {
sendemail.$history_request_response_error.text(
gettext('There is no email history for this course.')
);
return sendemail.$history_request_response_error.css({
display: 'block'
});
}
},
error: statusAjaxError(function() {
return sendemail.$history_request_response_error.text(
gettext('There was an error obtaining email task history for this course.')
);
})
});
});
this.$btn_task_history_email_content.click(function() {
var url = sendemail.$btn_task_history_email_content.data('endpoint');
return $.ajax({
type: 'POST',
dataType: 'json',
url: url,
success: function(data) {
if (data.emails.length) {
createEmailContentTable(sendemail.$table_email_content_history,
sendemail.$email_content_table_inner, data.emails
);
return createEmailMessageViews(sendemail.$email_messages_wrapper, data.emails);
} else {
sendemail.$content_request_response_error.text(
gettext('There is no email history for this course.')
);
return sendemail.$content_request_response_error.css({
display: 'block'
});
}
},
error: statusAjaxError(function() {
return sendemail.$content_request_response_error.text(
gettext('There was an error obtaining email content history for this course.')
);
})
});
});
this.$send_to.change(function() {
var targets;
if ($('input#target_learners:checked').length) {
sendemail.$cohort_targets.each(function() {
this.checked = false;
this.disabled = true;
return true;
});
} else {
sendemail.$cohort_targets.each(function() {
this.disabled = false;
return true;
});
}
targets = [];
$('input[name="send_to"]:checked+label').each(function() {
return targets.push(this.innerText.replace(/\s*\n.*/g, ''));
});
return $('.send_to_list').text(gettext('Send to:') + ' ' + targets.join(', '));
});
}
SendEmail.prototype.fail_with_error = function(msg) {
this.$task_response.empty();
this.$request_response_error.empty();
this.$request_response_error.text(msg);
return $('.msg-confirm').css({
display: 'none'
});
};
SendEmail.prototype.display_response = function(dataFromServer) {
this.$task_response.empty();
this.$request_response_error.empty();
this.$task_response.text(dataFromServer);
return $('.msg-confirm').css({
display: 'block'
});
};
return SendEmail;
}());
this.Email = (function() {
function email($section) {
var eml = this;
this.$section = $section;
this.$section.data('wrapper', this);
plantTimeout(0, function() {
return new SendEmail(eml.$section.find('.send-email'));
});
this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
}
email.prototype.onClickTitle = function() {
return this.instructor_tasks.task_poller.start();
};
email.prototype.onExit = function() {
return this.instructor_tasks.task_poller.stop();
};
return email;
}());
_.defaults(window, {
InstructorDashboard: {}
});
_.defaults(window.InstructorDashboard, {
sections: {}
});
$.ajax _.defaults(window.InstructorDashboard.sections, {
type: 'POST' Email: this.Email
dataType: 'json' });
url: @$btn_send.data 'endpoint' }).call(this);
data: send_data
success: (data) =>
@display_response success_message
error: std_ajax_err =>
@fail_with_error gettext('Error sending email.')
else
@task_response.empty()
@$request_response_error.empty()
# list task history for email
@$btn_task_history_email.click =>
url = @$btn_task_history_email.data 'endpoint'
$.ajax
type: 'POST'
dataType: 'json'
url: url
success: (data) =>
if data.tasks.length
create_task_list_table @$table_task_history_email, data.tasks
else
@$history_request_response_error.text gettext("There is no email history for this course.")
# Enable the msg-warning css display
@$history_request_response_error.css({"display":"block"})
error: std_ajax_err =>
@$history_request_response_error.text gettext("There was an error obtaining email task history for this course.")
# List content history for emails sent
@$btn_task_history_email_content.click =>
url = @$btn_task_history_email_content.data 'endpoint'
$.ajax
type: 'POST'
dataType: 'json'
url : url
success: (data) =>
if data.emails.length
create_email_content_table @$table_email_content_history, @$email_content_table_inner, data.emails
create_email_message_views @$email_messages_wrapper, data.emails
else
@$content_request_response_error.text gettext("There is no email history for this course.")
@$content_request_response_error.css({"display":"block"})
error: std_ajax_err =>
@$content_request_response_error.text gettext("There was an error obtaining email content history for this course.")
@$send_to.change =>
# Ensure invalid combinations are disabled
if $('input#target_learners:checked').length
# If all is selected, cohorts can't be
@$cohort_targets.each ->
this.checked = false
this.disabled = true
true
else
@$cohort_targets.each ->
this.disabled = false
true
# Also, keep the sent_to_list div updated
targets = []
$('input[name="send_to"]:checked+label').each ->
# Only use the first line, even if a subheading is present
targets.push(this.innerText.replace(/\s*\n.*/g,''))
$(".send_to_list").text(gettext("Send to:") + " " + targets.join(", "))
fail_with_error: (msg) ->
console.warn msg
@$task_response.empty()
@$request_response_error.empty()
@$request_response_error.text msg
$(".msg-confirm").css({"display":"none"})
display_response: (data_from_server) ->
@$task_response.empty()
@$request_response_error.empty()
@$task_response.text(data_from_server)
$(".msg-confirm").css({"display":"block"})
# Email Section
class Email
# enable subsections.
constructor: (@$section) ->
# attach self to html so that instructor_dashboard.coffee can find
# this object to call event handlers like 'onClickTitle'
@$section.data 'wrapper', @
# isolate # initialize SendEmail subsection
plantTimeout 0, => new SendEmail @$section.find '.send-email'
@instructor_tasks = new (PendingInstructorTasks()) @$section
# handler for when the section title is clicked.
onClickTitle: -> @instructor_tasks.task_poller.start()
# handler for when the section is closed
onExit: -> @instructor_tasks.task_poller.stop()
# export for use
# create parent namespaces if they do not already exist.
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Email: Email
### /* globals _, interpolate_text */
Student Admin Section
(function() {
imports from other modules. 'use strict';
wrap in (-> ... apply) to defer evaluation var PendingInstructorTasks, createTaskListTable, findAndAssert, statusAjaxError;
such that the value can be defined later than this assignment (file load order).
### statusAjaxError = function() {
return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
# Load utilities };
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments createTaskListTable = function() {
PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks return window.InstructorDashboard.util.createTaskListTable.apply(this, arguments);
};
# get jquery element and assert its existance PendingInstructorTasks = function() {
find_and_assert = ($root, selector) -> return window.InstructorDashboard.util.PendingInstructorTasks;
item = $root.find selector };
if item.length != 1
console.error "element selection failed for '#{selector}' resulted in length #{item.length}" findAndAssert = function($root, selector) {
throw "Failed Element Selection" var item, msg;
else item = $root.find(selector);
item if (item.length !== 1) {
msg = 'Failed Element Selection';
throw msg;
class @StudentAdmin } else {
constructor: (@$section) -> return item;
# attach self to html so that instructor_dashboard.coffee can find }
# this object to call event handlers like 'onClickTitle' };
@$section.data 'wrapper', @
this.StudentAdmin = (function() {
# gather buttons function StudentAdmin($section) {
# some buttons are optional because they can be flipped by the instructor task feature switch var studentadmin = this;
# student-specific this.$section = $section;
@$field_student_select_progress = find_and_assert @$section, "input[name='student-select-progress']" this.$section.data('wrapper', this);
@$field_student_select_grade = find_and_assert @$section, "input[name='student-select-grade']" this.$field_student_select_progress = findAndAssert(this.$section, "input[name='student-select-progress']");
@$progress_link = find_and_assert @$section, "a.progress-link" this.$field_student_select_grade = findAndAssert(this.$section, "input[name='student-select-grade']");
@$field_problem_select_single = find_and_assert @$section, "input[name='problem-select-single']" this.$progress_link = findAndAssert(this.$section, 'a.progress-link');
@$btn_reset_attempts_single = find_and_assert @$section, "input[name='reset-attempts-single']" this.$field_problem_select_single = findAndAssert(this.$section, "input[name='problem-select-single']");
@$btn_delete_state_single = @$section.find "input[name='delete-state-single']" this.$btn_reset_attempts_single = findAndAssert(this.$section, "input[name='reset-attempts-single']");
@$btn_rescore_problem_single = @$section.find "input[name='rescore-problem-single']" this.$btn_delete_state_single = this.$section.find("input[name='delete-state-single']");
@$btn_task_history_single = @$section.find "input[name='task-history-single']" this.$btn_rescore_problem_single = this.$section.find("input[name='rescore-problem-single']");
@$table_task_history_single = @$section.find ".task-history-single-table" this.$btn_task_history_single = this.$section.find("input[name='task-history-single']");
this.$table_task_history_single = this.$section.find('.task-history-single-table');
# entrance-exam-specific this.$field_exam_grade = this.$section.find("input[name='entrance-exam-student-select-grade']");
@$field_entrance_exam_student_select_grade = @$section.find "input[name='entrance-exam-student-select-grade']" this.$btn_reset_entrance_exam_attempts = this.$section.find("input[name='reset-entrance-exam-attempts']");
@$btn_reset_entrance_exam_attempts = @$section.find "input[name='reset-entrance-exam-attempts']" this.$btn_delete_entrance_exam_state = this.$section.find("input[name='delete-entrance-exam-state']");
@$btn_delete_entrance_exam_state = @$section.find "input[name='delete-entrance-exam-state']" this.$btn_rescore_entrance_exam = this.$section.find("input[name='rescore-entrance-exam']");
@$btn_rescore_entrance_exam = @$section.find "input[name='rescore-entrance-exam']" this.$btn_skip_entrance_exam = this.$section.find("input[name='skip-entrance-exam']");
@$btn_skip_entrance_exam = @$section.find "input[name='skip-entrance-exam']" this.$btn_entrance_exam_task_history = this.$section.find("input[name='entrance-exam-task-history']");
@$btn_entrance_exam_task_history = @$section.find "input[name='entrance-exam-task-history']" this.$table_entrance_exam_task_history = this.$section.find('.entrance-exam-task-history-table');
@$table_entrance_exam_task_history = @$section.find ".entrance-exam-task-history-table" this.$field_problem_select_all = this.$section.find("input[name='problem-select-all']");
this.$btn_reset_attempts_all = this.$section.find("input[name='reset-attempts-all']");
# course-specific this.$btn_rescore_problem_all = this.$section.find("input[name='rescore-problem-all']");
@$field_problem_select_all = @$section.find "input[name='problem-select-all']" this.$btn_task_history_all = this.$section.find("input[name='task-history-all']");
@$btn_reset_attempts_all = @$section.find "input[name='reset-attempts-all']" this.$table_task_history_all = this.$section.find('.task-history-all-table');
@$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']" this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
@$btn_task_history_all = @$section.find "input[name='task-history-all']" this.$request_err = findAndAssert(this.$section, '.student-specific-container .request-response-error');
@$table_task_history_all = @$section.find ".task-history-all-table" this.$request_err_grade = findAndAssert(this.$section, '.student-grade-container .request-response-error');
@instructor_tasks = new (PendingInstructorTasks()) @$section this.$request_err_ee = this.$section.find('.entrance-exam-grade-container .request-response-error');
this.$request_response_error_all = this.$section.find('.course-specific-container .request-response-error');
# response areas this.$progress_link.click(function(e) {
@$request_response_error_progress = find_and_assert @$section, ".student-specific-container .request-response-error" var errorMessage, fullErrorMessage, uniqStudentIdentifier;
@$request_response_error_grade = find_and_assert @$section, ".student-grade-container .request-response-error" e.preventDefault();
@$request_response_error_ee = @$section.find ".entrance-exam-grade-container .request-response-error" uniqStudentIdentifier = studentadmin.$field_student_select_progress.val();
@$request_response_error_all = @$section.find ".course-specific-container .request-response-error" if (!uniqStudentIdentifier) {
return studentadmin.$request_err.text(
# attach click handlers gettext('Please enter a student email address or username.')
);
# go to student progress page }
@$progress_link.click (e) => errorMessage = gettext("Error getting student progress url for '<%- student_id %>'. Make sure that the student identifier is spelled correctly."); // eslint-disable-line max-len
e.preventDefault() fullErrorMessage = _.template(errorMessage)({
unique_student_identifier = @$field_student_select_progress.val() student_id: uniqStudentIdentifier
if not unique_student_identifier });
return @$request_response_error_progress.text gettext("Please enter a student email address or username.") return $.ajax({
error_message = gettext("Error getting student progress url for '<%= student_id %>'. Make sure that the student identifier is spelled correctly.") type: 'POST',
full_error_message = _.template(error_message)({student_id: unique_student_identifier}) dataType: 'json',
url: studentadmin.$progress_link.data('endpoint'),
$.ajax data: {
type: 'POST' unique_student_identifier: uniqStudentIdentifier
dataType: 'json' },
url: @$progress_link.data 'endpoint' success: studentadmin.clear_errors_then(function(data) {
data: unique_student_identifier: unique_student_identifier window.location = data.progress_url;
success: @clear_errors_then (data) -> return window.location;
window.location = data.progress_url }),
error: std_ajax_err => @$request_response_error_progress.text full_error_message error: statusAjaxError(function() {
return studentadmin.$request_err.text(fullErrorMessage);
# reset attempts for student on problem })
@$btn_reset_attempts_single.click => });
unique_student_identifier = @$field_student_select_grade.val() });
problem_to_reset = @$field_problem_select_single.val() this.$btn_reset_attempts_single.click(function() {
if not unique_student_identifier var errorMessage, fullErrorMessage, fullSuccessMessage,
return @$request_response_error_grade.text gettext("Please enter a student email address or username.") problemToReset, sendData, successMessage, uniqStudentIdentifier;
if not problem_to_reset uniqStudentIdentifier = studentadmin.$field_student_select_grade.val();
return @$request_response_error_grade.text gettext("Please enter a problem location.") problemToReset = studentadmin.$field_problem_select_single.val();
send_data = if (!uniqStudentIdentifier) {
unique_student_identifier: unique_student_identifier return studentadmin.$request_err_grade.text(
problem_to_reset: problem_to_reset gettext('Please enter a student email address or username.')
);
}
if (!problemToReset) {
return studentadmin.$request_err_grade.text(gettext('Please enter a problem location.'));
}
sendData = {
unique_student_identifier: uniqStudentIdentifier,
problem_to_reset: problemToReset,
delete_module: false delete_module: false
success_message = gettext("Success! Problem attempts reset for problem '<%= problem_id %>' and student '<%= student_id %>'.") };
error_message = gettext("Error resetting problem attempts for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.") successMessage = gettext("Success! Problem attempts reset for problem '<%- problem_id %>' and student '<%- student_id %>'."); // eslint-disable-line max-len
full_success_message = _.template(success_message)({problem_id: problem_to_reset, student_id: unique_student_identifier}) errorMessage = gettext("Error resetting problem attempts for problem '<%= problem_id %>' and student '<%- student_id %>'. Make sure that the problem and student identifiers are complete and correct."); // eslint-disable-line max-len
full_error_message = _.template(error_message)({problem_id: problem_to_reset, student_id: unique_student_identifier}) fullSuccessMessage = _.template(successMessage)({
problem_id: problemToReset,
$.ajax student_id: uniqStudentIdentifier
type: 'POST' });
dataType: 'json' fullErrorMessage = _.template(errorMessage)({
url: @$btn_reset_attempts_single.data 'endpoint' problem_id: problemToReset,
data: send_data student_id: uniqStudentIdentifier
success: @clear_errors_then -> alert full_success_message });
error: std_ajax_err => @$request_response_error_grade.text full_error_message return $.ajax({
type: 'POST',
# delete state for student on problem dataType: 'json',
@$btn_delete_state_single.click => url: studentadmin.$btn_reset_attempts_single.data('endpoint'),
unique_student_identifier = @$field_student_select_grade.val() data: sendData,
problem_to_reset = @$field_problem_select_single.val() success: studentadmin.clear_errors_then(function() {
if not unique_student_identifier return alert(fullSuccessMessage); // eslint-disable-line no-alert
return @$request_response_error_grade.text gettext("Please enter a student email address or username.") }),
if not problem_to_reset error: statusAjaxError(function() {
return @$request_response_error_grade.text gettext("Please enter a problem location.") return studentadmin.$request_err_grade.text(fullErrorMessage);
confirm_message = gettext("Delete student '<%= student_id %>'s state on problem '<%= problem_id %>'?") })
full_confirm_message = _.template(confirm_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) });
});
if window.confirm full_confirm_message this.$btn_delete_state_single.click(function() {
send_data = var confirmMessage, errorMessage, fullConfirmMessage,
unique_student_identifier: unique_student_identifier fullErrorMessage, problemToReset, sendData, uniqStudentIdentifier;
problem_to_reset: problem_to_reset uniqStudentIdentifier = studentadmin.$field_student_select_grade.val();
problemToReset = studentadmin.$field_problem_select_single.val();
if (!uniqStudentIdentifier) {
return studentadmin.$request_err_grade.text(
gettext('Please enter a student email address or username.')
);
}
if (!problemToReset) {
return studentadmin.$request_err_grade.text(
gettext('Please enter a problem location.')
);
}
confirmMessage = gettext("Delete student '<%- student_id %>'s state on problem '<%- problem_id %>'?");
fullConfirmMessage = _.template(confirmMessage)({
student_id: uniqStudentIdentifier,
problem_id: problemToReset
});
if (window.confirm(fullConfirmMessage)) { // eslint-disable-line no-alert
sendData = {
unique_student_identifier: uniqStudentIdentifier,
problem_to_reset: problemToReset,
delete_module: true delete_module: true
error_message = gettext("Error deleting student '<%= student_id %>'s state on problem '<%= problem_id %>'. Make sure that the problem and student identifiers are complete and correct.") };
full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) errorMessage = gettext("Error deleting student '<%- student_id %>'s state on problem '<%- problem_id %>'. Make sure that the problem and student identifiers are complete and correct."); // eslint-disable-line max-len
fullErrorMessage = _.template(errorMessage)({
$.ajax student_id: uniqStudentIdentifier,
type: 'POST' problem_id: problemToReset
dataType: 'json' });
url: @$btn_delete_state_single.data 'endpoint' return $.ajax({
data: send_data type: 'POST',
success: @clear_errors_then -> alert gettext('Module state successfully deleted.') dataType: 'json',
error: std_ajax_err => @$request_response_error_grade.text full_error_message url: studentadmin.$btn_delete_state_single.data('endpoint'),
else data: sendData,
# Clear error messages if "Cancel" was chosen on confirmation alert success: studentadmin.clear_errors_then(function() {
@clear_errors() return alert(gettext('Module state successfully deleted.')); // eslint-disable-line no-alert, max-len
}),
# start task to rescore problem for student error: statusAjaxError(function() {
@$btn_rescore_problem_single.click => return studentadmin.$request_err_grade.text(fullErrorMessage);
unique_student_identifier = @$field_student_select_grade.val() })
problem_to_reset = @$field_problem_select_single.val() });
if not unique_student_identifier } else {
return @$request_response_error_grade.text gettext("Please enter a student email address or username.") return studentadmin.clear_errors();
if not problem_to_reset }
return @$request_response_error_grade.text gettext("Please enter a problem location.") });
send_data = this.$btn_rescore_problem_single.click(function() {
unique_student_identifier: unique_student_identifier var errorMessage, fullErrorMessage, fullSuccessMessage,
problem_to_reset: problem_to_reset problemToReset, sendData, successMessage, uniqStudentIdentifier;
success_message = gettext("Started rescore problem task for problem '<%= problem_id %>' and student '<%= student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task.") uniqStudentIdentifier = studentadmin.$field_student_select_grade.val();
full_success_message = _.template(success_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) problemToReset = studentadmin.$field_problem_select_single.val();
error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>' for student '<%= student_id %>'. Make sure that the the problem and student identifiers are complete and correct.") if (!uniqStudentIdentifier) {
full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) return studentadmin.$request_err_grade.text(
gettext('Please enter a student email address or username.')
$.ajax );
type: 'POST' }
dataType: 'json' if (!problemToReset) {
url: @$btn_rescore_problem_single.data 'endpoint' return studentadmin.$request_err_grade.text(
data: send_data gettext('Please enter a problem location.')
success: @clear_errors_then -> alert full_success_message );
error: std_ajax_err => @$request_response_error_grade.text full_error_message }
sendData = {
# list task history for student+problem unique_student_identifier: uniqStudentIdentifier,
@$btn_task_history_single.click => problem_to_reset: problemToReset
unique_student_identifier = @$field_student_select_grade.val() };
problem_to_reset = @$field_problem_select_single.val() successMessage = gettext("Started rescore problem task for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task."); // eslint-disable-line max-len
if not unique_student_identifier fullSuccessMessage = _.template(successMessage)({
return @$request_response_error_grade.text gettext("Please enter a student email address or username.") student_id: uniqStudentIdentifier,
if not problem_to_reset problem_id: problemToReset
return @$request_response_error_grade.text gettext("Please enter a problem location.") });
send_data = errorMessage = gettext("Error starting a task to rescore problem '<%- problem_id %>' for student '<%- student_id %>'. Make sure that the the problem and student identifiers are complete and correct."); // eslint-disable-line max-len
unique_student_identifier: unique_student_identifier fullErrorMessage = _.template(errorMessage)({
problem_location_str: problem_to_reset student_id: uniqStudentIdentifier,
error_message = gettext("Error getting task history for problem '<%= problem_id %>' and student '<%= student_id %>'. Make sure that the problem and student identifiers are complete and correct.") problem_id: problemToReset
full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) });
return $.ajax({
$.ajax type: 'POST',
type: 'POST' dataType: 'json',
dataType: 'json' url: studentadmin.$btn_rescore_problem_single.data('endpoint'),
url: @$btn_task_history_single.data 'endpoint' data: sendData,
data: send_data success: studentadmin.clear_errors_then(function() {
success: @clear_errors_then (data) => return alert(fullSuccessMessage); // eslint-disable-line no-alert
create_task_list_table @$table_task_history_single, data.tasks }),
error: std_ajax_err => @$request_response_error_grade.text full_error_message error: statusAjaxError(function() {
return studentadmin.$request_err_grade.text(fullErrorMessage);
# reset entrance exam attempts for student })
@$btn_reset_entrance_exam_attempts.click => });
unique_student_identifier = @$field_entrance_exam_student_select_grade.val() });
if not unique_student_identifier this.$btn_task_history_single.click(function() {
return @$request_response_error_ee.text gettext("Please enter a student email address or username.") var errorMessage, fullErrorMessage, problemToReset, sendData, uniqStudentIdentifier;
send_data = uniqStudentIdentifier = studentadmin.$field_student_select_grade.val();
unique_student_identifier: unique_student_identifier problemToReset = studentadmin.$field_problem_select_single.val();
if (!uniqStudentIdentifier) {
return studentadmin.$request_err_grade.text(
gettext('Please enter a student email address or username.')
);
}
if (!problemToReset) {
return studentadmin.$request_err_grade.text(
gettext('Please enter a problem location.')
);
}
sendData = {
unique_student_identifier: uniqStudentIdentifier,
problem_location_str: problemToReset
};
errorMessage = gettext("Error getting task history for problem '<%- problem_id %>' and student '<%- student_id %>'. Make sure that the problem and student identifiers are complete and correct."); // eslint-disable-line max-len
fullErrorMessage = _.template(errorMessage)({
student_id: uniqStudentIdentifier,
problem_id: problemToReset
});
return $.ajax({
type: 'POST',
dataType: 'json',
url: studentadmin.$btn_task_history_single.data('endpoint'),
data: sendData,
success: studentadmin.clear_errors_then(function(data) {
return createTaskListTable(studentadmin.$table_task_history_single, data.tasks);
}),
error: statusAjaxError(function() {
return studentadmin.$request_err_grade.text(fullErrorMessage);
})
});
});
this.$btn_reset_entrance_exam_attempts.click(function() {
var sendData, uniqStudentIdentifier;
uniqStudentIdentifier = studentadmin.$field_exam_grade.val();
if (!uniqStudentIdentifier) {
return studentadmin.$request_err_ee.text(gettext(
'Please enter a student email address or username.')
);
}
sendData = {
unique_student_identifier: uniqStudentIdentifier,
delete_module: false delete_module: false
};
$.ajax return $.ajax({
type: 'POST' type: 'POST',
dataType: 'json' dataType: 'json',
url: @$btn_reset_entrance_exam_attempts.data 'endpoint' url: studentadmin.$btn_reset_entrance_exam_attempts.data('endpoint'),
data: send_data data: sendData,
success: @clear_errors_then -> success: studentadmin.clear_errors_then(function() {
success_message = gettext("Entrance exam attempts is being reset for student '{student_id}'.") var fullSuccessMessage, successMessage;
full_success_message = interpolate_text(success_message, {student_id: unique_student_identifier}) successMessage = gettext("Entrance exam attempts is being reset for student '{student_id}'.");
alert full_success_message fullSuccessMessage = interpolate_text(successMessage, {
error: std_ajax_err => student_id: uniqStudentIdentifier
error_message = gettext("Error resetting entrance exam attempts for student '{student_id}'. Make sure student identifier is correct.") });
full_error_message = interpolate_text(error_message, {student_id: unique_student_identifier}) return alert(fullSuccessMessage); // eslint-disable-line no-alert
@$request_response_error_ee.text full_error_message }),
error: statusAjaxError(function() {
# start task to rescore entrance exam for student var errorMessage, fullErrorMessage;
@$btn_rescore_entrance_exam.click => errorMessage = gettext("Error resetting entrance exam attempts for student '{student_id}'. Make sure student identifier is correct."); // eslint-disable-line max-len
unique_student_identifier = @$field_entrance_exam_student_select_grade.val() fullErrorMessage = interpolate_text(errorMessage, {
if not unique_student_identifier student_id: uniqStudentIdentifier
return @$request_response_error_ee.text gettext("Please enter a student email address or username.") });
send_data = return studentadmin.$request_err_ee.text(fullErrorMessage);
unique_student_identifier: unique_student_identifier })
});
$.ajax });
type: 'POST' this.$btn_rescore_entrance_exam.click(function() {
dataType: 'json' var sendData, uniqStudentIdentifier;
url: @$btn_rescore_entrance_exam.data 'endpoint' uniqStudentIdentifier = studentadmin.$field_exam_grade.val();
data: send_data if (!uniqStudentIdentifier) {
success: @clear_errors_then -> return studentadmin.$request_err_ee.text(gettext(
success_message = gettext("Started entrance exam rescore task for student '{student_id}'. Click the 'Show Background Task History for Student' button to see the status of the task.") 'Please enter a student email address or username.')
full_success_message = interpolate_text(success_message, {student_id: unique_student_identifier}) );
alert full_success_message }
error: std_ajax_err => sendData = {
error_message = gettext("Error starting a task to rescore entrance exam for student '{student_id}'. Make sure that entrance exam has problems in it and student identifier is correct.") unique_student_identifier: uniqStudentIdentifier
full_error_message = interpolate_text(error_message, {student_id: unique_student_identifier}) };
@$request_response_error_ee.text full_error_message return $.ajax({
type: 'POST',
# Mark a student to skip entrance exam dataType: 'json',
@$btn_skip_entrance_exam.click => url: studentadmin.$btn_rescore_entrance_exam.data('endpoint'),
unique_student_identifier = @$field_entrance_exam_student_select_grade.val() data: sendData,
if not unique_student_identifier success: studentadmin.clear_errors_then(function() {
return @$request_response_error_ee.text gettext("Enter a student's username or email address.") var fullSuccessMessage, successMessage;
confirm_message = gettext("Do you want to allow this student ('{student_id}') to skip the entrance exam?") successMessage = gettext("Started entrance exam rescore task for student '{student_id}'. Click the 'Show Background Task History for Student' button to see the status of the task."); // eslint-disable-line max-len
full_confirm_message = interpolate_text(confirm_message, {student_id: unique_student_identifier}) fullSuccessMessage = interpolate_text(successMessage, {
if window.confirm full_confirm_message student_id: uniqStudentIdentifier
send_data = });
unique_student_identifier: unique_student_identifier return alert(fullSuccessMessage); // eslint-disable-line no-alert
}),
$.ajax error: statusAjaxError(function() {
dataType: 'json' var errorMessage, fullErrorMessage;
url: @$btn_skip_entrance_exam.data 'endpoint' errorMessage = gettext("Error starting a task to rescore entrance exam for student '{student_id}'. Make sure that entrance exam has problems in it and student identifier is correct."); // eslint-disable-line max-len
data: send_data fullErrorMessage = interpolate_text(errorMessage, {
type: 'POST' student_id: uniqStudentIdentifier
success: @clear_errors_then (data) -> });
alert data.message return studentadmin.$request_err_ee.text(fullErrorMessage);
error: std_ajax_err => })
error_message = gettext("An error occurred. Make sure that the student's username or email address is correct and try again.") });
@$request_response_error_ee.text error_message });
this.$btn_skip_entrance_exam.click(function() {
# delete student state for entrance exam var confirmMessage, fullConfirmMessage, sendData, uniqStudentIdentifier;
@$btn_delete_entrance_exam_state.click => uniqStudentIdentifier = studentadmin.$field_exam_grade.val();
unique_student_identifier = @$field_entrance_exam_student_select_grade.val() if (!uniqStudentIdentifier) {
if not unique_student_identifier return studentadmin.$request_err_ee.text(gettext("Enter a student's username or email address."));
return @$request_response_error_ee.text gettext("Please enter a student email address or username.") }
send_data = confirmMessage = gettext("Do you want to allow this student ('{student_id}') to skip the entrance exam?"); // eslint-disable-line max-len
unique_student_identifier: unique_student_identifier fullConfirmMessage = interpolate_text(confirmMessage, {
student_id: uniqStudentIdentifier
});
if (window.confirm(fullConfirmMessage)) { // eslint-disable-line no-alert
sendData = {
unique_student_identifier: uniqStudentIdentifier
};
return $.ajax({
dataType: 'json',
url: studentadmin.$btn_skip_entrance_exam.data('endpoint'),
data: sendData,
type: 'POST',
success: studentadmin.clear_errors_then(function(data) {
return alert(data.message); // eslint-disable-line no-alert
}),
error: statusAjaxError(function() {
var errorMessage;
errorMessage = gettext("An error occurred. Make sure that the student's username or email address is correct and try again."); // eslint-disable-line max-len
return studentadmin.$request_err_ee.text(errorMessage);
})
});
}
return false;
});
this.$btn_delete_entrance_exam_state.click(function() {
var sendData, uniqStudentIdentifier;
uniqStudentIdentifier = studentadmin.$field_exam_grade.val();
if (!uniqStudentIdentifier) {
return studentadmin.$request_err_ee.text(
gettext('Please enter a student email address or username.')
);
}
sendData = {
unique_student_identifier: uniqStudentIdentifier,
delete_module: true delete_module: true
};
$.ajax return $.ajax({
type: 'POST' type: 'POST',
dataType: 'json' dataType: 'json',
url: @$btn_delete_entrance_exam_state.data 'endpoint' url: studentadmin.$btn_delete_entrance_exam_state.data('endpoint'),
data: send_data data: sendData,
success: @clear_errors_then -> success: studentadmin.clear_errors_then(function() {
success_message = gettext("Entrance exam state is being deleted for student '{student_id}'.") var fullSuccessMessage, successMessage;
full_success_message = interpolate_text(success_message, {student_id: unique_student_identifier}) successMessage = gettext("Entrance exam state is being deleted for student '{student_id}'.");
alert full_success_message fullSuccessMessage = interpolate_text(successMessage, {
error: std_ajax_err => student_id: uniqStudentIdentifier
error_message = gettext("Error deleting entrance exam state for student '{student_id}'. Make sure student identifier is correct.") });
full_error_message = interpolate_text(error_message, {student_id: unique_student_identifier}) return alert(fullSuccessMessage); // eslint-disable-line no-alert
@$request_response_error_ee.text full_error_message }),
error: statusAjaxError(function() {
# list entrance exam task history for student var errorMessage, fullErrorMessage;
@$btn_entrance_exam_task_history.click => errorMessage = gettext("Error deleting entrance exam state for student '{student_id}'. Make sure student identifier is correct."); // eslint-disable-line max-len
unique_student_identifier = @$field_entrance_exam_student_select_grade.val() fullErrorMessage = interpolate_text(errorMessage, {
if not unique_student_identifier student_id: uniqStudentIdentifier
return @$request_response_error_ee.text gettext("Enter a student's username or email address.") });
send_data = return studentadmin.$request_err_ee.text(fullErrorMessage);
unique_student_identifier: unique_student_identifier })
});
$.ajax });
type: 'POST' this.$btn_entrance_exam_task_history.click(function() {
dataType: 'json' var sendData, uniqStudentIdentifier;
url: @$btn_entrance_exam_task_history.data 'endpoint' uniqStudentIdentifier = studentadmin.$field_exam_grade.val();
data: send_data if (!uniqStudentIdentifier) {
success: @clear_errors_then (data) => return studentadmin.$request_err_ee.text(
create_task_list_table @$table_entrance_exam_task_history, data.tasks gettext("Enter a student's username or email address.")
error: std_ajax_err => );
error_message = gettext("Error getting entrance exam task history for student '{student_id}'. Make sure student identifier is correct.") }
full_error_message = interpolate_text(error_message, {student_id: unique_student_identifier}) sendData = {
@$request_response_error_ee.text full_error_message unique_student_identifier: uniqStudentIdentifier
};
# start task to reset attempts on problem for all students return $.ajax({
@$btn_reset_attempts_all.click => type: 'POST',
problem_to_reset = @$field_problem_select_all.val() dataType: 'json',
if not problem_to_reset url: studentadmin.$btn_entrance_exam_task_history.data('endpoint'),
return @$request_response_error_all.text gettext("Please enter a problem location.") data: sendData,
confirm_message = gettext("Reset attempts for all students on problem '<%= problem_id %>'?") success: studentadmin.clear_errors_then(function(data) {
full_confirm_message = _.template(confirm_message)({problem_id: problem_to_reset}) return createTaskListTable(studentadmin.$table_entrance_exam_task_history, data.tasks);
if window.confirm full_confirm_message }),
send_data = error: statusAjaxError(function() {
all_students: true var errorMessage, fullErrorMessage;
problem_to_reset: problem_to_reset errorMessage = gettext("Error getting entrance exam task history for student '{student_id}'. Make sure student identifier is correct."); // eslint-disable-line max-len
success_message = gettext("Successfully started task to reset attempts for problem '<%= problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task.") fullErrorMessage = interpolate_text(errorMessage, {
full_success_message = _.template(success_message)({problem_id: problem_to_reset}) student_id: uniqStudentIdentifier
error_message = gettext("Error starting a task to reset attempts for all students on problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.") });
full_error_message = _.template(error_message)({problem_id: problem_to_reset}) return studentadmin.$request_err_ee.text(fullErrorMessage);
})
$.ajax });
type: 'POST' });
dataType: 'json' this.$btn_reset_attempts_all.click(function() {
url: @$btn_reset_attempts_all.data 'endpoint' var confirmMessage, errorMessage, fullConfirmMessage,
data: send_data fullErrorMessage, fullSuccessMessage, problemToReset, sendData, successMessage;
success: @clear_errors_then -> alert full_success_message problemToReset = studentadmin.$field_problem_select_all.val();
error: std_ajax_err => @$request_response_error_all.text full_error_message if (!problemToReset) {
else return studentadmin.$request_response_error_all.text(
# Clear error messages if "Cancel" was chosen on confirmation alert gettext('Please enter a problem location.')
@clear_errors() );
}
# start task to rescore problem for all students confirmMessage = gettext("Reset attempts for all students on problem '<%- problem_id %>'?");
@$btn_rescore_problem_all.click => fullConfirmMessage = _.template(confirmMessage)({
problem_to_reset = @$field_problem_select_all.val() problem_id: problemToReset
if not problem_to_reset });
return @$request_response_error_all.text gettext("Please enter a problem location.") if (window.confirm(fullConfirmMessage)) { // eslint-disable-line no-alert
confirm_message = gettext("Rescore problem '<%= problem_id %>' for all students?") sendData = {
full_confirm_message = _.template(confirm_message)({problem_id: problem_to_reset}) all_students: true,
if window.confirm full_confirm_message problem_to_reset: problemToReset
send_data = };
all_students: true successMessage = gettext("Successfully started task to reset attempts for problem '<%- problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task."); // eslint-disable-line max-len
problem_to_reset: problem_to_reset fullSuccessMessage = _.template(successMessage)({
success_message = gettext("Successfully started task to rescore problem '<%= problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task.") problem_id: problemToReset
full_success_message = _.template(success_message)({problem_id: problem_to_reset}) });
error_message = gettext("Error starting a task to rescore problem '<%= problem_id %>'. Make sure that the problem identifier is complete and correct.") errorMessage = gettext("Error starting a task to reset attempts for all students on problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct."); // eslint-disable-line max-len
full_error_message = _.template(error_message)({problem_id: problem_to_reset}) fullErrorMessage = _.template(errorMessage)({
problem_id: problemToReset
$.ajax });
type: 'POST' return $.ajax({
dataType: 'json' type: 'POST',
url: @$btn_rescore_problem_all.data 'endpoint' dataType: 'json',
data: send_data url: studentadmin.$btn_reset_attempts_all.data('endpoint'),
success: @clear_errors_then -> alert full_success_message data: sendData,
error: std_ajax_err => @$request_response_error_all.text full_error_message success: studentadmin.clear_errors_then(function() {
else return alert(fullSuccessMessage); // eslint-disable-line no-alert
# Clear error messages if "Cancel" was chosen on confirmation alert }),
@clear_errors() error: statusAjaxError(function() {
return studentadmin.$request_response_error_all.text(fullErrorMessage);
# list task history for problem })
@$btn_task_history_all.click => });
send_data = } else {
problem_location_str: @$field_problem_select_all.val() return studentadmin.clear_errors();
}
if not send_data.problem_location_str });
return @$request_response_error_all.text gettext("Please enter a problem location.") this.$btn_rescore_problem_all.click(function() {
var confirmMessage, errorMessage, fullConfirmMessage,
$.ajax fullErrorMessage, fullSuccessMessage, problemToReset, sendData, successMessage;
type: 'POST' problemToReset = studentadmin.$field_problem_select_all.val();
dataType: 'json' if (!problemToReset) {
url: @$btn_task_history_all.data 'endpoint' return studentadmin.$request_response_error_all.text(
data: send_data gettext('Please enter a problem location.')
success: @clear_errors_then (data) => );
create_task_list_table @$table_task_history_all, data.tasks }
error: std_ajax_err => @$request_response_error_all.text gettext("Error listing task history for this student and problem.") confirmMessage = gettext("Rescore problem '<%- problem_id %>' for all students?");
fullConfirmMessage = _.template(confirmMessage)({
# wraps a function, but first clear the error displays problem_id: problemToReset
clear_errors_then: (cb) -> });
@$request_response_error_progress.empty() if (window.confirm(fullConfirmMessage)) { // eslint-disable-line no-alert
@$request_response_error_grade.empty() sendData = {
@$request_response_error_ee.empty() all_students: true,
@$request_response_error_all.empty() problem_to_reset: problemToReset
-> };
cb?.apply this, arguments successMessage = gettext("Successfully started task to rescore problem '<%- problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task."); // eslint-disable-line max-len
fullSuccessMessage = _.template(successMessage)({
problem_id: problemToReset
clear_errors: -> });
@$request_response_error_progress.empty() errorMessage = gettext("Error starting a task to rescore problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct."); // eslint-disable-line max-len
@$request_response_error_grade.empty() fullErrorMessage = _.template(errorMessage)({
@$request_response_error_ee.empty() problem_id: problemToReset
@$request_response_error_all.empty() });
return $.ajax({
# handler for when the section title is clicked. type: 'POST',
onClickTitle: -> @instructor_tasks.task_poller.start() dataType: 'json',
url: studentadmin.$btn_rescore_problem_all.data('endpoint'),
# handler for when the section is closed data: sendData,
onExit: -> @instructor_tasks.task_poller.stop() success: studentadmin.clear_errors_then(function() {
return alert(fullSuccessMessage); // eslint-disable-line no-alert
}),
# export for use error: statusAjaxError(function() {
# create parent namespaces if they do not already exist. return studentadmin.$request_response_error_all.text(fullErrorMessage);
_.defaults window, InstructorDashboard: {} })
_.defaults window.InstructorDashboard, sections: {} });
_.defaults window.InstructorDashboard.sections, } else {
StudentAdmin: StudentAdmin return studentadmin.clear_errors();
}
});
this.$btn_task_history_all.click(function() {
var sendData;
sendData = {
problem_location_str: studentadmin.$field_problem_select_all.val()
};
if (!sendData.problem_location_str) {
return studentadmin.$request_response_error_all.text(
gettext('Please enter a problem location.')
);
}
return $.ajax({
type: 'POST',
dataType: 'json',
url: studentadmin.$btn_task_history_all.data('endpoint'),
data: sendData,
success: studentadmin.clear_errors_then(function(data) {
return createTaskListTable(studentadmin.$table_task_history_all, data.tasks);
}),
error: statusAjaxError(function() {
return studentadmin.$request_response_error_all.text(
gettext('Error listing task history for this student and problem.')
);
})
});
});
}
StudentAdmin.prototype.clear_errors_then = function(cb) {
this.$request_err.empty();
this.$request_err_grade.empty();
this.$request_err_ee.empty();
this.$request_response_error_all.empty();
return function() {
return cb != null ? cb.apply(this, arguments) : void 0;
};
};
StudentAdmin.prototype.clear_errors = function() {
this.$request_err.empty();
this.$request_err_grade.empty();
this.$request_err_ee.empty();
return this.$request_response_error_all.empty();
};
StudentAdmin.prototype.onClickTitle = function() {
return this.instructor_tasks.task_poller.start();
};
StudentAdmin.prototype.onExit = function() {
return this.instructor_tasks.task_poller.stop();
};
return StudentAdmin;
}());
_.defaults(window, {
InstructorDashboard: {}
});
_.defaults(window.InstructorDashboard, {
sections: {}
});
_.defaults(window.InstructorDashboard.sections, {
StudentAdmin: this.StudentAdmin
});
}).call(this);
# Common utilities for instructor dashboard components. /* globals _, Logger, Slick, tinyMCE, InstructorDashboard */
# reverse arguments on common functions to enable (function() {
# better coffeescript with callbacks at the end. 'use strict';
plantTimeout = (ms, cb) -> setTimeout cb, ms var IntervalManager, KeywordValidator,
plantInterval = (ms, cb) -> setInterval cb, ms createEmailContentTable, createEmailMessageViews,
findAndAssert, pWrapper, plantInterval, plantTimeout,
sentToFormatter, setupCopyEmailButton, subjectFormatter,
# get jquery element and assert its existance unknownIfNullFormatter, unknownP,
find_and_assert = ($root, selector) -> anyOf = [].indexOf || function(item) {
item = $root.find selector var i, l;
if item.length != 1 for (i = 0, l = this.length; i < l; i++) {
console.error "element selection failed for '#{selector}' resulted in length #{item.length}" if (i in this && this[i] === item) {
throw "Failed Element Selection" return i;
else }
item }
return -1;
# standard ajax error wrapper };
#
# wraps a `handler` function so that first plantTimeout = function(ms, cb) {
# it prints basic error information to the console. return setTimeout(cb, ms);
@std_ajax_err = (handler) -> (jqXHR, textStatus, errorThrown) -> };
console.warn """ajax error
textStatus: #{textStatus} plantInterval = function(ms, cb) {
errorThrown: #{errorThrown}""" return setInterval(cb, ms);
handler.apply this, arguments };
findAndAssert = function($root, selector) {
# render a task list table to the DOM var item, msg;
# `$table_tasks` the $element in which to put the table item = $root.find(selector);
# `tasks_data` if (item.length !== 1) {
@create_task_list_table = ($table_tasks, tasks_data) -> msg = 'Failed Element Selection';
$table_tasks.empty() throw msg;
} else {
options = return item;
enableCellNavigation: true }
enableColumnReorder: false };
autoHeight: true
rowHeight: 100 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 forceFitColumns: true
};
columns = [ columns = [
id: 'task_type' {
field: 'task_type' id: 'task_type',
### field: 'task_type',
/*
Translators: a "Task" is a background process such as grading students or sending email Translators: a "Task" is a background process such as grading students or sending email
### */
name: gettext('Task Type')
name: gettext('Task Type'),
minWidth: 102 minWidth: 102
, }, {
id: 'task_input' id: 'task_input',
field: 'task_input' field: 'task_input',
### /*
Translators: a "Task" is a background process such as grading students or sending email Translators: a "Task" is a background process such as grading students or sending email
### */
name: gettext('Task inputs')
name: gettext('Task inputs'),
minWidth: 150 minWidth: 150
, }, {
id: 'task_id' id: 'task_id',
field: 'task_id' field: 'task_id',
### /*
Translators: a "Task" is a background process such as grading students or sending email Translators: a "Task" is a background process such as grading students or sending email
### */
name: gettext('Task ID')
name: gettext('Task ID'),
minWidth: 150 minWidth: 150
, }, {
id: 'requester' id: 'requester',
field: 'requester' field: 'requester',
### /*
Translators: a "Requester" is a username that requested a task such as sending email Translators: a "Requester" is a username that requested a task such as sending email
### */
name: gettext('Requester')
name: gettext('Requester'),
minWidth: 80 minWidth: 80
, }, {
id: 'created' id: 'created',
field: 'created' field: 'created',
### /*
Translators: A timestamp of when a task (eg, sending email) was submitted appears after this Translators: A timestamp of when a task (eg, sending email) was submitted appears after this
### */
name: gettext('Submitted')
name: gettext('Submitted'),
minWidth: 120 minWidth: 120
, }, {
id: 'duration_sec' id: 'duration_sec',
field: 'duration_sec' field: 'duration_sec',
### /*
Translators: The length of a task (eg, sending email) in seconds appears this Translators: The length of a task (eg, sending email) in seconds appears this
### */
name: gettext('Duration (sec)')
name: gettext('Duration (sec)'),
minWidth: 80 minWidth: 80
, }, {
id: 'task_state' id: 'task_state',
field: 'task_state' field: 'task_state',
### /*
Translators: The state (eg, "In progress") of a task (eg, sending email) appears after this. Translators: The state (eg, "In progress") of a task (eg, sending email) appears after this.
### */
name: gettext('State')
name: gettext('State'),
minWidth: 80 minWidth: 80
, }, {
id: 'status' id: 'status',
field: 'status' field: 'status',
### /*
Translators: a "Task" is a background process such as grading students or sending email Translators: a "Task" is a background process such as grading students or sending email
### */
name: gettext('Task Status')
name: gettext('Task Status'),
minWidth: 80 minWidth: 80
, }, {
id: 'task_message' id: 'task_message',
field: 'task_message' field: 'task_message',
### /*
Translators: a "Task" is a background process such as grading students or sending email Translators: a "Task" is a background process such as grading students or sending email
### */
name: gettext('Task Progress')
name: gettext('Task Progress'),
minWidth: 120 minWidth: 120
] }
];
table_data = tasks_data tableData = tasksData;
$tablePlaceholder = $('<div/>', {
$table_placeholder = $ '<div/>', class: 'slickgrid' class: 'slickgrid'
$table_tasks.append($table_placeholder) });
grid = new Slick.Grid($table_placeholder, table_data, columns, options) $tableTasks.append($tablePlaceholder);
return new Slick.Grid($tablePlaceholder, tableData, columns, options);
# Formats the subject field for email content history table };
subject_formatter = (row, cell, value, columnDef, dataContext) ->
if value is null then return gettext("An error occurred retrieving your email. Please try again later, and contact technical support if the problem persists.") subjectFormatter = function(row, cell, value) {
subject_text = $('<span>').text(value['subject']).html() var subjectText;
return edx.HtmlUtils.joinHtml( if (value === null) {
edx.HtmlUtils.HTML('<p><a href="#email_message_'), 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
value['id'], }
edx.HtmlUtils.HTML('" id="email_message_'), subjectText = $('<span>').text(value.subject).html();
value['id'], return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML(
edx.HtmlUtils.HTML('_trig">'), '<p><a href="#email_message_'), value.id, edx.HtmlUtils.HTML(
subject_text, '" id="email_message_'), value.id, edx.HtmlUtils.HTML('_trig">'),
edx.HtmlUtils.HTML('</a></p>'), subjectText, edx.HtmlUtils.HTML('</a></p>')
) );
};
p_wrapper = (value) ->
edx.HtmlUtils.joinHtml( pWrapper = function(value) {
edx.HtmlUtils.HTML('<p>'), return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<p>'), value, edx.HtmlUtils.HTML('</p>'));
value, };
edx.HtmlUtils.HTML('</p>'),
) unknownP = function() {
return pWrapper(gettext('Unknown'));
unknown_p = () -> };
p_wrapper(gettext('Unknown'))
sentToFormatter = function(row, cell, value) {
# Since sent_to is a json array, it needs some extra attention if (value === null) {
sent_to_formatter = (row, cell, value, columnDef, dataContext) -> return unknownP();
if value is null } else {
return unknown_p() return pWrapper(value.join(', '));
else }
return p_wrapper(value.join(", ")) };
# Formats the author, created, and number sent fields for the email content history table
unknown_if_null_formatter = (row, cell, value, columnDef, dataContext) ->
if value is null
return unknown_p()
else
return p_wrapper(value)
# Creates a table to display the content of bulk course emails
# sent in the past
create_email_content_table = ($table_emails, $table_emails_inner, email_data) ->
$table_emails_inner.empty()
$table_emails.show()
options =
enableCellNavigation: true
enableColumnReorder: false
autoHeight: true
rowHeight: 50
forceFitColumns: true
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 = [ columns = [
id: 'email' {
field: 'email' id: 'email',
name: gettext('Subject') field: 'email',
minWidth: 80 name: gettext('Subject'),
cssClass: "email-content-cell" minWidth: 80,
formatter: subject_formatter cssClass: 'email-content-cell',
, formatter: subjectFormatter
id: 'requester' }, {
field: 'requester' id: 'requester',
name: gettext('Sent By') field: 'requester',
minWidth: 80 name: gettext('Sent By'),
maxWidth: 100 minWidth: 80,
cssClass: "email-content-cell" maxWidth: 100,
formatter: unknown_if_null_formatter cssClass: 'email-content-cell',
, formatter: unknownIfNullFormatter
id: 'sent_to' }, {
field: 'sent_to' id: 'sent_to',
name: gettext('Sent To') field: 'sent_to',
minWidth: 80 name: gettext('Sent To'),
maxWidth: 100 minWidth: 80,
cssClass: "email-content-cell" maxWidth: 100,
formatter: sent_to_formatter cssClass: 'email-content-cell',
, formatter: sentToFormatter
id: 'created' }, {
field: 'created' id: 'created',
name: gettext('Time Sent') field: 'created',
minWidth: 80 name: gettext('Time Sent'),
cssClass: "email-content-cell" minWidth: 80,
formatter: unknown_if_null_formatter cssClass: 'email-content-cell',
, formatter: unknownIfNullFormatter
id: 'number_sent' }, {
field: 'number_sent' id: 'number_sent',
name: gettext('Number Sent') field: 'number_sent',
minwidth: 100 name: gettext('Number Sent'),
maxWidth: 150 minwidth: 100,
cssClass: "email-content-cell" maxWidth: 150,
formatter: unknown_if_null_formatter cssClass: 'email-content-cell',
, formatter: unknownIfNullFormatter
] }
];
table_data = email_data tableData = emailData;
$tablePlaceholder = $('<div/>', {
$table_placeholder = $ '<div/>', class: 'slickgrid' class: 'slickgrid'
$table_emails_inner.append($table_placeholder) });
grid = new Slick.Grid($table_placeholder, table_data, columns, options) $tableEmailsInner.append($tablePlaceholder);
$table_emails.append($('<br/>')) Slick.Grid($tablePlaceholder, tableData, columns, options);
return $tableEmails.append($('<br/>'));
# Creates the modal windows linked to each email in the email history };
# Displayed when instructor clicks an email's subject in the content history table
create_email_message_views = ($messages_wrapper, emails) -> createEmailMessageViews = function($messagesWrapper, emails) {
$messages_wrapper.empty() var $closeButton, $created, $emailContent, $emailContentHeader,
for email_info in emails $emailHeader, $emailWrapper, $message, $messageContent,
$requester, $sentTo, $subject, emailId, emailInfo, interpolateHeader, i, len;
# If some error occured, bail out $messagesWrapper.empty();
if !email_info.email then return for (i = 0, len = emails.length; i < len; i++) {
emailInfo = emails[i];
# Create hidden section for modal window if (!emailInfo.email) {
email_id = email_info.email['id'] return;
$message_content = $('<section>', "aria-hidden": "true", class: "modal email-modal", id: "email_message_" + email_id) }
$email_wrapper = $ '<div>', class: 'inner-wrapper email-content-wrapper' emailId = emailInfo.email.id;
$email_header = $ '<div>', class: 'email-content-header' $messageContent = $('<section>', {
'aria-hidden': 'true',
# Add copy email body button class: 'modal email-modal',
$email_header.append($('<input>', type: "button", name: "copy-email-body-text", value: gettext("Copy Email To Editor"), id: "copy_email_" + email_id)) id: 'email_message_' + emailId
});
$close_button = $ '<a>', href: '#', class: "close-modal" $emailWrapper = $('<div>', {
$close_button.append($('<i>', class: 'icon fa fa-times')) class: 'inner-wrapper email-content-wrapper'
$email_header.append($close_button) });
$emailHeader = $('<div>', {
# HTML escape things class: 'email-content-header'
interpolate_header = (title, value) -> });
edx.HtmlUtils.setHtml( $emailHeader.append($('<input>', {
$('<h2>', class: 'message-bold'), type: 'button',
edx.HtmlUtils.joinHtml( name: 'copy-email-body-text',
edx.HtmlUtils.HTML('<em>'), value: gettext('Copy Email To Editor'),
title id: 'copy_email_' + emailId
edx.HtmlUtils.HTML('</em>'), }));
value, $closeButton = $('<a>', {
) href: '#',
) class: 'close-modal'
$subject = interpolate_header(gettext('Subject:'), email_info.email['subject']) });
$requester = interpolate_header(gettext('Sent By:'), email_info.requester) $closeButton.append($('<i>', {
$created = interpolate_header(gettext('Time Sent:'), email_info.created) class: 'icon fa fa-times'
$sent_to = interpolate_header(gettext('Sent To:'), email_info.sent_to.join(", ")) }));
$email_header.append($subject) $emailHeader.append($closeButton);
$email_header.append($requester) interpolateHeader = function(title, value) {
$email_header.append($created) return edx.HtmlUtils.setHtml($('<h2>', {
$email_header.append($sent_to) class: 'message-bold'
$email_wrapper.append($email_header) }), edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<em>'), title, edx.HtmlUtils.HTML('</em>'), value));
};
$email_wrapper.append($('<hr>')) $subject = interpolateHeader(gettext('Subject:'), emailInfo.email.subject);
$requester = interpolateHeader(gettext('Sent By:'), emailInfo.requester);
# Last, add email content section $created = interpolateHeader(gettext('Time Sent:'), emailInfo.created);
$email_content = $ '<div>', class: 'email-content-message' $sentTo = interpolateHeader(gettext('Sent To:'), emailInfo.sentTo.join(', '));
$email_content_header = edx.HtmlUtils.setHtml( $emailHeader.append($subject);
$('<h2>', class: "message-bold"), $emailHeader.append($requester);
edx.HtmlUtils.joinHtml( $emailHeader.append($created);
edx.HtmlUtils.HTML('<em>'), $emailHeader.append($sentTo);
gettext("Message:"), $emailWrapper.append($emailHeader);
edx.HtmlUtils.HTML('</em>'), $emailWrapper.append($('<hr>'));
) $emailContent = $('<div>', {
) class: 'email-content-message'
$email_content.append($email_content_header) });
$message = edx.HtmlUtils.setHtml( $emailContentHeader = edx.HtmlUtils.setHtml($('<h2>', {
$('<div>'), class: 'message-bold'
edx.HtmlUtils.HTML(email_info.email['html_message']) }), edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML('<em>'), gettext('Message:'), edx.HtmlUtils.HTML('</em>')));
) $emailContent.append($emailContentHeader);
$email_content.append($message) $message = edx.HtmlUtils.setHtml($('<div>'), edx.HtmlUtils.HTML(emailInfo.email.html_message));
$email_wrapper.append($email_content) $emailContent.append($message);
$emailWrapper.append($emailContent);
$message_content.append($email_wrapper) $messageContent.append($emailWrapper);
$messages_wrapper.append($message_content) $messagesWrapper.append($messageContent);
$('#email_message_' + emailInfo.email.id + '_trig').leanModal({
# Setup buttons to open modal window and copy an email message closeButton: '.close-modal',
$('#email_message_' + email_info.email['id'] + '_trig').leanModal({closeButton: ".close-modal", copyEmailButton: "#copy_email_" + email_id}) copyEmailButton: '#copy_email_' + emailId
setup_copy_email_button(email_id, email_info.email['html_message'], email_info.email['subject']) });
setupCopyEmailButton(emailId, emailInfo.email.html_message, emailInfo.email.subject);
# Helper method to set click handler for modal copy email button }
setup_copy_email_button = (email_id, html_message, subject) -> };
$("#copy_email_" + email_id).click =>
editor = tinyMCE.get("mce_0") setupCopyEmailButton = function(emailId, htmlMessage, subject) {
editor.setContent(html_message) return $('#copy_email_' + emailId).click(function() {
$('#id_subject').val(subject) var editor;
editor = tinyMCE.get('mce_0');
editor.setContent(htmlMessage);
# Helper class for managing the execution of interval tasks. return $('#id_subject').val(subject);
# Handles pausing and restarting. });
class IntervalManager };
# Create a manager which will call `fn`
# after a call to .start every `ms` milliseconds. IntervalManager = (function() {
constructor: (@ms, @fn) -> function intervalManager(ms, fn) {
@intervalID = null this.ms = ms;
this.fn = fn;
# Start or restart firing every `ms` milliseconds. this.intervalID = null;
start: -> }
@fn()
if @intervalID is null
@intervalID = setInterval @fn, @ms
# Pause firing.
stop: ->
clearInterval @intervalID
@intervalID = null
class @PendingInstructorTasks
### Pending Instructor Tasks Section ####
constructor: (@$section) ->
# Currently running tasks
@$running_tasks_section = find_and_assert @$section, ".running-tasks-section"
@$table_running_tasks = find_and_assert @$section, ".running-tasks-table"
@$no_tasks_message = find_and_assert @$section, ".no-pending-tasks-message"
# start polling for task list
# if the list is in the DOM
if @$table_running_tasks.length
# reload every 20 seconds.
TASK_LIST_POLL_INTERVAL = 20000
@reload_running_tasks_list()
@task_poller = new IntervalManager(TASK_LIST_POLL_INTERVAL, => @reload_running_tasks_list())
# Populate the running tasks list
reload_running_tasks_list: =>
list_endpoint = @$table_running_tasks.data 'endpoint'
$.ajax
type: 'POST'
dataType: 'json'
url: list_endpoint
success: (data) =>
if data.tasks.length
create_task_list_table @$table_running_tasks, data.tasks
@$no_tasks_message.hide()
@$running_tasks_section.show()
else
console.log "No pending tasks to display"
@$running_tasks_section.hide()
@$no_tasks_message.empty()
@$no_tasks_message.append($('<p>').text(gettext("No tasks currently running.")))
@$no_tasks_message.show()
error: std_ajax_err => console.error "Error finding pending tasks to display"
### /Pending Instructor Tasks Section ####
class KeywordValidator
@keyword_regex = /%%+[^%]+%%/g
@keywords = ['%%USER_ID%%', '%%USER_FULLNAME%%', '%%COURSE_DISPLAY_NAME%%', '%%COURSE_END_DATE%%']
@validate_string: (string) =>
regex_match = string.match(@keyword_regex)
found_keywords = if regex_match == null then [] else regex_match
invalid_keywords = []
is_valid = true
keywords = @keywords
for found_keyword in found_keywords
do (found_keyword) ->
if found_keyword not in keywords
invalid_keywords.push found_keyword
if invalid_keywords.length != 0
is_valid = false
return { intervalManager.prototype.start = function() {
is_valid: is_valid, this.fn();
invalid_keywords: invalid_keywords 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;
};
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) {
ths.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();
}
}
});
};
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();
});
}
class ReportDownloads ReportDownloads.prototype.reload_report_downloads = function() {
### Report Downloads -- links expire quickly, so we refresh every 5 mins #### var endpoint,
constructor: (@$section) -> ths = this;
endpoint = this.$report_downloads_table.data('endpoint');
@$report_downloads_table = @$section.find ".report-downloads-table" return $.ajax({
type: 'POST',
POLL_INTERVAL = 20000 # 20 seconds, just like the "pending instructor tasks" table dataType: 'json',
@downloads_poller = new window.InstructorDashboard.util.IntervalManager( url: endpoint,
POLL_INTERVAL, => @reload_report_downloads() success: function(data) {
) if (data.downloads.length) {
return ths.create_report_downloads_table(data.downloads);
reload_report_downloads: -> } else {
endpoint = @$report_downloads_table.data 'endpoint' return false;
$.ajax }
type: 'POST' }
dataType: 'json' });
url: endpoint };
success: (data) =>
if data.downloads.length ReportDownloads.prototype.create_report_downloads_table = function(reportDownloadsData) {
@create_report_downloads_table data.downloads var $tablePlaceholder, columns, grid, options;
else this.$report_downloads_table.empty();
console.log "No reports ready for download" options = {
error: (std_ajax_err) => console.error "Error finding report downloads" enableCellNavigation: true,
enableColumnReorder: false,
create_report_downloads_table: (report_downloads_data) -> rowHeight: 30,
@$report_downloads_table.empty()
options =
enableCellNavigation: true
enableColumnReorder: false
rowHeight: 30
forceFitColumns: true forceFitColumns: true
};
columns = [ columns = [
id: 'link' {
field: 'link' id: 'link',
name: gettext('File Name') field: 'link',
toolTip: gettext("Links are generated on demand and expire within 5 minutes due to the sensitive nature of student information.") name: gettext('File Name'),
sortable: false 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
minWidth: 150 sortable: false,
cssClass: "file-download-link" minWidth: 150,
formatter: (row, cell, value, columnDef, dataContext) -> cssClass: 'file-download-link',
edx.HtmlUtils.joinHtml( formatter: function(row, cell, value, columnDef, dataContext) {
edx.HtmlUtils.HTML('<a target="_blank" href="'), return edx.HtmlUtils.joinHtml(edx.HtmlUtils.HTML(
dataContext['url'], '<a target="_blank" href="'), dataContext.url,
edx.HtmlUtils.HTML('">'), edx.HtmlUtils.HTML('">'), dataContext.name,
dataContext['name'], edx.HtmlUtils.HTML('</a>'));
edx.HtmlUtils.HTML('</a>') }
) }
] ];
$tablePlaceholder = $('<div/>', {
$table_placeholder = $ '<div/>', class: 'slickgrid' class: 'slickgrid'
@$report_downloads_table.append($table_placeholder) });
grid = new Slick.Grid($table_placeholder, report_downloads_data, columns, options) this.$report_downloads_table.append($tablePlaceholder);
grid.onClick.subscribe( grid = new Slick.Grid($tablePlaceholder, reportDownloadsData, columns, options);
(event) => grid.onClick.subscribe(function(event) {
report_url = event.target.href var reportUrl;
if report_url reportUrl = event.target.href;
# Record that the user requested to download a report if (reportUrl) {
Logger.log('edx.instructor.report.downloaded', { return Logger.log('edx.instructor.report.downloaded', {
report_url: report_url report_url: reportUrl
}) });
) }
grid.autosizeColumns() return Logger.log('edx.instructor.report.downloaded', {
report_url: reportUrl
});
# export for use });
# create parent namespaces if they do not already exist. return grid.autosizeColumns();
# abort if underscore can not be found. };
if _?
_.defaults window, InstructorDashboard: {} return ReportDownloads;
window.InstructorDashboard.util = }());
plantTimeout: plantTimeout
plantInterval: plantInterval if (typeof _ !== 'undefined' && _ !== null) {
std_ajax_err: std_ajax_err _.defaults(window, {
IntervalManager: IntervalManager InstructorDashboard: {}
create_task_list_table: create_task_list_table });
create_email_content_table: create_email_content_table window.InstructorDashboard.util = {
create_email_message_views: create_email_message_views plantTimeout: plantTimeout,
PendingInstructorTasks: PendingInstructorTasks plantInterval: plantInterval,
KeywordValidator: KeywordValidator statusAjaxError: this.statusAjaxError,
ReportDownloads: ReportDownloads IntervalManager: IntervalManager,
createTaskListTable: this.createTaskListTable,
createEmailContentTable: createEmailContentTable,
createEmailMessageViews: createEmailMessageViews,
PendingInstructorTasks: this.PendingInstructorTasks,
KeywordValidator: KeywordValidator,
ReportDownloads: this.ReportDownloads
};
}
}).call(this);
/* global define */ /* global define */
define(['jquery', define(['jquery',
'coffee/src/instructor_dashboard/data_download', 'js/instructor_dashboard/data_download',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'slick.grid'], 'slick.grid'],
function($, DataDownload, AjaxHelpers) { function($, DataDownload, AjaxHelpers) {
......
// Generated by CoffeeScript 1.6.1
(function() {
'use strict';
describe('AutoEnrollment', function() {
beforeEach(function() {
loadFixtures('../../fixtures/autoenrollment.html');
this.autoenrollment = new window.AutoEnrollmentViaCsv($('.auto_enroll_csv'));
return this.autoenrollment;
});
it('binds the ajax call and the result will be success', function() {
var submitCallback;
spyOn($, 'ajax').and.callFake(function(params) {
params.success({
row_errors: [],
general_errors: [],
warnings: []
});
return {
always: function() {}
};
});
this.autoenrollment.render_notification_view = jasmine.createSpy(
'render_notification_view(type, title, message, details) spy').and.callFake(function() {
return '<div><div class="message message-confirmation"><h3 class="message-title">Success</h3><div class="message-copy"><p>All accounts were created successfully.</p></div></div><div>'; // eslint-disable-line max-len
});
submitCallback = jasmine.createSpy().and.returnValue();
this.autoenrollment.$student_enrollment_form.submit(submitCallback);
this.autoenrollment.$enrollment_signup_button.click();
expect($('.results .message-copy').text()).toEqual('All accounts were created successfully.');
return expect(submitCallback).toHaveBeenCalled();
});
it('binds the ajax call and the result will be error', function() {
var submitCallback;
spyOn($, 'ajax').and.callFake(function(params) {
params.success({
row_errors: [
{
username: 'testuser1',
email: 'testemail1@email.com',
response: 'Username already exists'
}
],
general_errors: [
{
response: 'cannot read the line 2'
}
],
warnings: []
});
return {
always: function() {}
};
});
this.autoenrollment.render_notification_view = jasmine.createSpy(
'render_notification_view(type, title, message, details) spy').and.callFake(function() {
return '<div><div class="message message-error"><h3 class="message-title">Errors</h3><div class="message-copy"><p>The following errors were generated:</p><ul class="list-summary summary-items"><li class="summary-item">cannot read the line 2</li><li class="summary-item">testuser1 (testemail1@email.com): (Username already exists)</li></ul></div></div></div>'; // eslint-disable-line max-len
});
submitCallback = jasmine.createSpy().and.returnValue();
this.autoenrollment.$student_enrollment_form.submit(submitCallback);
this.autoenrollment.$enrollment_signup_button.click();
expect($('.results .list-summary').text()).toEqual('cannot read the line 2testuser1 (testemail1@email.com): (Username already exists)'); // eslint-disable-line max-len
return expect(submitCallback).toHaveBeenCalled();
});
return it('binds the ajax call and the result will be warnings', function() {
var submitCallback;
spyOn($, 'ajax').and.callFake(function(params) {
params.success({
row_errors: [],
general_errors: [],
warnings: [
{
username: 'user1',
email: 'user1email',
response: 'email is in valid'
}
]
});
return {
always: function() {}
};
});
this.autoenrollment.render_notification_view = jasmine.createSpy(
'render_notification_view(type, title, message, details) spy').and.callFake(function() {
return '<div><div class="message message-warning"><h3 class="message-title">Warnings</h3><div class="message-copy"><p>The following warnings were generated:</p><ul class="list-summary summary-items"><li class="summary-item">user1 (user1email): (email is in valid)</li></ul></div></div></div>'; // eslint-disable-line max-len
});
submitCallback = jasmine.createSpy().and.returnValue();
this.autoenrollment.$student_enrollment_form.submit(submitCallback);
this.autoenrollment.$enrollment_signup_button.click();
expect($('.results .list-summary').text()).toEqual('user1 (user1email): (email is in valid)');
return expect(submitCallback).toHaveBeenCalled();
});
});
}).call(this);
(function() {
'use strict';
describe('Bulk Email Queueing', function() {
beforeEach(function() {
var testBody, testSubject;
testSubject = 'Test Subject';
testBody = 'Hello, World! This is a test email message!';
loadFixtures('../../fixtures/send_email.html');
this.send_email = new window.SendEmail($('.send-email'));
this.send_email.$subject.val(testSubject);
this.send_email.$send_to.first().prop('checked', true);
this.send_email.$emailEditor = {
save: function() {
return {
data: testBody
};
}
};
this.ajax_params = {
type: 'POST',
dataType: 'json',
url: void 0,
data: {
action: 'send',
send_to: JSON.stringify([this.send_email.$send_to.first().val()]),
subject: testSubject,
message: testBody
},
success: jasmine.any(Function),
error: jasmine.any(Function)
};
return this.ajax_params;
});
it('cannot send an email with no target', function() {
var target, i, len, ref;
spyOn(window, 'alert');
spyOn($, 'ajax');
ref = this.send_email.$send_to;
for (i = 0, len = ref.length; i < len; i++) {
target = ref[i];
target.checked = false;
}
this.send_email.$btn_send.click();
expect(window.alert).toHaveBeenCalledWith('Your message must have at least one target.');
return expect($.ajax).not.toHaveBeenCalled();
});
it('cannot send an email with no subject', function() {
spyOn(window, 'alert');
spyOn($, 'ajax');
this.send_email.$subject.val('');
this.send_email.$btn_send.click();
expect(window.alert).toHaveBeenCalledWith('Your message must have a subject.');
return expect($.ajax).not.toHaveBeenCalled();
});
it('cannot send an email with no message', function() {
spyOn(window, 'alert');
spyOn($, 'ajax');
this.send_email.$emailEditor = {
save: function() {
return {
data: ''
};
}
};
this.send_email.$btn_send.click();
expect(window.alert).toHaveBeenCalledWith('Your message cannot be blank.');
return expect($.ajax).not.toHaveBeenCalled();
});
it('can send a simple message to a single target', function() {
spyOn($, 'ajax').and.callFake(function(params) {
return params.success();
});
this.send_email.$btn_send.click();
expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.'); // eslint-disable-line max-len
return expect($.ajax).toHaveBeenCalledWith(this.ajax_params);
});
it('can send a simple message to a multiple targets', function() {
var target, i, len, ref;
spyOn($, 'ajax').and.callFake(function(params) {
return params.success();
});
this.ajax_params.data.send_to = JSON.stringify((function() {
var j, len1, ref1, results;
ref1 = this.send_email.$send_to;
results = [];
for (j = 0, len1 = ref.length; j < len1; j++) {
target = ref1[j];
results.push(target.value);
}
return results;
}).call(this));
ref = this.send_email.$send_to;
for (i = 0, len = ref.length; i < len; i++) {
target = ref[i];
target.checked = true;
}
this.send_email.$btn_send.click();
expect($('.msg-confirm').text()).toEqual('Your email message was successfully queued for sending. In courses with a large number of learners, email messages to learners might take up to an hour to be sent.'); // eslint-disable-line max-len
return expect($.ajax).toHaveBeenCalledWith(this.ajax_params);
});
it('can handle an error result from the bulk email api', function() {
spyOn($, 'ajax').and.callFake(function(params) {
return params.error();
});
spyOn(console, 'warn');
this.send_email.$btn_send.click();
return expect($('.request-response-error').text()).toEqual('Error sending email.');
});
it('selecting all learners disables cohort selections', function() {
this.send_email.$send_to.filter("[value='learners']").click();
this.send_email.$cohort_targets.each(function() {
return expect(this.disabled).toBe(true);
});
this.send_email.$send_to.filter("[value='learners']").click();
return this.send_email.$cohort_targets.each(function() {
return expect(this.disabled).toBe(false);
});
});
return it('selected targets are listed after "send to:"', function() {
this.send_email.$send_to.click();
return $('input[name="send_to"]:checked+label').each(function() {
return expect($('.send_to_list'.text())).toContain(this.innerText.replace(/\s*\n.*/g, ''));
});
});
});
}).call(this);
define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'], /* globals _, interpolate_text, statusAjaxError, PendingInstructorTasks, createTaskListTable*/
define(['jquery', 'js/instructor_dashboard/student_admin', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'],
function($, StudentAdmin, AjaxHelpers) { function($, StudentAdmin, AjaxHelpers) {
// 'coffee/src/instructor_dashboard/student_admin' // 'js/instructor_dashboard/student_admin'
'use strict'; 'use strict';
describe('edx.instructor_dashboard.student_admin.StudentAdmin', function() { describe('edx.instructor_dashboard.student_admin.StudentAdmin', function() {
var studentadmin, dashboard_api_url, unique_student_identifier, alert_msg; var studentadmin, dashboardApiUrl, uniqStudentIdentifier, alertMsg;
beforeEach(function() { beforeEach(function() {
loadFixtures('js/fixtures/instructor_dashboard/student_admin.html'); loadFixtures('js/fixtures/instructor_dashboard/student_admin.html');
window.InstructorDashboard = {}; window.InstructorDashboard = {};
window.InstructorDashboard.util = { window.InstructorDashboard.util = {
std_ajax_err: std_ajax_err, statusAjaxError: statusAjaxError,
PendingInstructorTasks: PendingInstructorTasks, PendingInstructorTasks: PendingInstructorTasks,
create_task_list_table: create_task_list_table createTaskListTable: createTaskListTable
}; };
studentadmin = new window.StudentAdmin($('#student_admin')); studentadmin = new window.StudentAdmin($('#student_admin'));
dashboard_api_url = '/courses/PU/FSc/2014_T4/instructor/api'; dashboardApiUrl = '/courses/PU/FSc/2014_T4/instructor/api';
unique_student_identifier = 'test@example.com'; uniqStudentIdentifier = 'test@example.com';
alert_msg = ''; alertMsg = '';
spyOn(window, 'alert').and.callFake(function(message) { spyOn(window, 'alert').and.callFake(function(message) {
alert_msg = message; alertMsg = message;
}); });
}); });
it('initiates resetting of entrance exam when button is clicked', function() { it('initiates resetting of entrance exam when button is clicked', function() {
studentadmin.$btn_reset_entrance_exam_attempts.click(); var successMessage = gettext("Entrance exam attempts is being reset for student '{student_id}'.");
// expect error to be shown since student identifier is not set var fullSuccessMessage = interpolate_text(successMessage, {
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext('Please enter a student email address or username.')); student_id: uniqStudentIdentifier
var success_message = gettext("Entrance exam attempts is being reset for student '{student_id}'.");
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
}); });
var url = dashboardApiUrl + '/reset_student_attempts_for_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_reset_entrance_exam_attempts.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier, unique_student_identifier: uniqStudentIdentifier,
delete_module: false delete_module: false
}); });
var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam';
studentadmin.$btn_reset_entrance_exam_attempts.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_err_ee.text()).toEqual(
gettext('Please enter a student email address or username.')
);
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_reset_entrance_exam_attempts.click();
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate a success response from the server // Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
message: full_success_message message: fullSuccessMessage
}); });
expect(alert_msg).toEqual(full_success_message); expect(alertMsg).toEqual(fullSuccessMessage);
}); });
it('shows an error when resetting of entrance exam fails', function() { it('shows an error when resetting of entrance exam fails', function() {
var url = dashboardApiUrl + '/reset_student_attempts_for_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_reset_entrance_exam_attempts.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier, unique_student_identifier: uniqStudentIdentifier,
delete_module: false delete_module: false
}); });
var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; var errorMessage = gettext("Error resetting entrance exam attempts for student '{student_id}'. Make sure student identifier is correct."); // eslint-disable-line max-len
var fullErrorMessage = interpolate_text(errorMessage, {
student_id: uniqStudentIdentifier
});
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_reset_entrance_exam_attempts.click();
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate an error response from the server // Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400, {}); AjaxHelpers.respondWithError(requests, 400, {});
var error_message = gettext("Error resetting entrance exam attempts for student '{student_id}'. Make sure student identifier is correct."); expect(studentadmin.$request_err_ee.text()).toEqual(fullErrorMessage);
var full_error_message = interpolate_text(error_message, {
student_id: unique_student_identifier
});
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
}); });
it('initiates rescoring of the entrance exam when the button is clicked', function() { it('initiates rescoring of the entrance exam when the button is clicked', function() {
studentadmin.$btn_rescore_entrance_exam.click(); var successMessage = gettext("Started entrance exam rescore task for student '{student_id}'." +
// expect error to be shown since student identifier is not set " Click the 'Show Background Task History for Student' button to see the status of the task."); // eslint-disable-line max-len
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext('Please enter a student email address or username.')); var fullSuccessMessage = interpolate_text(successMessage, {
student_id: uniqStudentIdentifier
var success_message = gettext("Started entrance exam rescore task for student '{student_id}'." +
" Click the 'Show Background Task History for Student' button to see the status of the task.");
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
}); });
var url = dashboardApiUrl + '/rescore_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_rescore_entrance_exam.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier unique_student_identifier: uniqStudentIdentifier
}); });
var url = dashboard_api_url + '/rescore_entrance_exam';
studentadmin.$btn_rescore_entrance_exam.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_err_ee.text()).toEqual(
gettext('Please enter a student email address or username.')
);
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_rescore_entrance_exam.click();
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate a success response from the server // Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
message: full_success_message message: fullSuccessMessage
}); });
expect(alert_msg).toEqual(full_success_message); expect(alertMsg).toEqual(fullSuccessMessage);
}); });
it('shows an error when entrance exam rescoring fails', function() { it('shows an error when entrance exam rescoring fails', function() {
var url = dashboardApiUrl + '/rescore_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_rescore_entrance_exam.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier unique_student_identifier: uniqStudentIdentifier
});
var errorMessage = gettext(
"Error starting a task to rescore entrance exam for student '{student_id}'." +
' Make sure that entrance exam has problems in it and student identifier is correct.'
);
var fullErrorMessage = interpolate_text(errorMessage, {
student_id: uniqStudentIdentifier
}); });
var url = dashboard_api_url + '/rescore_entrance_exam';
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_rescore_entrance_exam.click();
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate an error response from the server // Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400, {}); AjaxHelpers.respondWithError(requests, 400, {});
var error_message = gettext("Error starting a task to rescore entrance exam for student '{student_id}'." + expect(studentadmin.$request_err_ee.text()).toEqual(fullErrorMessage);
' Make sure that entrance exam has problems in it and student identifier is correct.');
var full_error_message = interpolate_text(error_message, {
student_id: unique_student_identifier
});
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
}); });
it('initiates skip entrance exam when button is clicked', function() { it('initiates skip entrance exam when button is clicked', function() {
studentadmin.$btn_skip_entrance_exam.click(); var successMessage = "This student ('{student_id}') will skip the entrance exam.";
// expect error to be shown since student identifier is not set var fullSuccessMessage = interpolate_text(successMessage, {
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Enter a student's username or email address.")); student_id: uniqStudentIdentifier
var success_message = "This student ('{student_id}') will skip the entrance exam.";
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
}); });
var url = dashboardApiUrl + '/mark_student_can_skip_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier); studentadmin.$btn_skip_entrance_exam.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_err_ee.text()).toEqual(
gettext("Enter a student's username or email address.")
);
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_skip_entrance_exam.click(); studentadmin.$btn_skip_entrance_exam.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var url = dashboard_api_url + '/mark_student_can_skip_entrance_exam';
AjaxHelpers.expectRequest(requests, 'POST', url, $.param({ AjaxHelpers.expectRequest(requests, 'POST', url, $.param({
'unique_student_identifier': unique_student_identifier unique_student_identifier: uniqStudentIdentifier
})); }));
// Simulate a success response from the server // Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
message: full_success_message message: fullSuccessMessage
}); });
expect(alert_msg).toEqual(full_success_message); expect(alertMsg).toEqual(fullSuccessMessage);
}); });
it('shows an error when skip entrance exam fails', function() { it('shows an error when skip entrance exam fails', function() {
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier); var url = dashboardApiUrl + '/mark_student_can_skip_entrance_exam';
var errorMessage = "An error occurred. Make sure that the student's username or email address is correct and try again."; // eslint-disable-line max-len
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_skip_entrance_exam.click(); studentadmin.$btn_skip_entrance_exam.click();
// Verify that the client contacts the server to start instructor task
var url = dashboard_api_url + '/mark_student_can_skip_entrance_exam';
AjaxHelpers.expectRequest(requests, 'POST', url, $.param({ AjaxHelpers.expectRequest(requests, 'POST', url, $.param({
'unique_student_identifier': unique_student_identifier unique_student_identifier: uniqStudentIdentifier
})); }));
// Simulate an error response from the server // Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400, {}); AjaxHelpers.respondWithError(requests, 400, {});
var error_message = "An error occurred. Make sure that the student's username or email address is correct and try again."; expect(studentadmin.$request_err_ee.text()).toEqual(errorMessage);
expect(studentadmin.$request_response_error_ee.text()).toEqual(error_message);
}); });
it('initiates delete student state for entrance exam when button is clicked', function() { it('initiates delete student state for entrance exam when button is clicked', function() {
studentadmin.$btn_delete_entrance_exam_state.click(); var successMessage = gettext("Entrance exam state is being deleted for student '{student_id}'.");
// expect error to be shown since student identifier is not set var fullSuccessMessage = interpolate_text(successMessage, {
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext('Please enter a student email address or username.')); student_id: uniqStudentIdentifier
var success_message = gettext("Entrance exam state is being deleted for student '{student_id}'.");
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
}); });
var url = dashboardApiUrl + '/reset_student_attempts_for_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_delete_entrance_exam_state.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier, unique_student_identifier: uniqStudentIdentifier,
delete_module: true delete_module: true
}); });
var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; studentadmin.$btn_delete_entrance_exam_state.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_err_ee.text()).toEqual(
gettext('Please enter a student email address or username.')
);
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_delete_entrance_exam_state.click();
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate a success response from the server // Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
message: full_success_message message: fullSuccessMessage
}); });
expect(alert_msg).toEqual(full_success_message); expect(alertMsg).toEqual(fullSuccessMessage);
}); });
it('shows an error when delete student state for entrance exam fails', function() { it('shows an error when delete student state for entrance exam fails', function() {
var url = dashboardApiUrl + '/reset_student_attempts_for_entrance_exam';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_delete_entrance_exam_state.click();
// Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier, unique_student_identifier: uniqStudentIdentifier,
delete_module: true delete_module: true
}); });
var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; var errorMessage = gettext("Error deleting entrance exam state for student '{student_id}'. " +
'Make sure student identifier is correct.'); // eslint-disable-line max-len
var fullErrorMessage = interpolate_text(errorMessage, {
student_id: uniqStudentIdentifier
});
studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_delete_entrance_exam_state.click();
// Verify that the client contacts the server to start instructor task
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate an error response from the server // Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400, {}); AjaxHelpers.respondWithError(requests, 400, {});
var error_message = gettext("Error deleting entrance exam state for student '{student_id}'. " + expect(studentadmin.$request_err_ee.text()).toEqual(fullErrorMessage);
'Make sure student identifier is correct.');
var full_error_message = interpolate_text(error_message, {
student_id: unique_student_identifier
});
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
}); });
it('initiates listing of entrance exam task history when button is clicked', function() { it('initiates listing of entrance exam task history when button is clicked', function() {
studentadmin.$btn_entrance_exam_task_history.click(); var url = dashboardApiUrl + '/list_entrance_exam_instructor_tasks';
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Enter a student's username or email address."));
var success_message = gettext("Entrance exam state is being deleted for student '{student_id}'.");
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
});
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var params = $.param({
unique_student_identifier: uniqStudentIdentifier
});
studentadmin.$btn_entrance_exam_task_history.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_err_ee.text()).toEqual(
gettext("Enter a student's username or email address.")
);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier); studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_entrance_exam_task_history.click(); studentadmin.$btn_entrance_exam_task_history.click();
// Verify that the client contacts the server to start instructor task // Verify that the client contacts the server to start instructor task
var params = $.param({
unique_student_identifier: unique_student_identifier
});
var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks';
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate a success response from the server // Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
'tasks': [ tasks: [
{ {
'status': 'Incomplete', status: 'Incomplete',
'task_type': 'rescore_problem', task_type: 'rescore_problem',
'task_id': '9955d413-eac1-441f-978d-27c60dd1c946', task_id: '9955d413-eac1-441f-978d-27c60dd1c946',
'created': '2015-02-19T10:59:01+00:00', created: '2015-02-19T10:59:01+00:00',
'task_input': '{"entrance_exam_url": "i4x://PU/FSc/chapter/d2204197cce443c4a0d5c852d4e7f638", "student": "audit"}', task_input: '{"entrance_exam_url": "i4x://PU/FSc/chapter/d2204197cce443c4a0d5c852d4e7f638", "student": "audit"}', // eslint-disable-line max-len
'duration_sec': 'unknown', duration_sec: 'unknown',
'task_message': 'No status information available', task_message: 'No status information available',
'requester': 'staff', requester: 'staff',
'task_state': 'QUEUING' task_state: 'QUEUING'
} }
] ]
}); });
...@@ -270,26 +285,26 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'edx-ui-toolk ...@@ -270,26 +285,26 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'edx-ui-toolk
}); });
it('shows an error when listing entrance exam task history fails', function() { it('shows an error when listing entrance exam task history fails', function() {
var url = dashboardApiUrl + '/list_entrance_exam_instructor_tasks';
// Spy on AJAX requests // Spy on AJAX requests
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier);
studentadmin.$btn_entrance_exam_task_history.click();
// Verify that the client contacts the server to start instructor task
var params = $.param({ var params = $.param({
unique_student_identifier: unique_student_identifier unique_student_identifier: uniqStudentIdentifier
});
var errorMessage = gettext("Error getting entrance exam task history for student '{student_id}'. " +
'Make sure student identifier is correct.');
var fullErrorMessage = interpolate_text(errorMessage, {
student_id: uniqStudentIdentifier
}); });
var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks'; studentadmin.$field_exam_grade.val(uniqStudentIdentifier);
studentadmin.$btn_entrance_exam_task_history.click();
// Verify that the client contacts the server to start instructor task
AjaxHelpers.expectPostRequest(requests, url, params); AjaxHelpers.expectPostRequest(requests, url, params);
// Simulate an error response from the server // Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400, {}); AjaxHelpers.respondWithError(requests, 400, {});
var error_message = gettext("Error getting entrance exam task history for student '{student_id}'. " + expect(studentadmin.$request_err_ee.text()).toEqual(fullErrorMessage);
'Make sure student identifier is correct.');
var full_error_message = interpolate_text(error_message, {
student_id: unique_student_identifier
});
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
}); });
}); });
}); });
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
mathjax: '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len mathjax: '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len
'youtube': '//www.youtube.com/player_api?noext', 'youtube': '//www.youtube.com/player_api?noext',
'coffee/src/ajax_prefix': 'xmodule_js/common_static/coffee/src/ajax_prefix', 'coffee/src/ajax_prefix': 'xmodule_js/common_static/coffee/src/ajax_prefix',
'coffee/src/instructor_dashboard/student_admin': 'coffee/src/instructor_dashboard/student_admin', 'js/instructor_dashboard/student_admin': 'js/instructor_dashboard/student_admin',
'xmodule_js/common_static/js/test/add_ajax_prefix': 'xmodule_js/common_static/js/test/add_ajax_prefix', 'xmodule_js/common_static/js/test/add_ajax_prefix': 'xmodule_js/common_static/js/test/add_ajax_prefix',
'xblock/lms.runtime.v1': 'lms/js/xblock/lms.runtime.v1', 'xblock/lms.runtime.v1': 'lms/js/xblock/lms.runtime.v1',
'xblock': 'common/js/xblock', 'xblock': 'common/js/xblock',
...@@ -283,8 +283,8 @@ ...@@ -283,8 +283,8 @@
exports: 'AjaxPrefix', exports: 'AjaxPrefix',
deps: ['coffee/src/ajax_prefix'] deps: ['coffee/src/ajax_prefix']
}, },
'coffee/src/instructor_dashboard/util': { 'js/instructor_dashboard/util': {
exports: 'coffee/src/instructor_dashboard/util', exports: 'js/instructor_dashboard/util',
deps: ['jquery', 'underscore', 'slick.core', 'slick.grid'], deps: ['jquery', 'underscore', 'slick.core', 'slick.grid'],
init: function() { init: function() {
// Set global variables that the util code is expecting to be defined // Set global variables that the util code is expecting to be defined
...@@ -298,9 +298,9 @@ ...@@ -298,9 +298,9 @@
}); });
} }
}, },
'coffee/src/instructor_dashboard/student_admin': { 'js/instructor_dashboard/student_admin': {
exports: 'coffee/src/instructor_dashboard/student_admin', exports: 'js/instructor_dashboard/student_admin',
deps: ['jquery', 'underscore', 'coffee/src/instructor_dashboard/util', 'string_utils'] deps: ['jquery', 'underscore', 'js/instructor_dashboard/util', 'string_utils']
}, },
'js/instructor_dashboard/certificates': { 'js/instructor_dashboard/certificates': {
exports: 'js/instructor_dashboard/certificates', exports: 'js/instructor_dashboard/certificates',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment