Commit 57a57e8a by Sarina Canelake

Move PIT code into util.coffee

Add testing coverage
LMS-1242

Add "Edit This Course In Studio" link for studio courses
LMS-1291
parent 123e1810
...@@ -5,6 +5,7 @@ Unit tests for instructor.api methods. ...@@ -5,6 +5,7 @@ Unit tests for instructor.api methods.
import unittest import unittest
import json import json
import requests import requests
import datetime
from urllib import quote from urllib import quote
from django.test import TestCase from django.test import TestCase
from nose.tools import raises from nose.tools import raises
...@@ -761,6 +762,18 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -761,6 +762,18 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
class MockCompletionInfo(object):
"""Mock for get_task_completion_info"""
times_called = 0
def mock_get_task_completion_info(self, *args): # pylint: disable=unused-argument
"""Mock for get_task_completion_info"""
self.times_called += 1
if self.times_called % 2 == 0:
return (True, 'Task Completed')
return (False, 'Task Errored In Some Way')
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase): class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
...@@ -769,15 +782,44 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -769,15 +782,44 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
class FakeTask(object): class FakeTask(object):
""" Fake task object """ """ Fake task object """
FEATURES = ['task_type', 'task_input', 'task_id', 'requester', 'created', 'task_state'] FEATURES = [
'task_type',
'task_input',
'task_id',
'requester',
'task_state',
'created',
'status',
'task_message',
'duration_sec',
'task_output'
]
def __init__(self): def __init__(self, completion):
for feature in self.FEATURES: for feature in self.FEATURES:
setattr(self, feature, 'expected') setattr(self, feature, 'expected')
# Make 'created' into a datetime
setattr(self, 'created', datetime.datetime(2013, 10, 25, 11, 42, 35))
# set 'status' and 'task_message' attrs
success, task_message = completion()
if success:
setattr(self, 'status', "Complete")
else:
setattr(self, 'status', "Incomplete")
setattr(self, 'task_message', task_message)
# Set 'task_output' attr, which will be parsed to the 'duration_sec' attr.
setattr(self, 'task_output', '{"duration_ms": 1035000}')
setattr(self, 'duration_sec', 1035000 / 1000.0)
def to_dict(self): def to_dict(self):
""" Convert fake task to dictionary representation. """ """ Convert fake task to dictionary representation. """
return {key: 'expected' for key in self.FEATURES} attr_dict = {key: getattr(self, key) for key in self.FEATURES}
attr_dict['created'] = attr_dict['created'].isoformat()
# Don't actually want task_output in the attribute dictionary, as this
# is not explicitly extracted in extract_task_features
del attr_dict['task_output']
return attr_dict
def setUp(self): def setUp(self):
self.instructor = AdminFactory.create() self.instructor = AdminFactory.create()
...@@ -797,58 +839,77 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -797,58 +839,77 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
), ),
state=json.dumps({'attempts': 10}), state=json.dumps({'attempts': 10}),
) )
mock_factory = MockCompletionInfo()
self.tasks = [self.FakeTask(mock_factory.mock_get_task_completion_info) for _ in xrange(6)]
self.tasks = [self.FakeTask() for _ in xrange(6)] def tearDown(self):
"""
Undo all patches.
"""
patch.stopall()
@patch.object(instructor_task.api, 'get_running_instructor_tasks') @patch.object(instructor_task.api, 'get_running_instructor_tasks')
def test_list_instructor_tasks_running(self, act): def test_list_instructor_tasks_running(self, act):
""" Test list of all running tasks. """ """ Test list of all running tasks. """
act.return_value = self.tasks act.return_value = self.tasks
url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id}) url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id})
mock_factory = MockCompletionInfo()
with patch('instructor.views.api.get_task_completion_info') as mock_completion_info:
mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info
response = self.client.get(url, {}) response = self.client.get(url, {})
print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# check response # check response
self.assertTrue(act.called) self.assertTrue(act.called)
expected_tasks = [ftask.to_dict() for ftask in self.tasks] expected_tasks = [ftask.to_dict() for ftask in self.tasks]
expected_res = {'tasks': expected_tasks} actual_tasks = json.loads(response.content)['tasks']
self.assertEqual(json.loads(response.content), expected_res) for exp_task, act_task in zip(expected_tasks, actual_tasks):
self.assertDictEqual(exp_task, act_task)
self.assertEqual(actual_tasks, expected_tasks)
@patch.object(instructor_task.api, 'get_instructor_task_history') @patch.object(instructor_task.api, 'get_instructor_task_history')
def test_list_instructor_tasks_problem(self, act): def test_list_instructor_tasks_problem(self, act):
""" Test list task history for problem. """ """ Test list task history for problem. """
act.return_value = self.tasks act.return_value = self.tasks
url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id}) url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id})
mock_factory = MockCompletionInfo()
with patch('instructor.views.api.get_task_completion_info') as mock_completion_info:
mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info
response = self.client.get(url, { response = self.client.get(url, {
'problem_urlname': self.problem_urlname, 'problem_urlname': self.problem_urlname,
}) })
print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# check response # check response
self.assertTrue(act.called) self.assertTrue(act.called)
expected_tasks = [ftask.to_dict() for ftask in self.tasks] expected_tasks = [ftask.to_dict() for ftask in self.tasks]
expected_res = {'tasks': expected_tasks} actual_tasks = json.loads(response.content)['tasks']
self.assertEqual(json.loads(response.content), expected_res) for exp_task, act_task in zip(expected_tasks, actual_tasks):
self.assertDictEqual(exp_task, act_task)
self.assertEqual(actual_tasks, expected_tasks)
@patch.object(instructor_task.api, 'get_instructor_task_history') @patch.object(instructor_task.api, 'get_instructor_task_history')
def test_list_instructor_tasks_problem_student(self, act): def test_list_instructor_tasks_problem_student(self, act):
""" Test list task history for problem AND student. """ """ Test list task history for problem AND student. """
act.return_value = self.tasks act.return_value = self.tasks
url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id}) url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id})
mock_factory = MockCompletionInfo()
with patch('instructor.views.api.get_task_completion_info') as mock_completion_info:
mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info
response = self.client.get(url, { response = self.client.get(url, {
'problem_urlname': self.problem_urlname, 'problem_urlname': self.problem_urlname,
'unique_student_identifier': self.student.email, 'unique_student_identifier': self.student.email,
}) })
print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# check response # check response
self.assertTrue(act.called) self.assertTrue(act.called)
expected_tasks = [ftask.to_dict() for ftask in self.tasks] expected_tasks = [ftask.to_dict() for ftask in self.tasks]
expected_res = {'tasks': expected_tasks} actual_tasks = json.loads(response.content)['tasks']
self.assertEqual(json.loads(response.content), expected_res) for exp_task, act_task in zip(expected_tasks, actual_tasks):
self.assertDictEqual(exp_task, act_task)
self.assertEqual(actual_tasks, expected_tasks)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
......
...@@ -8,6 +8,7 @@ Many of these GETs may become PUTs in the future. ...@@ -8,6 +8,7 @@ Many of these GETs may become PUTs in the future.
import re import re
import logging import logging
import json
import requests import requests
from django.conf import settings from django.conf import settings
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
...@@ -30,6 +31,7 @@ from courseware.models import StudentModule ...@@ -30,6 +31,7 @@ from courseware.models import StudentModule
from student.models import unique_id_for_user from student.models import unique_id_for_user
import instructor_task.api import instructor_task.api
from instructor_task.api_helper import AlreadyRunningError from instructor_task.api_helper import AlreadyRunningError
from instructor_task.views import get_task_completion_info
import instructor.enrollment as enrollment import instructor.enrollment as enrollment
from instructor.enrollment import enroll_email, unenroll_email from instructor.enrollment import enroll_email, unenroll_email
from instructor.views.tools import strip_if_string, get_student_from_identifier from instructor.views.tools import strip_if_string, get_student_from_identifier
...@@ -675,9 +677,38 @@ def list_instructor_tasks(request, course_id): ...@@ -675,9 +677,38 @@ def list_instructor_tasks(request, course_id):
tasks = instructor_task.api.get_running_instructor_tasks(course_id) tasks = instructor_task.api.get_running_instructor_tasks(course_id)
def extract_task_features(task): def extract_task_features(task):
""" Convert task to dict for json rendering """ """
features = ['task_type', 'task_input', 'task_id', 'requester', 'created', 'task_state'] Convert task to dict for json rendering.
return dict((feature, str(getattr(task, feature))) for feature in features) Expects tasks have the following features:
* task_type (str, type of task)
* task_input (dict, input(s) to the task)
* task_id (str, celery id of the task)
* requester (str, username who submitted the task)
* task_state (str, state of task eg PROGRESS, COMPLETED)
* created (datetime, when the task was completed)
* task_output (optional)
"""
# Pull out information from the task
features = ['task_type', 'task_input', 'task_id', 'requester', 'task_state']
task_feature_dict = dict((feature, str(getattr(task, feature))) for feature in features)
# Some information (created, duration, status, task message) require additional formatting
task_feature_dict['created'] = task.created.isoformat()
# Get duration info, if known
duration_sec = 'unknown'
if hasattr(task, 'task_output') and task.task_output is not None:
task_output = json.loads(task.task_output)
if 'duration_ms' in task_output:
duration_sec = int(task_output['duration_ms'] / 1000.0)
task_feature_dict['duration_sec'] = duration_sec
# Get progress status message & success information
success, task_message = get_task_completion_info(task)
status = "Complete" if success else "Incomplete"
task_feature_dict['status'] = status
task_feature_dict['task_message'] = task_message
return task_feature_dict
response_payload = { response_payload = {
'tasks': map(extract_task_features, tasks), 'tasks': map(extract_task_features, tasks),
......
...@@ -18,7 +18,7 @@ from xmodule.modulestore.django import modulestore ...@@ -18,7 +18,7 @@ from xmodule.modulestore.django import modulestore
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id, get_cms_course_link_by_id
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from student.models import CourseEnrollment from student.models import CourseEnrollment
...@@ -57,6 +57,10 @@ def instructor_dashboard_2(request, course_id): ...@@ -57,6 +57,10 @@ def instructor_dashboard_2(request, course_id):
is_studio_course and CourseAuthorization.instructor_email_enabled(course_id): is_studio_course and CourseAuthorization.instructor_email_enabled(course_id):
sections.append(_section_send_email(course_id, access, course)) sections.append(_section_send_email(course_id, access, course))
studio_url = None
if is_studio_course:
studio_url = get_cms_course_link_by_id(course_id)
enrollment_count = sections[0]['enrollment_count'] enrollment_count = sections[0]['enrollment_count']
disable_buttons = False disable_buttons = False
max_enrollment_for_buttons = settings.MITX_FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") max_enrollment_for_buttons = settings.MITX_FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
...@@ -66,6 +70,7 @@ def instructor_dashboard_2(request, course_id): ...@@ -66,6 +70,7 @@ def instructor_dashboard_2(request, course_id):
context = { context = {
'course': course, 'course': course,
'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}), 'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}),
'studio_url': studio_url,
'sections': sections, 'sections': sections,
'disable_buttons': disable_buttons, 'disable_buttons': disable_buttons,
} }
......
...@@ -1589,14 +1589,16 @@ def get_background_task_table(course_id, problem_url=None, student=None, task_ty ...@@ -1589,14 +1589,16 @@ def get_background_task_table(course_id, problem_url=None, student=None, task_ty
success, task_message = get_task_completion_info(instructor_task) success, task_message = get_task_completion_info(instructor_task)
status = "Complete" if success else "Incomplete" status = "Complete" if success else "Incomplete"
# generate row for this task: # generate row for this task:
row = [str(instructor_task.task_type), row = [
str(instructor_task.task_type),
str(instructor_task.task_id), str(instructor_task.task_id),
str(instructor_task.requester), str(instructor_task.requester),
instructor_task.created.isoformat(' '), instructor_task.created.isoformat(' '),
duration_sec, duration_sec,
str(instructor_task.task_state), str(instructor_task.task_state),
status, status,
task_message] task_message
]
datatable['data'].append(row) datatable['data'].append(row)
if problem_url is None: if problem_url is None:
......
...@@ -9,9 +9,7 @@ such that the value can be defined later than this assignment (file load order). ...@@ -9,9 +9,7 @@ such that the value can be defined later than this assignment (file load order).
# Load utilities # Load utilities
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments
# A typical section object. # A typical section object.
# constructed with $section, a jquery object # constructed with $section, a jquery object
...@@ -39,29 +37,13 @@ class CourseInfo ...@@ -39,29 +37,13 @@ class CourseInfo
else else
@$course_errors_wrapper.addClass 'open' @$course_errors_wrapper.addClass 'open'
### Pending Instructor Tasks Section #### @instructor_tasks = new (PendingInstructorTasks()) @$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 # handler for when the section title is clicked.
reload_running_tasks_list: => onClickTitle: -> @instructor_tasks.task_poller?.start()
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 is closed
onExit: -> @instructor_tasks.task_poller?.stop()
# export for use # export for use
# create parent namespaces if they do not already exist. # create parent namespaces if they do not already exist.
......
...@@ -8,8 +8,7 @@ such that the value can be defined later than this assignment (file load order). ...@@ -8,8 +8,7 @@ such that the value can be defined later than this assignment (file load order).
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments
# Data Download Section # Data Download Section
class DataDownload class DataDownload
...@@ -81,29 +80,13 @@ class DataDownload ...@@ -81,29 +80,13 @@ class DataDownload
@clear_display() @clear_display()
@$display_text.html data['grading_config_summary'] @$display_text.html data['grading_config_summary']
@instructor_tasks = new (PendingInstructorTasks()) @$section
### Pending Instructor Tasks Section #### # handler for when the section title is clicked.
# Currently running tasks onClickTitle: -> @instructor_tasks.task_poller?.start()
@$table_running_tasks = @$section.find ".running-tasks-table"
# start polling for task list # handler for when the section is closed
# if the list is in the DOM onExit: -> @instructor_tasks.task_poller?.stop()
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: -> clear_display: ->
@$display_text.empty() @$display_text.empty()
......
...@@ -9,8 +9,7 @@ such that the value can be defined later than this assignment (file load order). ...@@ -9,8 +9,7 @@ such that the value can be defined later than this assignment (file load order).
# Load utilities # Load utilities
plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager PendingInstructorTasks = -> window.InstructorDashboard.util.PendingInstructorTasks
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.apply this, arguments
class SendEmail class SendEmail
constructor: (@$container) -> constructor: (@$container) ->
...@@ -90,31 +89,13 @@ class Email ...@@ -90,31 +89,13 @@ class Email
# isolate # initialize SendEmail subsection # isolate # initialize SendEmail subsection
plantTimeout 0, => new SendEmail @$section.find '.send-email' plantTimeout 0, => new SendEmail @$section.find '.send-email'
### Pending Instructor Tasks Section #### @instructor_tasks = new (PendingInstructorTasks()) @$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. # handler for when the section title is clicked.
onClickTitle: -> onClickTitle: -> @instructor_tasks.task_poller?.start()
# handler for when the section is closed
onExit: -> @instructor_tasks.task_poller?.stop()
# export for use # export for use
......
...@@ -12,6 +12,7 @@ plantInterval = -> window.InstructorDashboard.util.plantInterval.apply this, arg ...@@ -12,6 +12,7 @@ plantInterval = -> window.InstructorDashboard.util.plantInterval.apply this, arg
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager load_IntervalManager = -> window.InstructorDashboard.util.IntervalManager
create_task_list_table = -> window.InstructorDashboard.util.create_task_list_table.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 # get jquery element and assert its existance
...@@ -47,7 +48,7 @@ class StudentAdmin ...@@ -47,7 +48,7 @@ class StudentAdmin
@$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']" @$btn_rescore_problem_all = @$section.find "input[name='rescore-problem-all']"
@$btn_task_history_all = @$section.find "input[name='task-history-all']" @$btn_task_history_all = @$section.find "input[name='task-history-all']"
@$table_task_history_all = @$section.find ".task-history-all-table" @$table_task_history_all = @$section.find ".task-history-all-table"
@$table_running_tasks = @$section.find ".running-tasks-table" @instructor_tasks = new (PendingInstructorTasks()) @$section
# response areas # response areas
@$request_response_error_progress = find_and_assert @$section, ".student-specific-container .request-response-error" @$request_response_error_progress = find_and_assert @$section, ".student-specific-container .request-response-error"
...@@ -239,24 +240,6 @@ class StudentAdmin ...@@ -239,24 +240,6 @@ class StudentAdmin
create_task_list_table @$table_task_history_all, data.tasks 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.") 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
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"
# wraps a function, but first clear the error displays # wraps a function, but first clear the error displays
clear_errors_then: (cb) -> clear_errors_then: (cb) ->
@$request_response_error_progress.empty() @$request_response_error_progress.empty()
...@@ -272,10 +255,10 @@ class StudentAdmin ...@@ -272,10 +255,10 @@ class StudentAdmin
@$request_response_error_all.empty() @$request_response_error_all.empty()
# handler for when the section title is clicked. # handler for when the section title is clicked.
onClickTitle: -> @task_poller?.start() onClickTitle: -> @instructor_tasks.task_poller?.start()
# handler for when the section is closed # handler for when the section is closed
onExit: -> @task_poller?.stop() onExit: -> @instructor_tasks.task_poller?.stop()
# export for use # export for use
......
...@@ -6,6 +6,15 @@ plantTimeout = (ms, cb) -> setTimeout cb, ms ...@@ -6,6 +6,15 @@ plantTimeout = (ms, cb) -> setTimeout cb, ms
plantInterval = (ms, cb) -> setInterval 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 # standard ajax error wrapper
# #
# wraps a `handler` function so that first # wraps a `handler` function so that first
...@@ -34,29 +43,47 @@ create_task_list_table = ($table_tasks, tasks_data) -> ...@@ -34,29 +43,47 @@ create_task_list_table = ($table_tasks, tasks_data) ->
id: 'task_type' id: 'task_type'
field: 'task_type' field: 'task_type'
name: 'Task Type' name: 'Task Type'
minWidth: 100
,
id: 'task_input'
field: 'task_input'
name: 'Task inputs'
minWidth: 150
,
id: 'task_id'
field: 'task_id'
name: 'Task ID'
minWidth: 150
, ,
id: 'requester' id: 'requester'
field: 'requester' field: 'requester'
name: 'Requester' name: 'Requester'
width: 30 minWidth: 80
, ,
id: 'task_input' id: 'created'
field: 'task_input' field: 'created'
name: 'Input' name: 'Submitted'
minWidth: 120
,
id: 'duration_sec'
field: 'duration_sec'
name: 'Duration (sec)'
minWidth: 80
, ,
id: 'task_state' id: 'task_state'
field: 'task_state' field: 'task_state'
name: 'State' name: 'State'
width: 30 minWidth: 80
, ,
id: 'task_id' id: 'status'
field: 'task_id' field: 'status'
name: 'Task ID' name: 'Task Status'
width: 50 minWidth: 80
, ,
id: 'created' id: 'task_message'
field: 'created' field: 'task_message'
name: 'Created' name: 'Task Progress'
minWidth: 120
] ]
table_data = tasks_data table_data = tasks_data
...@@ -85,6 +112,30 @@ class IntervalManager ...@@ -85,6 +112,30 @@ class IntervalManager
@intervalID = null @intervalID = null
class PendingInstructorTasks
### Pending Instructor Tasks Section ####
constructor: (@$section) ->
# Currently running tasks
@$table_running_tasks = find_and_assert @$section, ".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 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 # export for use
# create parent namespaces if they do not already exist. # create parent namespaces if they do not already exist.
# abort if underscore can not be found. # abort if underscore can not be found.
...@@ -96,3 +147,4 @@ if _? ...@@ -96,3 +147,4 @@ if _?
std_ajax_err: std_ajax_err std_ajax_err: std_ajax_err
IntervalManager: IntervalManager IntervalManager: IntervalManager
create_task_list_table: create_task_list_table create_task_list_table: create_task_list_table
PendingInstructorTasks: PendingInstructorTasks
...@@ -14,9 +14,16 @@ ...@@ -14,9 +14,16 @@
.olddash-button-wrapper { .olddash-button-wrapper {
position: absolute; position: absolute;
top: 17px; top: 16px;
right: 15px; right: 15px;
@include font-size(14); @include font-size(16);
}
.studio-edit-link{
position: absolute;
top: 40px;
right: 15px;
@include font-size(16);
} }
// system feedback - messages // system feedback - messages
......
...@@ -50,10 +50,14 @@ ...@@ -50,10 +50,14 @@
<section class="container"> <section class="container">
<div class="instructor-dashboard-wrapper-2"> <div class="instructor-dashboard-wrapper-2">
<div class="olddash-button-wrapper"><a href="${ old_dashboard_url }"> ${_("Back to Standard Dashboard")} </a></div> <div class="olddash-button-wrapper"><a href="${ old_dashboard_url }"> ${_("Back to Standard Dashboard")} </a></div>
%if studio_url:
## not checking access because if user can see this, they are at least course staff (with studio edit access)
<div class="studio-edit-link"><a href="${studio_url}" target="_blank">${_('Edit Course In Studio')}</a></div>
%endif
<section class="instructor-dashboard-content-2"> <section class="instructor-dashboard-content-2">
## <h1>Instructor Dashboard</h1> <h1>${_("Instructor Dashboard")}</h1>
<hr />
## links which are tied to idash-sections below. ## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee ## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section ## when the javascript loads, it clicks on the first section
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment