Commit 07990336 by alisan617 Committed by GitHub

Merge pull request #13681 from edx/alisan/instructor-dashboard-coffeescript

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