Skip to content

Commit e663f88

Browse files
author
Stanley Stuart
committed
draft state: add assignment scores to the assignments index page
Test Plan: - Enable draft state on your account. - As a teacher, create a few assignments in the course. At least one should have points possible, and one should have no points possible. - As a student, submit / complete a few of the assignments in the course. - As a teacher, grade the assignments. Make sure one assignment is left ungraded. - As a student, loading the assignments index page (/courses/:course_id/assignments) should show you your scores. - Make sure the content is accessible with a screenreader! also does some miscellaneous qunit spec fixes fixes CNVS-7203 Change-Id: I69e8896fc96bed5ec2978370b4115f491ac66071 Reviewed-on: https://gerrit.instructure.com/23570 Reviewed-by: Simon Williams <simon@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Caleb Guanzon <cguanzon@instructure.com> Product-Review: Stanley Stuart <stanley@instructure.com>
1 parent d7ef4fa commit e663f88

12 files changed

Lines changed: 287 additions & 16 deletions

File tree

app/coffeescripts/bundles/assignment_index.coffee

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ require [
4242
params:
4343
include: includes
4444
override_assignment_dates: !ENV.PERMISSIONS.manage
45+
courseSubmissionsURL: ENV.URLS.course_student_submissions_url
4546

4647
assignmentGroupsView = new AssignmentGroupListView
4748
collection: assignmentGroups
@@ -67,16 +68,18 @@ require [
6768
course: course
6869
assignmentGroups: assignmentGroups
6970

70-
@app = new IndexView
71+
app = new IndexView
7172
assignmentGroupsView: assignmentGroupsView
7273
assignmentSettingsView: assignmentSettingsView
7374
createGroupView: createGroupView
7475
showByView: showByView
7576
collection: assignmentGroups
7677

77-
@app.render()
78+
app.render()
7879

7980
# kick it all off
8081
assignmentGroups.fetch(reset: true).then ->
8182
if ENV.PERMISSIONS.manage
8283
assignmentGroups.loadModuleNames()
84+
else
85+
assignmentGroups.getGrades()

app/coffeescripts/collections/AssignmentGroupCollection.coffee

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
define [
2-
'underscore'
32
'Backbone'
43
'compiled/models/AssignmentGroup'
5-
], (_, Backbone, AssignmentGroup) ->
4+
'underscore'
5+
'i18n!assignments'
6+
'compiled/collections/PaginatedCollection'
7+
], (Backbone, AssignmentGroup, _, I18n, PaginatedCollection) ->
8+
9+
PER_PAGE_LIMIT = 50
610

711
class AssignmentGroupCollection extends Backbone.Collection
812

913
model: AssignmentGroup
1014

1115
@optionProperty 'course'
16+
@optionProperty 'courseSubmissionsURL'
1217

1318
# TODO: this will also return the assignments discussion_topic if it is of
1419
# that type, which we don't need.
@@ -34,3 +39,32 @@ define [
3439
.value()
3540

3641
comparator: 'position'
42+
43+
getGrades: ->
44+
collection = new PaginatedCollection
45+
collection.url = => "#{@courseSubmissionsURL}?per_page=#{PER_PAGE_LIMIT}"
46+
collection.loadAll = true
47+
collection.on 'fetched:last', =>
48+
@loadGradesFromSubmissions(collection.toArray())
49+
collection.fetch()
50+
51+
loadGradesFromSubmissions: (submissions) ->
52+
submissionsHash = {}
53+
for submission in submissions
54+
submissionsHash[submission.get('assignment_id')] = submission
55+
56+
for assignment in @assignments()
57+
submission = submissionsHash[assignment.get('id')]
58+
if submission
59+
if submission.get('grade')?
60+
grade = parseFloat submission.get('grade')
61+
# may be a letter grade like 'A-'
62+
if !isNaN grade
63+
submission.set 'grade', grade
64+
else
65+
submission.set 'notYetGraded', true
66+
assignment.set 'submission', submission
67+
else
68+
# manually trigger a change so the UI can update appropriately.
69+
assignment.set 'submission', null
70+
assignment.trigger 'change:submission'

app/coffeescripts/views/assignments/AssignmentListItemView.coffee

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ define [
99
'compiled/views/MoveDialogView'
1010
'compiled/fn/preventDefault'
1111
'jst/assignments/AssignmentListItem'
12-
], (I18n, Backbone, _, PublishIconView, DateDueColumnView, DateAvailableColumnView, CreateAssignmentView, MoveDialogView, preventDefault, template) ->
12+
'jst/assignments/_assignmentListItemScore'
13+
'compiled/util/round'
14+
'jqueryui/tooltip'
15+
'compiled/behaviors/tooltip'
16+
], (I18n, Backbone, _, PublishIconView, DateDueColumnView, DateAvailableColumnView, CreateAssignmentView, MoveDialogView, preventDefault, template, scoreTemplate, round) ->
1317

1418
class AssignmentListItemView extends Backbone.View
1519
tagName: "li"
@@ -50,6 +54,7 @@ define [
5054
attrs = ["name", "points_possible", "due_at", "lock_at", "unlock_at", "modules"]
5155
observe = _.map(attrs, (attr) -> "change:#{attr}").join(" ")
5256
@model.on(observe, @render)
57+
@model.on 'change:submission', @updateScore
5358

5459
initializeChildViews: ->
5560
@publishIconView = false
@@ -100,6 +105,8 @@ define [
100105
@moveAssignmentView.hide()
101106
@moveAssignmentView.setTrigger @$moveAssignmentButton
102107

108+
@updateScore() unless @canManage()
109+
103110
toggleHidden: (model, hidden) =>
104111
@$el.toggleClass('hidden', hidden)
105112
@$el.toggleClass('search_show', !hidden)
@@ -119,10 +126,19 @@ define [
119126
toJSON: ->
120127
data = @model.toView()
121128
data.canManage = @canManage()
129+
data = @_setJSONForGrade(data) unless data.canManage
130+
122131
# can move items if there's more than one parent
123132
# collection OR more than one in the model's collection
124133
data.canMove = @model.collection.view?.parentCollection?.length > 1 or @model.collection.length > 1
125134

135+
if data.canManage
136+
data.spanWidth = 'span3'
137+
data.alignTextClass = ''
138+
else
139+
data.spanWidth = 'span4'
140+
data.alignTextClass = 'align-right'
141+
126142
if modules = @model.get('modules')
127143
moduleName = modules[0]
128144
has_modules = modules.length > 0
@@ -147,3 +163,24 @@ define [
147163

148164
canManage: ->
149165
ENV.PERMISSIONS.manage
166+
167+
_setJSONForGrade: (json) ->
168+
if submission = @model.get('submission')
169+
submissionJSON = submission.toJSON()
170+
grade = submission.get('grade')
171+
if typeof grade is 'number' && !isNaN(grade)
172+
submissionJSON.grade = round grade, round.DEFAULT
173+
json.submission = submissionJSON
174+
pointsPossible = json.pointsPossible
175+
176+
if typeof pointsPossible is 'number' && !isNaN(pointsPossible)
177+
json.pointsPossible = round pointsPossible, round.DEFAULT
178+
json.submission.pointsPossible = json.pointsPossible if json.submission?
179+
180+
json
181+
182+
updateScore: =>
183+
json = @model.toView()
184+
json = @_setJSONForGrade(json) unless @canManage()
185+
186+
@$('.js-score').html scoreTemplate(json)

app/coffeescripts/views/content_migrations/ProgressStatusView.coffee

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ define [
88
@progress = @model.progressModel
99

1010
render: ->
11-
if statusView = @model.collection.view.getStatusView(@model)
11+
if statusView = @model.collection?.view?.getStatusView(@model)
1212
@$el.html(statusView)
1313
else
1414
super

app/controllers/assignments_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def index
4646
:sort_url => reorder_course_assignment_groups_url,
4747
:assignment_sort_base_url => course_assignment_groups_url,
4848
:context_modules_url => api_v1_course_context_modules_path(@context),
49+
:course_student_submissions_url => api_v1_course_student_submissions_url(@context)
4950
},
5051
:PERMISSIONS => permissions,
5152
})

app/views/jst/assignments/AssignmentListItem.handlebars

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</a>
1515
<div class="ig-details row-fluid">
1616
{{#if canManage}}
17-
<div class="span3 ellipses modules">
17+
<div class="{{spanWidth}} ellipses modules">
1818
{{#if has_modules}}
1919
{{#ifEqual module_count 1}}
2020
{{module_name}} {{#t "module"}}Module{{/t}}
@@ -35,12 +35,22 @@
3535
</div>
3636
{{/if}}
3737

38-
<div class="span3 ellipses" data-view="date-available"></div>
39-
<div class="span3 ellipses" data-view="date-due"></div>
38+
<div class="{{spanWidth}} ellipses" data-view="date-available"></div>
39+
<div class="{{spanWidth}} ellipses" data-view="date-due"></div>
4040

41-
<div class="span3 ellipses">
42-
{{#if pointsPossible}}{{#t "points_possible"}}{{pointsPossible}} pts{{/t}}{{/if}}
41+
<div class="{{spanWidth}} ellipses js-score {{alignTextClass}}">
42+
<span class="screenreader-only">
43+
{{#if pointsPossible}}
44+
{{#t "points_possible_screen_reader"}}{{pointsPossible}} Points Possible{{/t}}
45+
{{else}}
46+
{{#t "no_points_possible"}}No Points Possible{{/t}}
47+
{{/if}}
48+
</span>
49+
<span class="non-screenreader" aria-hidden="true">
50+
{{#if pointsPossible}}{{#t "points_possible"}}{{pointsPossible}} pts{{/t}}{{/if}}
51+
</span>
4352
</div>
53+
4454
</div>
4555

4656
{{#if canManage}}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<span class="non-screenreader" aria-hidden=true>
2+
3+
{{#unless submission}}
4+
5+
<span data-tooltip="" title='{{#t "no_submission"}}No Submission{{/t}}'>
6+
{{#t "no_submission_with_points_possible"}}
7+
-/{{pointsPossible}} pts
8+
{{/t}}
9+
</span>
10+
11+
{{else}}
12+
{{#with submission}}
13+
{{#if notYetGraded}}
14+
15+
{{#t "not_yet_graded_with_points_possible"}}
16+
<em>Not Yet Graded</em>/{{pointsPossible}} pts
17+
{{/t}}
18+
19+
{{else}}
20+
21+
<strong>
22+
{{#t "score_with_points_possible"}}
23+
{{grade}}/{{pointsPossible}} pts
24+
{{/t}}
25+
</strong>
26+
27+
{{/if}}
28+
29+
{{/with}}
30+
31+
{{/unless}}
32+
33+
</span>
34+
35+
<span class="screenreader-only">
36+
37+
{{#unless submission}}
38+
39+
{{#t "no_submission_for_assignment_screenreader"}}
40+
No submission for this assignment. {{pointsPossible}} points possible.
41+
{{/t}}
42+
43+
{{else}}
44+
{{#with submission}}
45+
{{#if notYetGraded}}
46+
47+
{{#t "assignment_not_yet_graded_screenreader"}}
48+
Assignment not yet graded. {{pointsPossible}} points possible.
49+
{{/t}}
50+
51+
{{else}}
52+
53+
{{#t "score_with_points_possible_screenreader"}}
54+
Score: {{grade}} out of {{pointsPossible}} points
55+
{{/t}}
56+
57+
{{/if}}
58+
59+
{{/with}}
60+
61+
{{/unless}}
62+
63+
</span>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
define [
2+
'compiled/models/AssignmentGroup'
3+
'compiled/models/Assignment'
4+
'compiled/collections/AssignmentGroupCollection'
5+
'compiled/models/Course'
6+
], (AssignmentGroup, Assignment, AssignmentGroupCollection, Course) ->
7+
8+
COURSE_SUBMISSIONS_URL = "/courses/1/submissions"
9+
10+
module "AssignmentGroupCollection",
11+
setup: ->
12+
@server = sinon.fakeServer.create()
13+
@assignments = (new Assignment({id: id}) for id in [1..4])
14+
@group = new AssignmentGroup assignments: @assignments
15+
@collection = new AssignmentGroupCollection [@group],
16+
courseSubmissionsURL: COURSE_SUBMISSIONS_URL
17+
18+
teardown: ->
19+
@server.restore()
20+
21+
test "::model is AssignmentGroup", ->
22+
strictEqual AssignmentGroupCollection::model, AssignmentGroup
23+
24+
test "default params include assignments and not discussion topics", ->
25+
{include} = AssignmentGroupCollection::defaults.params
26+
deepEqual include, ["assignments"], "include only contains assignments"
27+
28+
test "optionProperties", ->
29+
course = new Course
30+
collection = new AssignmentGroupCollection [],
31+
course: course
32+
courseSubmissionsURL: COURSE_SUBMISSIONS_URL
33+
34+
strictEqual collection.courseSubmissionsURL, COURSE_SUBMISSIONS_URL,
35+
"assigns courseSubmissionsURL to this.courseSubmissionsURL"
36+
37+
strictEqual collection.course, course, "assigns course to this.course"
38+
39+
test "(#getGrades) loading grades from the server", ->
40+
triggeredChangeForAssignmentWithoutSubmission = false
41+
submissions = ({id: id, assignment_id: id, grade: id} for id in [1..3])
42+
@server.respondWith "GET", "#{COURSE_SUBMISSIONS_URL}?per_page=50", [
43+
200,
44+
{ "Content-Type": "application/json" },
45+
JSON.stringify(submissions)
46+
]
47+
48+
lastAssignment = @assignments[3]
49+
lastAssignment.on 'change:submission', ->
50+
triggeredChangeForAssignmentWithoutSubmission = true
51+
52+
@collection.getGrades()
53+
@server.respond()
54+
55+
for assignment in @assignments
56+
continue if assignment.get("id") is 4
57+
equal assignment.get("submission").get("grade"), assignment.get("id"),
58+
"sets submission grade for assignments with a matching submission"
59+
60+
ok triggeredChangeForAssignmentWithoutSubmission,
61+
"""
62+
triggers change for assignments without a matching submission grade
63+
so the UI can update
64+
"""
65+

spec/coffeescripts/views/MoveDialogSelectSpec.coffee

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ define [
1818
module 'MoveDialogSelect',
1919
setup: ->
2020
@set_coll_spy = sinon.spy MoveDialogSelect.prototype, 'setCollection'
21-
@render_spy = sinon.spy MoveDialogSelect.prototype, 'render'
2221

2322
@assignments = new Assignments((id: i, name: "Assignment #{i}") for i in [1..3])
2423
@view = new MoveDialogSelect
@@ -28,7 +27,6 @@ define [
2827

2928
teardown: ->
3029
@set_coll_spy.restore()
31-
@render_spy.restore()
3230

3331
test '#initialize, if a collection is not passed the model\'s collection will be used for @collection', ->
3432
deepEqual @view.model.collection, @assignments
@@ -63,10 +61,11 @@ define [
6361
ok @set_coll_spy.returned(undefined)
6462

6563
test '#setCollection changes the value of @collection', ->
64+
spy = sinon.spy @view, 'renderOptions'
6665
other_assignments = new Assignments((id: i, name: "Assignment #{i}") for i in [5..9])
6766
@view.setCollection(other_assignments)
6867
deepEqual @view.collection, other_assignments
69-
ok @render_spy.called
68+
ok spy.called
7069

7170
test '#toJSON returns an object that can be used for rendering the select', ->
7271
expected = @view.model.toView()

0 commit comments

Comments
 (0)