diff --git a/lms/envs/common.py b/lms/envs/common.py index b8df57c88d..4b6a8bf604 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -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', diff --git a/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee b/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee deleted file mode 100644 index bb2ba81a11..0000000000 --- a/lms/static/coffee/spec/instructor_dashboard/membership_spec.coffee +++ /dev/null @@ -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 '

Success

All accounts were created successfully.

' - - 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 '

Errors

The following errors were generated:

  • cannot read the line 2
  • testuser1 (testemail1@email.com): (Username already exists)
' - - 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 '

Warnings

The following warnings were generated:

  • user1 (user1email): (email is in valid)
' - - submitCallback = jasmine.createSpy().and.returnValue() - @autoenrollment.$student_enrollment_form.submit(submitCallback) - @autoenrollment.$enrollment_signup_button.click() - expect($('.results .list-summary').text()).toEqual('user1 (user1email): (email is in valid)') - expect(submitCallback).toHaveBeenCalled() \ No newline at end of file diff --git a/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee b/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee deleted file mode 100644 index 63250d12ae..0000000000 --- a/lms/static/coffee/spec/instructor_dashboard/send_email_spec.coffee +++ /dev/null @@ -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,'')) diff --git a/lms/static/coffee/src/instructor_dashboard/course_info.coffee b/lms/static/coffee/src/instructor_dashboard/course_info.coffee deleted file mode 100644 index c481a33bb5..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/course_info.coffee +++ /dev/null @@ -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 diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/coffee/src/instructor_dashboard/data_download.coffee deleted file mode 100644 index 4fe762965a..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/data_download.coffee +++ /dev/null @@ -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 = $ '
', 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 = $ '
', 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 diff --git a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee b/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee deleted file mode 100644 index acc761c887..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee +++ /dev/null @@ -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 diff --git a/lms/static/coffee/src/instructor_dashboard/extensions.coffee b/lms/static/coffee/src/instructor_dashboard/extensions.coffee deleted file mode 100644 index a45cffce67..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/extensions.coffee +++ /dev/null @@ -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 = $ '
', 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 diff --git a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee b/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee deleted file mode 100644 index 7beee97056..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee +++ /dev/null @@ -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 diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee deleted file mode 100644 index 1f1febb087..0000000000 --- a/lms/static/coffee/src/instructor_dashboard/membership.coffee +++ /dev/null @@ -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 = $ '' - for item in row_array - $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('
<%- label %>
')({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 = $ '
', class: 'request-res-section' - task_res_section.append $ '

', text: label - ids_list = $ '
    ' - task_res_section.append ids_list - - for identifier in ids - ids_list.append $ '
  • ', 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 = $ '
    ', class: 'request-res-section' - task_res_section.append $ '

    ', text: label - ids_list = $ '
      ' - task_res_section.append ids_list - - for identifier in ids - ids_list.append $ '
    • ', 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) -> - "Revoke Access" - ] - - table_data = data[@rolename] - - $table_placeholder = $ '
      ', 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 $ '