diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index e579593a88..c48efa91f9 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -52,17 +52,17 @@ def instructor_dashboard_2(request, course_id): _section_analytics(course_id), ] + # Gate access to course email by feature flag & by course-specific authorization + if settings.MITX_FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and \ + is_studio_course and CourseAuthorization.instructor_email_enabled(course_id): + sections.append(_section_send_email(course_id, access, course)) + enrollment_count = sections[0]['enrollment_count'] disable_buttons = False max_enrollment_for_buttons = settings.MITX_FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: disable_buttons = enrollment_count > max_enrollment_for_buttons - # Gate access by feature flag & by course-specific authorization - if settings.MITX_FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and \ - is_studio_course and CourseAuthorization.instructor_email_enabled(course_id): - sections.append(_section_send_email(course_id, access, course)) - context = { 'course': course, 'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}), @@ -156,6 +156,7 @@ def _section_data_download(course_id): 'get_grading_config_url': reverse('get_grading_config', kwargs={'course_id': course_id}), 'get_students_features_url': reverse('get_students_features', kwargs={'course_id': course_id}), 'get_anon_ids_url': reverse('get_anon_ids', kwargs={'course_id': course_id}), + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), } return section_data @@ -171,7 +172,8 @@ def _section_send_email(course_id, access, course): 'section_display_name': _('Email'), 'access': access, 'send_email': reverse('send_email', kwargs={'course_id': course_id}), - 'editor': email_editor + 'editor': email_editor, + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), } return section_data diff --git a/lms/envs/dev.py b/lms/envs/dev.py index e873861196..e1eadf1331 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -29,7 +29,8 @@ MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard) MITX_FEATURES['ENABLE_INSTRUCTOR_ANALYTICS'] = True MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True -MITX_FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True +MITX_FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True # Enable email for all Studio courses +MITX_FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False # Give all courses email (don't require django-admin perms) MITX_FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True MITX_FEATURES['ENABLE_INSTRUCTOR_BETA_DASHBOARD'] = True MITX_FEATURES['MULTIPLE_ENROLLMENT_ROLES'] = True diff --git a/lms/static/coffee/src/instructor_dashboard/course_info.coffee b/lms/static/coffee/src/instructor_dashboard/course_info.coffee index 19f9ce9707..beca8db712 100644 --- a/lms/static/coffee/src/instructor_dashboard/course_info.coffee +++ b/lms/static/coffee/src/instructor_dashboard/course_info.coffee @@ -1,15 +1,17 @@ ### Course Info Section -This is the implementation of the simplest section -of the instructor dashboard. 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 +load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager +create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments + # A typical section object. # constructed with $section, a jquery object @@ -37,6 +39,29 @@ class CourseInfo else @$course_errors_wrapper.addClass 'open' + ### Pending Instructor Tasks Section #### + # Currently running tasks + @$table_running_tasks = @$section.find ".running-tasks-table" + + # start polling for task list + # if the list is in the DOM + if @$table_running_tasks.length > 0 + # reload every 20 seconds. + TASK_LIST_POLL_INTERVAL = 20000 + @reload_running_tasks_list() + @task_poller = new (load_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 + dataType: 'json' + url: list_endpoint + success: (data) => create_task_list_table @$table_running_tasks, data.tasks + error: std_ajax_err => console.warn "error listing all instructor tasks" + ### /Pending Instructor Tasks Section #### + # export for use # create parent namespaces if they do not already exist. diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/coffee/src/instructor_dashboard/data_download.coffee index b5bbde9182..ffc24a574b 100644 --- a/lms/static/coffee/src/instructor_dashboard/data_download.coffee +++ b/lms/static/coffee/src/instructor_dashboard/data_download.coffee @@ -8,7 +8,8 @@ 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 - +load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager +create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments # Data Download Section class DataDownload @@ -81,6 +82,29 @@ class DataDownload @$display_text.html data['grading_config_summary'] + ### Pending Instructor Tasks Section #### + # Currently running tasks + @$table_running_tasks = @$section.find ".running-tasks-table" + + # start polling for task list + # if the list is in the DOM + if @$table_running_tasks.length > 0 + # reload every 20 seconds. + TASK_LIST_POLL_INTERVAL = 20000 + @reload_running_tasks_list() + @task_poller = new (load_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 + dataType: 'json' + url: list_endpoint + success: (data) => create_task_list_table @$table_running_tasks, data.tasks + error: std_ajax_err => console.warn "error listing all instructor tasks" + ### /Pending Instructor Tasks Section #### + clear_display: -> @$display_text.empty() @$display_table.empty() diff --git a/lms/static/coffee/src/instructor_dashboard/send_email.coffee b/lms/static/coffee/src/instructor_dashboard/send_email.coffee index 7fc839cca6..27eea3d339 100644 --- a/lms/static/coffee/src/instructor_dashboard/send_email.coffee +++ b/lms/static/coffee/src/instructor_dashboard/send_email.coffee @@ -6,8 +6,11 @@ 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 +load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager +create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments class SendEmail constructor: (@$container) -> @@ -87,6 +90,29 @@ class Email # isolate # initialize SendEmail subsection plantTimeout 0, => new SendEmail @$section.find '.send-email' + ### Pending Instructor Tasks Section #### + # Currently running tasks + @$table_running_tasks = @$section.find ".running-tasks-table" + + # start polling for task list + # if the list is in the DOM + if @$table_running_tasks.length > 0 + # reload every 20 seconds. + TASK_LIST_POLL_INTERVAL = 20000 + @reload_running_tasks_list() + @task_poller = new (load_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 + dataType: 'json' + url: list_endpoint + success: (data) => create_task_list_table @$table_running_tasks, data.tasks + error: std_ajax_err => console.warn "error listing all instructor tasks" + ### /Pending Instructor Tasks Section #### + # handler for when the section title is clicked. onClickTitle: -> diff --git a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee index c07069a493..198c7e84a5 100644 --- a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee +++ b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee @@ -6,10 +6,12 @@ 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 plantInterval = -> window.InstructorDashboard.util.plantInterval.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager +create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments # get jquery element and assert its existance @@ -21,54 +23,6 @@ find_and_assert = ($root, selector) -> else item -# 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: 60 - forceFitColumns: true - - columns = [ - id: 'task_type' - field: 'task_type' - name: 'Task Type' - , - id: 'requester' - field: 'requester' - name: 'Requester' - width: 30 - , - id: 'task_input' - field: 'task_input' - name: 'Input' - , - id: 'task_state' - field: 'task_state' - name: 'State' - width: 30 - , - id: 'task_id' - field: 'task_id' - name: 'Task ID' - width: 50 - , - id: 'created' - field: 'created' - name: 'Created' - ] - - table_data = tasks_data - - $table_placeholder = $ '
', class: 'slickgrid' - $table_tasks.append $table_placeholder - grid = new Slick.Grid($table_placeholder, table_data, columns, options) - class StudentAdmin constructor: (@$section) -> @@ -100,15 +54,6 @@ class StudentAdmin @$request_response_error_grade = find_and_assert @$section, ".student-grade-container .request-response-error" @$request_response_error_all = @$section.find ".course-specific-container .request-response-error" - # start polling for task list - # if the list is in the DOM - if @$table_running_tasks.length > 0 - # reload every 20 seconds. - TASK_LIST_POLL_INTERVAL = 20000 - @reload_running_tasks_list() - @task_poller = new (load_IntervalManager()) TASK_LIST_POLL_INTERVAL, => - @reload_running_tasks_list() - # attach click handlers # go to student progress page @@ -294,6 +239,16 @@ class StudentAdmin 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.") + # start polling for task list + # if the list is in the DOM + if @$table_running_tasks.length > 0 + # reload every 20 seconds. + TASK_LIST_POLL_INTERVAL = 20000 + @reload_running_tasks_list() + @task_poller = new (load_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 diff --git a/lms/static/coffee/src/instructor_dashboard/util.coffee b/lms/static/coffee/src/instructor_dashboard/util.coffee index 9217da5064..629532768e 100644 --- a/lms/static/coffee/src/instructor_dashboard/util.coffee +++ b/lms/static/coffee/src/instructor_dashboard/util.coffee @@ -17,6 +17,54 @@ std_ajax_err = (handler) -> (jqXHR, textStatus, 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: 60 + forceFitColumns: true + + columns = [ + id: 'task_type' + field: 'task_type' + name: 'Task Type' + , + id: 'requester' + field: 'requester' + name: 'Requester' + width: 30 + , + id: 'task_input' + field: 'task_input' + name: 'Input' + , + id: 'task_state' + field: 'task_state' + name: 'State' + width: 30 + , + id: 'task_id' + field: 'task_id' + name: 'Task ID' + width: 50 + , + id: 'created' + field: 'created' + name: 'Created' + ] + + table_data = tasks_data + + $table_placeholder = $ '
', class: 'slickgrid' + $table_tasks.append $table_placeholder + grid = new Slick.Grid($table_placeholder, table_data, columns, options) + # Helper class for managing the execution of interval tasks. # Handles pausing and restarting. class IntervalManager @@ -47,3 +95,4 @@ if _? plantInterval: plantInterval std_ajax_err: std_ajax_err IntervalManager: IntervalManager + create_task_list_table: create_task_list_table diff --git a/lms/templates/instructor/instructor_dashboard_2/course_info.html b/lms/templates/instructor/instructor_dashboard_2/course_info.html index cb113e1846..626d1b8ec8 100644 --- a/lms/templates/instructor/instructor_dashboard_2/course_info.html +++ b/lms/templates/instructor/instructor_dashboard_2/course_info.html @@ -43,6 +43,7 @@

