add available column to the assignment index page

fixes CNVS-7671

test plan:
  - The availability date should display in six different formats
    - Blank (when there is no availability dates)
    - "Available" (when the assignment is unlocked, and there is no lock date)
    - "Not available until <date>" (when the assignment is not unlocked yet)
    - "Available until <date>" (when the assignment is unlocked and has a lock date)
    - "Closed" (when the locked date has passed)
    - "Multiple Availability" (when there are multiple availability dates)
  - Add different variations on availability dates for assignments
  - Check that the format appears correct

Change-Id: I9bbd416b50656dd67dc4ae2d391ec5b6a6283d5c
Reviewed-on: https://gerrit.instructure.com/24455
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Product-Review: Derek DeVries <ddevries@instructure.com>
This commit is contained in:
Derek DeVries 2013-09-17 11:23:35 -06:00
parent adb802cb22
commit 7bffcba259
13 changed files with 360 additions and 72 deletions

View File

@ -4,9 +4,10 @@ define [
'Backbone'
'compiled/backbone-ext/DefaultUrlMixin'
'compiled/models/TurnitinSettings'
'compiled/models/DateGroup'
'compiled/collections/AssignmentOverrideCollection'
'compiled/collections/DateGroupCollection'
], ($, _, {Model}, DefaultUrlMixin, TurnitinSettings, AssignmentOverrideCollection, DateGroupCollection) ->
], ($, _, {Model}, DefaultUrlMixin, TurnitinSettings, DateGroup, AssignmentOverrideCollection, DateGroupCollection) ->
class Assignment extends Model
@mixin DefaultUrlMixin
@ -211,6 +212,12 @@ define [
labelId: =>
return @id
defaultDates: =>
group = new DateGroup
due_at: @get("due_at")
unlock_at: @get("unlock_at")
lock_at: @get("lock_at")
multipleDueDates: =>
dateGroups = @get("all_dates")
dateGroups && dateGroups.length > 1
@ -218,16 +225,7 @@ define [
allDates: =>
groups = @get("all_dates")
models = (groups and groups.models) or []
result = _.map models, (group) ->
due = group.get("due_at")
unlock = group.get("unlock_at")
lock = group.get("lock_at")
dueAt: if due then new Date(due) else null
dueFor: group.get("title")
unlockAt: if unlock then new Date(unlock) else null
lockAt: if lock then new Date(lock) else null
result = _.map models, (group) -> group.toJSON()
toView: =>
fields = [

View File

@ -12,3 +12,53 @@ define [
due_at: null
unlock_at: null
lock_at: null
dueAt: ->
dueAt = @get("due_at")
if dueAt then Date.parse(dueAt) else null
unlockAt: ->
unlockAt = @get("unlock_at")
if unlockAt then Date.parse(unlockAt) else null
lockAt: ->
lockAt = @get("lock_at")
if lockAt then Date.parse(lockAt) else null
now: ->
now = @get("now")
if now then Date.parse(now) else new Date()
# no lock/unlock dates
alwaysAvailable: ->
!@unlockAt() && !@lockAt()
# not unlocked yet
pending: ->
unlockAt = @unlockAt()
unlockAt && unlockAt > @now()
# available and won't ever lock
available: ->
@alwaysAvailable() || (!@lockAt() && @unlockAt() < @now())
# available, but will lock at some point
open: ->
@lockAt() && !@pending() && !@closed()
# locked
closed: ->
lockAt = @lockAt()
lockAt && lockAt < @now()
toJSON: ->
dueFor: @get("title")
dueAt: @dueAt()
unlockAt: @unlockAt()
lockAt: @lockAt()
available: @available()
pending: @pending()
open: @open()
closed: @closed()

View File

@ -3,19 +3,21 @@ define [
'Backbone'
'underscore'
'compiled/views/PublishIconView'
'compiled/views/VddTooltipView'
'compiled/views/assignments/DateDueColumnView'
'compiled/views/assignments/DateAvailableColumnView'
'compiled/views/assignments/CreateAssignmentView'
'compiled/fn/preventDefault'
'jst/assignments/AssignmentListItem'
], (I18n, Backbone, _, PublishIconView, VddTooltipView, CreateAssignmentView, preventDefault, template) ->
], (I18n, Backbone, _, PublishIconView, DateDueColumnView, DateAvailableColumnView, CreateAssignmentView, preventDefault, template) ->
class AssignmentListItemView extends Backbone.View
tagName: "li"
className: "assignment"
template: template
@child 'publishIconView', '[data-view=publish-icon]'
@child 'vddDueTooltipView', '[data-view=vdd-due-tooltip]'
@child 'editAssignmentView', '[data-view=editAssignment]'
@child 'publishIconView', '[data-view=publish-icon]'
@child 'dateDueColumnView', '[data-view=date-due]'
@child 'dateAvailableColumnView', '[data-view=date-available]'
@child 'editAssignmentView', '[data-view=edit-assignment]'
els:
'.edit_assignment': '$editAssignmentButton'
@ -40,25 +42,27 @@ define [
@model.on(observe, @render)
initializeChildViews: ->
@publishIconView = false
@publishIconView = false
@editAssignmentView = false
@vddDueTooltipView = false
@vddDueColumnView = false
@dateAvailableColumnView = false
if @canManage()
@publishIconView = new PublishIconView(model: @model)
@editAssignmentView = new CreateAssignmentView(model: @model)
if @model.multipleDueDates()
@vddDueTooltipView = new VddTooltipView(model: @model)
@dateDueColumnView = new DateDueColumnView(model: @model)
@dateAvailableColumnView = new DateAvailableColumnView(model: @model)
updatePublishState: =>
@$('.ig-row').toggleClass('ig-published', @model.get('published'))
# call remove on children so that they can clean up old dialogs.
render: ->
@publishIconView.remove() if @publishIconView
@editAssignmentView.remove() if @editAssignmentView
@vddDueTooltipView.remove() if @vddDueTooltipView
@publishIconView.remove() if @publishIconView
@editAssignmentView.remove() if @editAssignmentView
@dateDueColumnView.remove() if @dateDueColumnView
@dateAvailableColumnView.remove() if @dateAvailableColumnView
super
# reset the model's view property; it got overwritten by child views

View File

@ -0,0 +1,34 @@
define [
'i18n!assignments'
'Backbone'
'jst/assignments/DateAvailableColumnView'
'jquery'
'underscore'
'compiled/behaviors/tooltip'
], (I18n, Backbone, template, $, _) ->
class DateAvailableColumnView extends Backbone.View
template: template
els:
'.vdd_tooltip_link': '$link'
afterRender: ->
@$link.tooltip
position: {my: 'center bottom', at: 'center top-10', collision: 'fit fit'},
tooltipClass: 'center bottom vertical',
content: -> $($(@).data('tooltipSelector')).html()
toJSON: ->
group = @model.defaultDates()
data = @model.toView()
data.defaultDates = group.toJSON()
data.canManage = @canManage()
data.selector = @model.get("id") + "_lock"
data.linkHref = @model.htmlUrl()
data.allDates = @model.allDates()
data
canManage: ->
ENV.PERMISSIONS.manage

View File

@ -1,12 +1,12 @@
define [
'i18n!assignments'
'Backbone'
'jst/VddTooltipView'
'jst/assignments/DateDueColumnView'
'jquery'
'compiled/behaviors/tooltip'
], (I18n, Backbone, template, $) ->
class VddTooltipView extends Backbone.View
class DateDueColumnView extends Backbone.View
template: template
els:
@ -19,8 +19,12 @@ define [
content: -> $($(@).data('tooltipSelector')).html()
toJSON: ->
base = super
base.selector = @model.get("id")
base.linkHref = @model.htmlUrl()
base.allDates = @model.allDates()
base
data = @model.toView()
data.canManage = @canManage()
data.selector = @model.get("id") + "_due"
data.linkHref = @model.htmlUrl()
data.allDates = @model.allDates()
data
canManage: ->
ENV.PERMISSIONS.manage

View File

@ -399,17 +399,20 @@ table.full_assignment_table {
.vdd_tooltip_link {
cursor: pointer
}
.ui-widget.ui-tooltip {
max-width: 240px;
}
.dl-horizontal.vdd_tooltip_content {
margin: 7px 0;
dt {
width: 95px;
width: 115px;
white-space: normal;
line-height: 12px;
margin-bottom: 5px;
}
dd {
margin-left: 105px;
width: 95px;
margin-left: 125px;
width: 115px;
line-height: 12px;
text-align: left;
}

View File

@ -26,6 +26,10 @@
tfoot td
font-weight: bold
.assignment
.ig-details
width: 590px
.ag-weights-tr
border-bottom: 1px solid #dae1e6

View File

@ -1,20 +0,0 @@
<a title class="vdd_tooltip_link" data-tooltip-selector="#vdd_tooltip_{{selector}}"
{{#if linkHref}}href="{{linkHref}}"{{/if}}
>{{#t "multiple_dates"}}Multiple Dates{{/t}}</a>
<div id="vdd_tooltip_{{selector}}" style="display:none;">
<dl class="vdd_tooltip_content dl-horizontal">
{{#each allDates}}
<div class="clearfix">
<dt>{{dueFor}}</dt>
<dd>{{#if dueAt}} {{strftime dueAt "%b %-d"}} {{else}} - {{/if}}</dd>
</div>
{{/each}}
{{#if moreMessage}}
<div class="clearfix">
<dd>{{moreMessage}}</dd>
</div>
{{/if}}
</dl>
</div>

View File

@ -14,7 +14,7 @@
</a>
<div class="ig-details row-fluid">
{{#if canManage}}
<div class="span4 ellipses modules">
<div class="span3 ellipses modules">
{{#if has_modules}}
{{#ifEqual module_count 1}}
{{module_name}} {{#t "module"}}Module{{/t}}
@ -35,20 +35,10 @@
</div>
{{/if}}
<div class="span4 ellipses">
{{#if multipleDueDates}}
<strong>{{#t "quiz_due"}}Due{{/t}}</strong>
<span data-view="vdd-due-tooltip"></span>
<div class="span3 ellipses" data-view="date-available"></div>
<div class="span3 ellipses" data-view="date-due"></div>
{{else}}
{{#if dueAt}}
<strong>{{#t "due_date"}}Due{{/t}}</strong>
{{datetimeFormatted dueAt}}
{{/if}}
{{/if}}
</div>
<div class="span4 ellipses">
<div class="span3 ellipses">
{{#if pointsPossible}}{{#t "points_possible"}}{{pointsPossible}} pts{{/t}}{{/if}}
</div>
</div>
@ -103,6 +93,6 @@
</ul>
</div>
<form data-view="editAssignment" class="form-dialog"></form>
<form data-view="edit-assignment" class="form-dialog"></form>
{{/if}}
</div>

View File

@ -38,7 +38,9 @@
</label>
<div class="controls">
{{#if multipleDueDates}}
<span class="datetime_field multiple_due_dates" title="vdd_tooltip" data-tooltip-selector="#vdd_tooltip_{{uniqLabel}}">
<span class="datetime_field multiple_due_dates" title
data-tooltip-selector="#vdd_tooltip_{{uniqLabel}}"
aria-labelledby="vdd_tooltip_{{uniqLabel}}">
<input
class="input-medium"
type="text"

View File

@ -0,0 +1,55 @@
{{#if multipleDueDates}}
<strong>{{#t "available"}}Available{{/t}}</strong>
<a title class="vdd_tooltip_link"
aria-labelledby="vdd_tooltip_{{selector}}"
data-tooltip-selector="#vdd_tooltip_{{selector}}"
{{#if linkHref}}href="{{linkHref}}"{{/if}}
>{{#t "multiple_dates"}}Multiple Dates{{/t}}</a>
<div id="vdd_tooltip_{{selector}}" style="display:none;">
<dl class="vdd_tooltip_content dl-horizontal">
{{#each allDates}}
<div class="clearfix">
<dt>{{dueFor}}</dt>
<dd>
<dd>
{{#if available}}
{{#t "available"}}Available{{/t}}
{{/if}}
{{#if pending}}
{{#t "available_on"}}Available on{{/t}}
{{strftime unlockAt "%b %-d"}}
{{/if}}
{{#if open}}
{{#t "available_until"}}Available until{{/t}}
{{strftime lockAt "%b %-d"}}
{{/if}}
{{#if closed}}
{{#t "closed"}}Closed{{/t}}
{{/if}}
</dd>
</dd>
</div>
{{/each}}
</dl>
</div>
{{else}}
{{#if defaultDates.pending}}
<strong>{{#t "not_available_until"}}Not available until{{/t}}</strong>
{{strftime defaultDates.unlockAt "%b %-d"}}
{{/if}}
{{#if defaultDates.open}}
<strong>{{#t "available_until"}}Available until{{/t}}</strong>
{{strftime defaultDates.lockAt "%b %-d"}}
{{/if}}
{{#if defaultDates.closed}}
<strong>{{#t "closed"}}Closed{{/t}}</strong>
{{/if}}
{{/if}}

View File

@ -0,0 +1,26 @@
{{#if multipleDueDates}}
<strong>{{#t "due"}}Due{{/t}}</strong>
<a title class="vdd_tooltip_link"
aria-labelledby="vdd_tooltip_{{selector}}"
data-tooltip-selector="#vdd_tooltip_{{selector}}"
{{#if linkHref}}href="{{linkHref}}"{{/if}}
>{{#t "multiple_dates"}}Multiple Dates{{/t}}</a>
<div id="vdd_tooltip_{{selector}}" style="display:none;">
<dl class="vdd_tooltip_content dl-horizontal">
{{#each allDates}}
<div class="clearfix">
<dt>{{dueFor}}</dt>
<dd>{{#if dueAt}} {{strftime dueAt "%b %-d"}} {{else}} - {{/if}}</dd>
</div>
{{/each}}
</dl>
</div>
{{else}}
{{#if dueAt}}
<strong>{{#t "due_date"}}Due{{/t}}</strong>
{{datetimeFormatted dueAt}}
{{/if}}
{{/if}}

View File

@ -8,8 +8,146 @@ define [
test 'default title is set', ->
dueAt = new Date("2013-08-20 11:13:00")
model = new DateGroup due_at: dueAt, title: "Summer session"
model = new DateGroup(due_at: dueAt, title: "Summer session")
equal model.get("title"), 'Summer session'
model = new DateGroup due_at: dueAt
model = new DateGroup(due_at: dueAt)
equal model.get("title"), 'Everyone else'
test "#dueAt parses due_at to a date", ->
model = new DateGroup(due_at: "2013-08-20 11:13:00")
equal model.dueAt().constructor, Date
test "#dueAt doesn't parse null date", ->
model = new DateGroup(due_at: null)
equal model.dueAt(), null
test "#unlockAt parses unlock_at to a date", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00")
equal model.unlockAt().constructor, Date
test "#unlockAt doesn't parse null date", ->
model = new DateGroup(unlock_at: null)
equal model.unlockAt(), null
test "#lockAt parses lock_at to a date", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00")
equal model.lockAt().constructor, Date
test "#lockAt doesn't parse null date", ->
model = new DateGroup(lock_at: null)
equal model.lockAt(), null
test "#alwaysAvailable if both unlock and lock dates aren't set", ->
model = new DateGroup(unlock_at: null, lock_at: null)
ok model.alwaysAvailable()
test "#alwaysAvailable is false if unlock date is set", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", lock_at: null)
ok !model.alwaysAvailable()
test "#alwaysAvailable is false if lock date is set", ->
model = new DateGroup(unlock_at: null, lock_at: "2013-08-20 11:13:00")
ok !model.alwaysAvailable()
test "#available is true if always available", ->
model = new DateGroup(unlock_at: null, lock_at: null)
ok model.available()
test "#available is true if no lock date and unlock date has passed", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-30 00:00:00")
ok model.available()
test "#available is false if not unlocked yet", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-19 00:00:00")
ok !model.available()
test "#available is false if locked", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-30 00:00:00")
ok !model.available()
test "#pending is true if not unlocked yet", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-19 00:00:00")
ok model.pending()
test "#pending is false if no unlock date", ->
model = new DateGroup(unlock_at: null)
ok !model.pending()
test "#pending is false if unlocked", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-30 00:00:00")
ok !model.pending()
test "#open is true if has a lock date but not locked yet", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-10 00:00:00")
ok model.open()
test "#open is false without an unlock date", ->
model = new DateGroup(unlock_at: null)
ok !model.open()
test "#open is false if not unlocked yet", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-19 00:00:00")
ok !model.open()
test "#closed is true if not locked", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-30 00:00:00")
ok model.closed()
test "#closed is false if no lock date", ->
model = new DateGroup(lock_at: null)
ok !model.closed()
test "#closed is false if unlocked has passed", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-19 00:00:00")
ok !model.closed()
test "#toJSON includes dueFor", ->
model = new DateGroup(title: "Summer session")
json = model.toJSON()
equal json.dueFor, "Summer session"
test "#toJSON includes dueAt", ->
model = new DateGroup(due_at: "2013-08-20 11:13:00")
json = model.toJSON()
equal json.dueAt.constructor, Date
test "#toJSON includes unlockAt", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00")
json = model.toJSON()
equal json.unlockAt.constructor, Date
test "#toJSON includes lockAt", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00")
json = model.toJSON()
equal json.lockAt.constructor, Date
test "#toJSON includes available", ->
model = new DateGroup
json = model.toJSON()
equal json.available, true
test "#toJSON includes pending", ->
model = new DateGroup(unlock_at: "2013-08-20 11:13:00", now: "2013-08-19 00:00:00")
json = model.toJSON()
equal json.pending, true
test "#toJSON includes open", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-10 00:00:00")
json = model.toJSON()
equal json.open, true
test "#toJSON includes closed", ->
model = new DateGroup(lock_at: "2013-08-20 11:13:00", now: "2013-08-30 00:00:00")
json = model.toJSON()
equal json.closed, true