Merge pull request #13681 from edx/alisan/instructor-dashboard-coffeescript
Convert instructor dashboard coffeeScript to js
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
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()
|
||||
@@ -1,92 +0,0 @@
|
||||
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,''))
|
||||
@@ -1,55 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,297 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,92 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,155 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,202 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,740 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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
|
||||
@@ -1,197 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,398 +0,0 @@
|
||||
###
|
||||
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
|
||||
@@ -1,463 +0,0 @@
|
||||
# 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
|
||||
46
lms/static/js/instructor_dashboard/course_info.js
Normal file
46
lms/static/js/instructor_dashboard/course_info.js
Normal file
@@ -0,0 +1,46 @@
|
||||
(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);
|
||||
382
lms/static/js/instructor_dashboard/data_download.js
Normal file
382
lms/static/js/instructor_dashboard/data_download.js
Normal file
@@ -0,0 +1,382 @@
|
||||
/* 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);
|
||||
96
lms/static/js/instructor_dashboard/e-commerce.js
Normal file
96
lms/static/js/instructor_dashboard/e-commerce.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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);
|
||||
192
lms/static/js/instructor_dashboard/extensions.js
Normal file
192
lms/static/js/instructor_dashboard/extensions.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/* 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);
|
||||
215
lms/static/js/instructor_dashboard/instructor_dashboard.js
Normal file
215
lms/static/js/instructor_dashboard/instructor_dashboard.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
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);
|
||||
976
lms/static/js/instructor_dashboard/membership.js
Normal file
976
lms/static/js/instructor_dashboard/membership.js
Normal file
@@ -0,0 +1,976 @@
|
||||
/* 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);
|
||||
17
lms/static/js/instructor_dashboard/metrics.js
Normal file
17
lms/static/js/instructor_dashboard/metrics.js
Normal file
@@ -0,0 +1,17 @@
|
||||
(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);
|
||||
256
lms/static/js/instructor_dashboard/send_email.js
Normal file
256
lms/static/js/instructor_dashboard/send_email.js
Normal file
@@ -0,0 +1,256 @@
|
||||
/* 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);
|
||||
570
lms/static/js/instructor_dashboard/student_admin.js
Normal file
570
lms/static/js/instructor_dashboard/student_admin.js
Normal file
@@ -0,0 +1,570 @@
|
||||
/* 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);
|
||||
537
lms/static/js/instructor_dashboard/util.js
Normal file
537
lms/static/js/instructor_dashboard/util.js
Normal file
@@ -0,0 +1,537 @@
|
||||
/* 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);
|
||||
@@ -1,6 +1,6 @@
|
||||
/* 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) {
|
||||
|
||||
93
lms/static/js/spec/instructor_dashboard/membership_spec.js
Normal file
93
lms/static/js/spec/instructor_dashboard/membership_spec.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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);
|
||||
126
lms/static/js/spec/instructor_dashboard/send_email_spec.js
Normal file
126
lms/static/js/spec/instructor_dashboard/send_email_spec.js
Normal file
@@ -0,0 +1,126 @@
|
||||
(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);
|
||||
@@ -1,268 +1,283 @@
|
||||
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 url = dashboard_api_url + '/rescore_entrance_exam';
|
||||
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
|
||||
});
|
||||
|
||||
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);
|
||||
var params = $.param({
|
||||
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
|
||||
});
|
||||
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 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',
|
||||
|
||||
Reference in New Issue
Block a user