${_("Pending Instructor Tasks")}

${_("The status for any active tasks appears in a table below.")}

+
diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download.html b/lms/templates/instructor/instructor_dashboard_2/data_download.html index 0bf21dd58a..b57fd7b30e 100644 --- a/lms/templates/instructor/instructor_dashboard_2/data_download.html +++ b/lms/templates/instructor/instructor_dashboard_2/data_download.html @@ -19,4 +19,16 @@
+ +%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'): +
+
+

${_("Pending Instructor Tasks")}

+

${_("The status for any active tasks appears in a table below.")}

+
+ +
+
+ +%endif
diff --git a/lms/templates/instructor/instructor_dashboard_2/send_email.html b/lms/templates/instructor/instructor_dashboard_2/send_email.html index 0ff0360e91..b3970c5091 100644 --- a/lms/templates/instructor/instructor_dashboard_2/send_email.html +++ b/lms/templates/instructor/instructor_dashboard_2/send_email.html @@ -54,4 +54,16 @@
+ +%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'): +
+
+

${_("Pending Instructor Tasks")}

+

${_("The status for any active tasks appears in a table below.")}

+
+ +
+
+ +%endif diff --git a/lms/templates/instructor/instructor_dashboard_2/student_admin.html b/lms/templates/instructor/instructor_dashboard_2/student_admin.html index 2ae60789df..4d60810008 100644 --- a/lms/templates/instructor/instructor_dashboard_2/student_admin.html +++ b/lms/templates/instructor/instructor_dashboard_2/student_admin.html @@ -109,3 +109,15 @@

%endif + +%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'): +
+
+

${_("Pending Instructor Tasks")}

+

${_("The status for any active tasks appears in a table below.")}

+
+ +
+
+ +%endif