Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
cb4025b1
Commit
cb4025b1
authored
Oct 30, 2013
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1539 from edx/sarina/inst-dash-tasks
Enable Pending Tasks on beta dash // Course Info prettifying
parents
eb1b9260
b86e9129
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
405 additions
and
178 deletions
+405
-178
lms/djangoapps/instructor/tests/test_api.py
+77
-13
lms/djangoapps/instructor/views/api.py
+38
-3
lms/djangoapps/instructor/views/instructor_dashboard.py
+21
-10
lms/djangoapps/instructor/views/legacy.py
+4
-2
lms/envs/dev.py
+2
-1
lms/static/coffee/src/instructor_dashboard/analytics.coffee
+3
-5
lms/static/coffee/src/instructor_dashboard/course_info.coffee
+16
-9
lms/static/coffee/src/instructor_dashboard/data_download.coffee
+14
-8
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
+1
-1
lms/static/coffee/src/instructor_dashboard/membership.coffee
+3
-5
lms/static/coffee/src/instructor_dashboard/send_email.coffee
+13
-9
lms/static/coffee/src/instructor_dashboard/student_admin.coffee
+11
-76
lms/static/coffee/src/instructor_dashboard/util.coffee
+102
-1
lms/static/sass/course/instructor/_instructor_2.scss
+9
-2
lms/templates/instructor/instructor_dashboard_2/course_info.html
+49
-31
lms/templates/instructor/instructor_dashboard_2/data_download.html
+12
-0
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+6
-2
lms/templates/instructor/instructor_dashboard_2/send_email.html
+12
-0
lms/templates/instructor/instructor_dashboard_2/student_admin.html
+12
-0
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
cb4025b1
...
...
@@ -5,6 +5,7 @@ Unit tests for instructor.api methods.
import
unittest
import
json
import
requests
import
datetime
from
urllib
import
quote
from
django.test
import
TestCase
from
nose.tools
import
raises
...
...
@@ -761,6 +762,18 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
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
)
class
TestInstructorAPITaskLists
(
ModuleStoreTestCase
,
LoginEnrollmentTestCase
):
"""
...
...
@@ -769,15 +782,46 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
class
FakeTask
(
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'
]
def
__init__
(
self
):
def
__init__
(
self
,
completion
):
for
feature
in
self
.
FEATURES
:
setattr
(
self
,
feature
,
'expected'
)
# created needs to be a datetime
self
.
created
=
datetime
.
datetime
(
2013
,
10
,
25
,
11
,
42
,
35
)
# set 'status' and 'task_message' attrs
success
,
task_message
=
completion
()
if
success
:
self
.
status
=
"Complete"
else
:
self
.
status
=
"Incomplete"
self
.
task_message
=
task_message
# Set 'task_output' attr, which will be parsed to the 'duration_sec' attr.
self
.
task_output
=
'{"duration_ms": 1035000}'
self
.
duration_sec
=
1035000
/
1000.0
def
make_invalid_output
(
self
):
"""Munge task_output to be invalid json"""
self
.
task_output
=
'HI MY NAME IS INVALID JSON'
# This should be given the value of 'unknown' if the task output
# can't be properly parsed
self
.
duration_sec
=
'unknown'
def
to_dict
(
self
):
""" 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
()
return
attr_dict
def
setUp
(
self
):
self
.
instructor
=
AdminFactory
.
create
()
...
...
@@ -797,58 +841,78 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
),
state
=
json
.
dumps
({
'attempts'
:
10
}),
)
mock_factory
=
MockCompletionInfo
()
self
.
tasks
=
[
self
.
FakeTask
(
mock_factory
.
mock_get_task_completion_info
)
for
_
in
xrange
(
7
)]
self
.
tasks
[
-
1
]
.
make_invalid_output
()
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'
)
def
test_list_instructor_tasks_running
(
self
,
act
):
""" Test list of all running tasks. """
act
.
return_value
=
self
.
tasks
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
,
{})
print
response
.
content
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
self
.
assertTrue
(
act
.
called
)
expected_tasks
=
[
ftask
.
to_dict
()
for
ftask
in
self
.
tasks
]
expected_res
=
{
'tasks'
:
expected_tasks
}
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
expected_res
)
actual_tasks
=
json
.
loads
(
response
.
content
)[
'tasks'
]
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'
)
def
test_list_instructor_tasks_problem
(
self
,
act
):
""" Test list task history for problem. """
act
.
return_value
=
self
.
tasks
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
,
{
'problem_urlname'
:
self
.
problem_urlname
,
})
print
response
.
content
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
self
.
assertTrue
(
act
.
called
)
expected_tasks
=
[
ftask
.
to_dict
()
for
ftask
in
self
.
tasks
]
expected_res
=
{
'tasks'
:
expected_tasks
}
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
expected_res
)
actual_tasks
=
json
.
loads
(
response
.
content
)[
'tasks'
]
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'
)
def
test_list_instructor_tasks_problem_student
(
self
,
act
):
""" Test list task history for problem AND student. """
act
.
return_value
=
self
.
tasks
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
,
{
'problem_urlname'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
})
print
response
.
content
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
self
.
assertTrue
(
act
.
called
)
expected_tasks
=
[
ftask
.
to_dict
()
for
ftask
in
self
.
tasks
]
expected_res
=
{
'tasks'
:
expected_tasks
}
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
expected_res
)
actual_tasks
=
json
.
loads
(
response
.
content
)[
'tasks'
]
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
)
...
...
lms/djangoapps/instructor/views/api.py
View file @
cb4025b1
...
...
@@ -8,6 +8,7 @@ Many of these GETs may become PUTs in the future.
import
re
import
logging
import
json
import
requests
from
django.conf
import
settings
from
django_future.csrf
import
ensure_csrf_cookie
...
...
@@ -30,6 +31,7 @@ from courseware.models import StudentModule
from
student.models
import
unique_id_for_user
import
instructor_task.api
from
instructor_task.api_helper
import
AlreadyRunningError
from
instructor_task.views
import
get_task_completion_info
import
instructor.enrollment
as
enrollment
from
instructor.enrollment
import
enroll_email
,
unenroll_email
from
instructor.views.tools
import
strip_if_string
,
get_student_from_identifier
...
...
@@ -675,9 +677,42 @@ def list_instructor_tasks(request, course_id):
tasks
=
instructor_task
.
api
.
get_running_instructor_tasks
(
course_id
)
def
extract_task_features
(
task
):
""" Convert task to dict for json rendering """
features
=
[
'task_type'
,
'task_input'
,
'task_id'
,
'requester'
,
'created'
,
'task_state'
]
return
dict
((
feature
,
str
(
getattr
(
task
,
feature
)))
for
feature
in
features
)
"""
Convert task to dict for json rendering.
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
=
{
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
:
try
:
task_output
=
json
.
loads
(
task
.
task_output
)
except
ValueError
:
log
.
error
(
"Could not parse task output as valid json; task output:
%
s"
,
task
.
task_output
)
else
:
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
=
{
'tasks'
:
map
(
extract_task_features
,
tasks
),
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
cb4025b1
...
...
@@ -18,7 +18,7 @@ from xmodule.modulestore.django import modulestore
from
xblock.field_data
import
DictFieldData
from
xblock.fields
import
ScopeIds
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_common.models
import
FORUM_ROLE_ADMINISTRATOR
from
student.models
import
CourseEnrollment
...
...
@@ -45,27 +45,32 @@ def instructor_dashboard_2(request, course_id):
raise
Http404
()
sections
=
[
_section_course_info
(
course_id
,
access
),
_section_course_info
(
course_id
),
_section_membership
(
course_id
,
access
),
_section_student_admin
(
course_id
,
access
),
_section_data_download
(
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
))
studio_url
=
None
if
is_studio_course
:
studio_url
=
get_cms_course_link_by_id
(
course_id
)
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
}),
'studio_url'
:
studio_url
,
'sections'
:
sections
,
'disable_buttons'
:
disable_buttons
,
}
...
...
@@ -86,15 +91,19 @@ section_display_name will be used to generate link titles in the nav bar.
"""
# pylint: disable=W0105
def
_section_course_info
(
course_id
,
access
):
def
_section_course_info
(
course_id
):
""" Provide data for the corresponding dashboard section """
course
=
get_course_by_id
(
course_id
,
depth
=
None
)
course_org
,
course_num
,
course_name
=
course_id
.
split
(
'/'
)
section_data
=
{
'section_key'
:
'course_info'
,
'section_display_name'
:
_
(
'Course Info'
),
'course_id'
:
course_id
,
'access'
:
access
,
'course_org'
:
course_org
,
'course_num'
:
course_num
,
'course_name'
:
course_name
,
'course_display_name'
:
course
.
display_name
,
'enrollment_count'
:
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
)
.
count
(),
'has_started'
:
course
.
has_started
(),
...
...
@@ -156,6 +165,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 +181,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
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
cb4025b1
...
...
@@ -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
)
status
=
"Complete"
if
success
else
"Incomplete"
# 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
.
requester
),
instructor_task
.
created
.
isoformat
(
' '
),
duration_sec
,
str
(
instructor_task
.
task_state
),
status
,
task_message
]
task_message
]
datatable
[
'data'
]
.
append
(
row
)
if
problem_url
is
None
:
...
...
lms/envs/dev.py
View file @
cb4025b1
...
...
@@ -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
...
...
lms/static/coffee/src/instructor_dashboard/analytics.coffee
View file @
cb4025b1
...
...
@@ -230,9 +230,7 @@ class Analytics
# 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
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
Analytics
:
Analytics
lms/static/coffee/src/instructor_dashboard/course_info.coffee
View file @
cb4025b1
###
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).
###
plantTimeout
=
->
window
.
InstructorDashboard
.
util
.
plantTimeout
.
apply
this
,
argument
s
std_ajax_err
=
->
window
.
InstructorDashboard
.
util
.
std_ajax_err
.
apply
this
,
argument
s
# Load utilitie
s
PendingInstructorTasks
=
->
window
.
InstructorDashboard
.
util
.
PendingInstructorTask
s
# 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
...
...
@@ -37,12 +41,15 @@ class CourseInfo
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.
# abort if underscore can not be found.
if
_
?
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
CourseInfo
:
CourseInfo
lms/static/coffee/src/instructor_dashboard/data_download.coffee
View file @
cb4025b1
...
...
@@ -6,13 +6,16 @@ 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
,
argument
s
# Load utilitie
s
std_ajax_err
=
->
window
.
InstructorDashboard
.
util
.
std_ajax_err
.
apply
this
,
arguments
PendingInstructorTasks
=
->
window
.
InstructorDashboard
.
util
.
PendingInstructorTasks
# 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'
,
@
# gather elements
@
$display
=
@
$section
.
find
'.data-display'
@
$display_text
=
@
$display
.
find
'.data-display-text'
...
...
@@ -21,9 +24,9 @@ class DataDownload
@
$list_studs_btn
=
@
$section
.
find
(
"input[name='list-profiles']'"
)
@
$list_anon_btn
=
@
$section
.
find
(
"input[name='list-anon-ids']'"
)
@
$grade_config_btn
=
@
$section
.
find
(
"input[name='dump-gradeconf']'"
)
@
instructor_tasks
=
new
(
PendingInstructorTasks
())
@
$section
# attach click handlers
# The list-anon case is always CSV
@
$list_anon_btn
.
click
(
e
)
=>
url
=
@
$list_anon_btn
.
data
'endpoint'
...
...
@@ -80,6 +83,11 @@ class DataDownload
@
clear_display
()
@
$display_text
.
html
data
[
'grading_config_summary'
]
# 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
()
clear_display
:
->
@
$display_text
.
empty
()
...
...
@@ -89,9 +97,7 @@ class DataDownload
# 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
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
DataDownload
:
DataDownload
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
View file @
cb4025b1
...
...
@@ -118,7 +118,7 @@ setup_instructor_dashboard = (idash_content) =>
location
.
hash
=
"
#{
HASH_LINK_PREFIX
}#{
section_name
}
"
sections_have_loaded
.
after
->
$section
.
data
(
'wrapper'
)
?
.
onClickTitle
?
()
$section
.
data
(
'wrapper'
)
.
onClickTitle
()
# call onExit handler if exiting a section to a different section.
unless
$section
.
is
$active_section
...
...
lms/static/coffee/src/instructor_dashboard/membership.coffee
View file @
cb4025b1
...
...
@@ -487,9 +487,7 @@ class Membership
# 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
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
Membership
:
Membership
lms/static/coffee/src/instructor_dashboard/send_email.coffee
View file @
cb4025b1
...
...
@@ -6,8 +6,10 @@ wrap in (-> ... apply) to defer evaluation
such that the value can be defined later than this assignment (file load order).
###
# Load utilities
plantTimeout
=
->
window
.
InstructorDashboard
.
util
.
plantTimeout
.
apply
this
,
arguments
std_ajax_err
=
->
window
.
InstructorDashboard
.
util
.
std_ajax_err
.
apply
this
,
arguments
PendingInstructorTasks
=
->
window
.
InstructorDashboard
.
util
.
PendingInstructorTasks
class
SendEmail
constructor
:
(
@
$container
)
->
...
...
@@ -79,23 +81,25 @@ class SendEmail
class
Email
# enable subsections.
constructor
:
(
@
$section
)
->
# attach self to html
# so that instructor_dashboard.coffee can find this object
# to call event handlers like 'onClickTitle'
# attach self to html so that instructor_dashboard.coffee can find
# this object to call event handlers like 'onClickTitle'
@
$section
.
data
'wrapper'
,
@
# isolate # initialize SendEmail subsection
plantTimeout
0
,
=>
new
SendEmail
@
$section
.
find
'.send-email'
@
instructor_tasks
=
new
(
PendingInstructorTasks
())
@
$section
# handler for when the section title is clicked.
onClickTitle
:
->
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.
# abort if underscore can not be found.
if
_
?
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
Email
:
Email
lms/static/coffee/src/instructor_dashboard/student_admin.coffee
View file @
cb4025b1
...
...
@@ -6,10 +6,10 @@ 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
plantInterval
=
->
window
.
InstructorDashboard
.
util
.
plantInterval
.
apply
this
,
arguments
# Load utilities
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
PendingInstructorTasks
=
->
window
.
InstructorDashboard
.
util
.
PendingInstructorTasks
# get jquery element and assert its existance
...
...
@@ -21,57 +21,11 @@ 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
=
$
'<div/>'
,
class
:
'slickgrid'
$table_tasks
.
append
$table_placeholder
grid
=
new
Slick
.
Grid
(
$table_placeholder
,
table_data
,
columns
,
options
)
class
StudentAdmin
constructor
:
(
@
$section
)
->
# attach self to html so that instructor_dashboard.coffee can find
# this object to call event handlers like 'onClickTitle'
@
$section
.
data
'wrapper'
,
@
# gather buttons
...
...
@@ -93,22 +47,13 @@ class StudentAdmin
@
$btn_rescore_problem_all
=
@
$section
.
find
"input[name='rescore-problem-all']"
@
$btn_task_history_all
=
@
$section
.
find
"input[name='task-history-all']"
@
$table_task_history_all
=
@
$section
.
find
".task-history-all-table"
@
$table_running_tasks
=
@
$section
.
find
".running-tasks-table"
@
instructor_tasks
=
new
(
PendingInstructorTasks
())
@
$section
# response areas
@
$request_response_error_progress
=
find_and_assert
@
$section
,
".student-specific-container .request-response-error"
@
$request_response_error_grade
=
find_and_assert
@
$section
,
".student-grade-container .request-response-error"
@
$request_response_error_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,14 +239,6 @@ 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."
)
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
clear_errors_then
:
(
cb
)
->
@
$request_response_error_progress
.
empty
()
...
...
@@ -317,17 +254,15 @@ class StudentAdmin
@
$request_response_error_all
.
empty
()
# 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
onExit
:
->
@
task_poller
?
.
stop
()
onExit
:
->
@
instructor_tasks
.
task_poller
.
stop
()
# 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
,
_
.
defaults
window
,
InstructorDashboard
:
{}
_
.
defaults
window
.
InstructorDashboard
,
sections
:
{}
_
.
defaults
window
.
InstructorDashboard
.
sections
,
StudentAdmin
:
StudentAdmin
lms/static/coffee/src/instructor_dashboard/util.coffee
View file @
cb4025b1
...
...
@@ -6,6 +6,15 @@ plantTimeout = (ms, cb) -> setTimeout cb, ms
plantInterval
=
(
ms
,
cb
)
->
setInterval
cb
,
ms
# get jquery element and assert its existance
find_and_assert
=
(
$root
,
selector
)
->
item
=
$root
.
find
selector
if
item
.
length
!=
1
console
.
error
"element selection failed for '
#{
selector
}
' resulted in length
#{
item
.
length
}
"
throw
"Failed Element Selection"
else
item
# standard ajax error wrapper
#
# wraps a `handler` function so that first
...
...
@@ -17,6 +26,72 @@ 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'
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'
field
:
'requester'
name
:
'Requester'
minWidth
:
80
,
id
:
'created'
field
:
'created'
name
:
'Submitted'
minWidth
:
120
,
id
:
'duration_sec'
field
:
'duration_sec'
name
:
'Duration (sec)'
minWidth
:
80
,
id
:
'task_state'
field
:
'task_state'
name
:
'State'
minWidth
:
80
,
id
:
'status'
field
:
'status'
name
:
'Task Status'
minWidth
:
80
,
id
:
'task_message'
field
:
'task_message'
name
:
'Task Progress'
minWidth
:
120
]
table_data
=
tasks_data
$table_placeholder
=
$
'<div/>'
,
class
:
'slickgrid'
$table_tasks
.
append
$table_placeholder
grid
=
new
Slick
.
Grid
(
$table_placeholder
,
table_data
,
columns
,
options
)
# Helper class for managing the execution of interval tasks.
# Handles pausing and restarting.
class
IntervalManager
...
...
@@ -26,8 +101,8 @@ class IntervalManager
@
intervalID
=
null
# Start or restart firing every `ms` milliseconds.
# Soes not fire immediately.
start
:
->
@
fn
()
if
@
intervalID
is
null
@
intervalID
=
setInterval
@
fn
,
@
ms
...
...
@@ -37,6 +112,30 @@ class IntervalManager
@
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
# create parent namespaces if they do not already exist.
# abort if underscore can not be found.
...
...
@@ -47,3 +146,5 @@ if _?
plantInterval
:
plantInterval
std_ajax_err
:
std_ajax_err
IntervalManager
:
IntervalManager
create_task_list_table
:
create_task_list_table
PendingInstructorTasks
:
PendingInstructorTasks
lms/static/sass/course/instructor/_instructor_2.scss
View file @
cb4025b1
...
...
@@ -14,9 +14,16 @@
.olddash-button-wrapper
{
position
:
absolute
;
top
:
1
7
px
;
top
:
1
6
px
;
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
...
...
lms/templates/instructor/instructor_dashboard_2/course_info.html
View file @
cb4025b1
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
page
args=
"section_data"
/>
<h2>
${_("Course Information")}
</h2>
<div
class=
"enrollment-wrapper"
>
<h2>
${_("Enrollment Information")}
</h2>
<span
class=
"tip"
>
${_("Total number of enrollees (instructors, staff members, and students)")}
</span>
<br/><br/>
<span
style=
"color: green;"
><b>
${ section_data['enrollment_count'] }
</b></span>
<div
class=
"basic-data"
>
${_("Course Name")}:
${ section_data['course_display_name'] }
</div>
<hr>
<div
class=
"basic-data"
>
${_("Course ID")}:
${ section_data['course_id'] }
</div>
<div
class=
"basic-wrapper"
>
<h2>
${_("Basic Course Information")}
</h2>
<div
class=
"basic-data"
>
${_("Students Enrolled")}:
${ section_data['enrollment_count'] }
</div>
<ul
class=
"list-input"
>
<li
class=
"field text is-not-editable"
id=
"field-course-organization"
>
<label
for=
"course-organization"
>
${_("Organization:")}
</label>
<b>
${ section_data['course_org'] }
</b>
</li>
<div
class=
"basic-data
"
>
${_("Started")}:
${ section_data['has_started'] }
</div
>
<li
class=
"field text is-not-editable"
id=
"field-course-number
"
>
<label
for=
"course-number"
>
${_("Course Number:")}
</label>
<b>
${ section_data['course_num'] }
</b>
</li
>
<div
class=
"basic-data"
>
${_("Ended")}:
${ section_data['has_ended'] }
</div>
<li
class=
"field text is-not-editable"
id=
"field-course-name"
>
<label
for=
"course-name"
>
${_("Course Name:")}
</label>
<b>
${ section_data['course_name'] }
</b>
</li>
<li
class=
"field text is-not-editable"
id=
"field-course-display-name"
>
<label
for=
"course-display-name"
>
${_("Course Display Name:")}
</label>
<b>
${ section_data['course_display_name'] }
</b>
</li>
<li
class=
"field text is-not-editable"
id=
"field-course-started"
>
<label
for=
"start-date"
>
${_("Has the course started?")}
</label>
<div
class=
"basic-data"
>
${_("Grade Cutoffs")}:
${ section_data['grade_cutoffs'] }
<b>
${_("Yes") if section_data['grade_cutoffs'] else _("No")}
</b>
</li>
<li
class=
"field text is-not-editable"
id=
"field-course-ended"
>
<label
for=
"start-date"
>
${_("Has the course ended?")}
</label>
%if section_data['has_ended']:
<b>
${_("Yes")}
</b>
%else:
<b>
${_("No")}
</b>
%endif
</li>
<li
class=
"field text is-not-editable"
id=
"field-grade-cutoffs"
>
<label
for=
"start-date"
>
${_("Grade Cutoffs:")}
</label>
<b>
${ section_data['grade_cutoffs'] }
</b>
</li>
</ul>
</div>
##
<div
class=
"basic-data"
>
## Offline Grades Available:
## ${ section_data['offline_grades'] }
##
</div>
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS')
and section_data['access']['instructor']
:
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
<div
class=
"running-tasks-container action-type-container"
>
<hr>
<h2>
${_("Pending Instructor Tasks")}
</h2>
<p>
${_("The status for any active tasks appears in a table below.")}
</p>
<br
/>
<div
class=
"running-tasks-table"
data-endpoint=
"${ section_data['list_instructor_tasks_url'] }"
></div>
</div>
...
...
@@ -69,6 +90,3 @@
</div>
<br>
%endif
lms/templates/instructor/instructor_dashboard_2/data_download.html
View file @
cb4025b1
...
...
@@ -19,4 +19,16 @@
<div
class=
"data-display-text"
></div>
<div
class=
"data-display-table"
></div>
<div
class=
"request-response-error"
></div>
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
<div
class=
"running-tasks-container action-type-container"
>
<hr>
<h2>
${_("Pending Instructor Tasks")}
</h2>
<p>
${_("The status for any active tasks appears in a table below.")}
</p>
<br
/>
<div
class=
"running-tasks-table"
data-endpoint=
"${ section_data['list_instructor_tasks_url'] }"
></div>
</div>
%endif
</div>
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
cb4025b1
...
...
@@ -50,10 +50,14 @@
<section
class=
"container"
>
<div
class=
"instructor-dashboard-wrapper-2"
>
<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"
>
##
<h1>
Instructor Dashboard
</h1>
<h1>
${_("Instructor Dashboard")}
</h1>
<hr
/>
## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
...
...
lms/templates/instructor/instructor_dashboard_2/send_email.html
View file @
cb4025b1
...
...
@@ -54,4 +54,16 @@
<br
/>
<input
type=
"button"
name=
"send"
value=
"${_("
Send
Email
")}"
data-endpoint=
"${ section_data['send_email'] }"
>
<div
class=
"request-response-error"
></div>
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
<div
class=
"running-tasks-container action-type-container"
>
<hr>
<h2>
${_("Pending Instructor Tasks")}
</h2>
<p>
${_("The status for any active tasks appears in a table below.")}
</p>
<br
/>
<div
class=
"running-tasks-table"
data-endpoint=
"${ section_data['list_instructor_tasks_url'] }"
></div>
</div>
%endif
</div>
lms/templates/instructor/instructor_dashboard_2/student_admin.html
View file @
cb4025b1
...
...
@@ -109,3 +109,15 @@
</p>
</div>
%endif
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
<div
class=
"running-tasks-container action-type-container"
>
<hr>
<h2>
${_("Pending Instructor Tasks")}
</h2>
<p>
${_("The status for any active tasks appears in a table below.")}
</p>
<br
/>
<div
class=
"running-tasks-table"
data-endpoint=
"${ section_data['list_instructor_tasks_url'] }"
></div>
</div>
%endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment