diff --git a/spec/coffeescripts/collections/OutcomeResultCollectionSpec.js b/spec/coffeescripts/collections/OutcomeResultCollectionSpec.js
index 153c08e3534..daa1c8890b6 100644
--- a/spec/coffeescripts/collections/OutcomeResultCollectionSpec.js
+++ b/spec/coffeescripts/collections/OutcomeResultCollectionSpec.js
@@ -18,7 +18,7 @@
import Backbone from '@canvas/backbone'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee'
+import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection'
import fakeENV from 'helpers/fakeENV'
import tz from '@canvas/timezone'
@@ -96,6 +96,7 @@ QUnit.module('OutcomeResultCollectionSpec', {
})
test('default params reflect aligned outcome', function () {
+ // eslint-disable-next-line new-cap
const collectionModel = new this.outcomeResultCollection.model()
deepEqual(collectionModel.get('mastery_points'), 8)
deepEqual(collectionModel.get('points_possible'), 10)
diff --git a/spec/coffeescripts/views/grade_summary/OutcomeDetailViewSpec.js b/spec/coffeescripts/views/grade_summary/OutcomeDetailViewSpec.js
index 78136b9ca17..e85e6706052 100644
--- a/spec/coffeescripts/views/grade_summary/OutcomeDetailViewSpec.js
+++ b/spec/coffeescripts/views/grade_summary/OutcomeDetailViewSpec.js
@@ -18,9 +18,9 @@
import Backbone from '@canvas/backbone'
import CollectionView from '@canvas/backbone-collection-view'
-import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee'
+import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import Group from 'ui/features/grade_summary/backbone/models/Group.coffee'
+import Group from 'ui/features/grade_summary/backbone/models/Group'
import OutcomeDetailView from 'ui/features/grade_summary/backbone/views/OutcomeDetailView'
import fakeENV from 'helpers/fakeENV'
diff --git a/spec/coffeescripts/views/grade_summary/OutcomeDialogViewSpec.js b/spec/coffeescripts/views/grade_summary/OutcomeDialogViewSpec.js
index db2895995f4..504f8574a03 100644
--- a/spec/coffeescripts/views/grade_summary/OutcomeDialogViewSpec.js
+++ b/spec/coffeescripts/views/grade_summary/OutcomeDialogViewSpec.js
@@ -16,10 +16,11 @@
* with this program. If not, see .
*/
+import $ from 'jquery'
import _ from 'underscore'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import OutcomeDialogView from 'ui/features/grade_summary/backbone/views/OutcomeDialogView.coffee'
-import OutcomeLineGraphView from 'ui/features/grade_summary/backbone/views/OutcomeLineGraphView.coffee'
+import OutcomeDialogView from 'ui/features/grade_summary/backbone/views/OutcomeDialogView'
+import OutcomeLineGraphView from 'ui/features/grade_summary/backbone/views/OutcomeLineGraphView'
QUnit.module('OutcomeDialogViewSpec', {
setup() {
diff --git a/spec/coffeescripts/views/grade_summary/OutcomeLineGraphViewSpec.js b/spec/coffeescripts/views/grade_summary/OutcomeLineGraphViewSpec.js
index 62082a2c488..c381870cb17 100644
--- a/spec/coffeescripts/views/grade_summary/OutcomeLineGraphViewSpec.js
+++ b/spec/coffeescripts/views/grade_summary/OutcomeLineGraphViewSpec.js
@@ -16,10 +16,11 @@
* with this program. If not, see .
*/
+import $ from 'jquery'
import {isUndefined} from 'lodash'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee'
-import OutcomeLineGraphView from 'ui/features/grade_summary/backbone/views/OutcomeLineGraphView.coffee'
+import OutcomeResultCollection from 'ui/features/grade_summary/backbone/collections/OutcomeResultCollection'
+import OutcomeLineGraphView from 'ui/features/grade_summary/backbone/views/OutcomeLineGraphView'
import tz from '@canvas/timezone'
import fakeENV from 'helpers/fakeENV'
diff --git a/spec/coffeescripts/views/grade_summary/OutcomePopoverViewSpec.js b/spec/coffeescripts/views/grade_summary/OutcomePopoverViewSpec.js
index 71451061e44..5b1a89a4777 100644
--- a/spec/coffeescripts/views/grade_summary/OutcomePopoverViewSpec.js
+++ b/spec/coffeescripts/views/grade_summary/OutcomePopoverViewSpec.js
@@ -20,7 +20,7 @@ import $ from 'jquery'
import {isUndefined} from 'lodash'
import Popover from 'jquery-popover'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import OutcomePopoverView from 'ui/features/grade_summary/backbone/views/OutcomePopoverView.coffee'
+import OutcomePopoverView from 'ui/features/grade_summary/backbone/views/OutcomePopoverView'
import template from '@canvas/outcomes/jst/outcomePopover.handlebars'
QUnit.module('OutcomePopoverViewSpec', {
@@ -42,7 +42,7 @@ QUnit.module('OutcomePopoverViewSpec', {
})
test('closePopover', function () {
- ok(isUndefined(this.popoverView.popover, 'precondition'))
+ ok(isUndefined(this.popoverView.popover), 'precondition')
ok(this.popoverView.closePopover())
this.popoverView.popover = new Popover(this.e('mouseleave'), this.popoverView.render(), {
verticalSide: 'bottom',
diff --git a/spec/coffeescripts/views/grade_summary/OutcomeViewSpec.js b/spec/coffeescripts/views/grade_summary/OutcomeViewSpec.js
index 40e9ee9764e..7a0a8cefb54 100644
--- a/spec/coffeescripts/views/grade_summary/OutcomeViewSpec.js
+++ b/spec/coffeescripts/views/grade_summary/OutcomeViewSpec.js
@@ -16,12 +16,13 @@
* with this program. If not, see .
*/
+import $ from 'jquery'
import {isUndefined} from 'lodash'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import OutcomePopoverView from 'ui/features/grade_summary/backbone/views/OutcomePopoverView.coffee'
-import OutcomeDialogView from 'ui/features/grade_summary/backbone/views/OutcomeDialogView.coffee'
-import OutcomeView from 'ui/features/grade_summary/backbone/views/OutcomeView.coffee'
-import ProgressBarView from 'ui/features/grade_summary/backbone/views/ProgressBarView.coffee'
+import OutcomePopoverView from 'ui/features/grade_summary/backbone/views/OutcomePopoverView'
+import OutcomeDialogView from 'ui/features/grade_summary/backbone/views/OutcomeDialogView'
+import OutcomeView from 'ui/features/grade_summary/backbone/views/OutcomeView'
+import ProgressBarView from 'ui/features/grade_summary/backbone/views/ProgressBarView'
import assertions from 'helpers/assertions'
QUnit.module('OutcomeViewSpec', {
@@ -36,6 +37,7 @@ QUnit.module('OutcomeViewSpec', {
},
})
+// eslint-disable-next-line qunit/resolve-async
test('should be accessible', function (assert) {
const done = assert.async()
assertions.isAccessible(this.outcomeView, done, {a11yReport: true})
@@ -46,7 +48,7 @@ test('assign instance of ProgressBarView on init', function () {
})
test('have after render behavior', function () {
- ok(isUndefined(this.outcomeView.popover, 'precondition'))
+ ok(isUndefined(this.outcomeView.popover), 'precondition')
this.outcomeView.render()
ok(this.outcomeView.popover instanceof OutcomePopoverView)
ok(this.outcomeView.dialog instanceof OutcomeDialogView)
diff --git a/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee b/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee
deleted file mode 100644
index cd9519e539d..00000000000
--- a/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.coffee
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Copyright (C) 2015 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import Backbone from '@canvas/backbone'
-import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
-import WrappedCollection from './WrappedCollection.coffee'
-
-export default class OutcomeResultCollection extends WrappedCollection
- key: 'outcome_results'
- model: Outcome
- @optionProperty 'outcome'
- url: -> "/api/v1/courses/#{@course_id}/outcome_results?user_ids[]=#{@user_id}&outcome_ids[]=#{@outcome.id}&include[]=alignments&per_page=100"
- loadAll: true
-
- comparator: (model) ->
- return -1 * model.get('submitted_or_assessed_at').getTime()
-
- initialize: ->
- super
- @model = Outcome.extend defaults: {
- points_possible: @outcome.get('points_possible'),
- mastery_points: @outcome.get('mastery_points')
- }
- @course_id = ENV.context_asset_string?.replace('course_', '')
- @user_id = ENV.student_id
- @on('reset', @handleReset)
- @on('add', @handleAdd)
-
- handleReset: =>
- @each @handleAdd
-
- handleAdd: (model) =>
- alignment_id = model.get('links').alignment
- model.set('alignment_name', @alignments.get(alignment_id)?.get('name'))
- if model.get('points_possible') > 0
- model.set('score', model.get('points_possible') * model.get('percent'))
- else
- model.set('score', model.get('mastery_points') * model.get('percent'))
-
- parse: (response) ->
- @alignments ?= new Backbone.Collection([])
- @alignments.add(response?.linked?.alignments || [])
- response[@key]
diff --git a/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.js b/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.js
new file mode 100644
index 00000000000..81fa98b5fa9
--- /dev/null
+++ b/ui/features/grade_summary/backbone/collections/OutcomeResultCollection.js
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2015 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import Backbone from '@canvas/backbone'
+import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
+import WrappedCollection from './WrappedCollection'
+
+class OutcomeResultCollection extends WrappedCollection {
+ constructor(...args) {
+ super(...args)
+ this.handleReset = this.handleReset.bind(this)
+ this.handleAdd = this.handleAdd.bind(this)
+ }
+
+ url = () =>
+ `/api/v1/courses/${this.course_id}/outcome_results?user_ids[]=${this.user_id}&outcome_ids[]=${this.outcome.id}&include[]=alignments&per_page=100`
+
+ comparator = model => -1 * model.get('submitted_or_assessed_at').getTime()
+
+ initialize() {
+ super.initialize(...arguments)
+ this.model = Outcome.extend({
+ defaults: {
+ points_possible: this.outcome.get('points_possible'),
+ mastery_points: this.outcome.get('mastery_points'),
+ },
+ })
+ this.course_id = ENV.context_asset_string?.replace('course_', '')
+ this.user_id = ENV.student_id
+ this.on('reset', this.handleReset)
+ this.on('add', this.handleAdd)
+ }
+
+ handleReset = () => this.each(this.handleAdd)
+
+ handleAdd(model) {
+ const alignment_id = model.get('links').alignment
+ model.set('alignment_name', this.alignments.get(alignment_id)?.get('name'))
+ if (model.get('points_possible') > 0) {
+ model.set('score', model.get('points_possible') * model.get('percent'))
+ } else {
+ model.set('score', model.get('mastery_points') * model.get('percent'))
+ }
+ }
+
+ parse(response) {
+ if (this.alignments === null || typeof this.alignments === 'undefined') {
+ this.alignments = new Backbone.Collection([])
+ }
+ this.alignments.add(response?.linked?.alignments || [])
+ return response[this.key]
+ }
+}
+
+OutcomeResultCollection.prototype.key = 'outcome_results'
+OutcomeResultCollection.prototype.model = Outcome
+OutcomeResultCollection.optionProperty('outcome')
+OutcomeResultCollection.prototype.loadAll = true
+
+export default OutcomeResultCollection
diff --git a/ui/features/grade_summary/backbone/collections/OutcomeSummaryCollection.js b/ui/features/grade_summary/backbone/collections/OutcomeSummaryCollection.js
index b0af501e0a5..56faa9bf0ef 100644
--- a/ui/features/grade_summary/backbone/collections/OutcomeSummaryCollection.js
+++ b/ui/features/grade_summary/backbone/collections/OutcomeSummaryCollection.js
@@ -17,13 +17,12 @@
import $ from 'jquery'
-import _ from 'underscore'
import {Collection} from '@canvas/backbone'
-import Section from '../models/Section.coffee'
-import Group from '../models/Group.coffee'
+import Section from '../models/Section'
+import Group from '../models/Group'
import Outcome from '@canvas/grade-summary/backbone/models/Outcome.coffee'
import PaginatedCollection from '@canvas/pagination/backbone/collections/PaginatedCollection.coffee'
-import WrappedCollection from './WrappedCollection.coffee'
+import WrappedCollection from './WrappedCollection'
import natcompare from '@canvas/util/natcompare'
class GroupCollection extends PaginatedCollection {
@@ -63,7 +62,7 @@ export default class OutcomeSummaryCollection extends Collection {
fetch = () => {
const dfd = $.Deferred()
- const requests = _.values(this.rawCollections).map(collection => {
+ const requests = Object.values(this.rawCollections).map(collection => {
collection.loadAll = true
return collection.fetch()
})
@@ -73,8 +72,7 @@ export default class OutcomeSummaryCollection extends Collection {
rollups() {
const studentRollups = this.rawCollections.rollups.at(0).get('scores')
- const pairs = studentRollups.map(x => [x.links.outcome, x])
- return _.object(pairs)
+ return Object.fromEntries(studentRollups.map(x => [x.links.outcome, x]))
}
populateGroupOutcomes() {
diff --git a/ui/features/grade_summary/backbone/collections/WrappedCollection.coffee b/ui/features/grade_summary/backbone/collections/WrappedCollection.coffee
deleted file mode 100644
index 43cc1e0117e..00000000000
--- a/ui/features/grade_summary/backbone/collections/WrappedCollection.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import PaginatedCollection from '@canvas/pagination/backbone/collections/PaginatedCollection.coffee'
-
-export default class WrappedCollection extends PaginatedCollection
- @optionProperty 'key'
-
- parse: (response) ->
- @linked = response.linked
- response[@key]
diff --git a/ui/features/grade_summary/sum.js b/ui/features/grade_summary/backbone/collections/WrappedCollection.js
similarity index 62%
rename from ui/features/grade_summary/sum.js
rename to ui/features/grade_summary/backbone/collections/WrappedCollection.js
index 1ebfe1d92d7..1aef3ef07a1 100644
--- a/ui/features/grade_summary/sum.js
+++ b/ui/features/grade_summary/backbone/collections/WrappedCollection.js
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2015 - present Instructure, Inc.
+// Copyright (C) 2014 - present Instructure, Inc.
//
// This file is part of Canvas.
//
@@ -15,19 +15,15 @@
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see .
-// Adds _.sum method.
-//
-// Use like:
-//
-// _.sum([2,3,4]) #=> 9
-//
-// or with a custom accessor:
-//
-// _.sum([[2,3], [3,4]], (a) -> a[0]) #=> 5
-import _ from 'underscore'
+import PaginatedCollection from '@canvas/pagination/backbone/collections/PaginatedCollection.coffee'
-export default _.mixin({
- sum(array, accessor = null, start = 0) {
- return _.reduce(array, (memo, el) => (accessor != null ? accessor(el) : el) + memo, start)
- },
-})
+class WrappedCollection extends PaginatedCollection {
+ parse(response) {
+ this.linked = response.linked
+ return response[this.key]
+ }
+}
+
+WrappedCollection.optionProperty('key')
+
+export default WrappedCollection
diff --git a/ui/features/grade_summary/backbone/models/Group.coffee b/ui/features/grade_summary/backbone/models/Group.coffee
deleted file mode 100644
index 07635ffc2dc..00000000000
--- a/ui/features/grade_summary/backbone/models/Group.coffee
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import _ from 'underscore'
-import {Model, Collection} from '@canvas/backbone'
-import natcompare from '@canvas/util/natcompare'
-
-export default class Group extends Model
- initialize: ->
- @set('outcomes', new Collection([], comparator: natcompare.byGet('friendly_name')))
-
- count: -> @get('outcomes').length
-
-
- statusCount: (status) ->
- @get('outcomes').filter((x) ->
- x.status() == status
- ).length
-
- mastery_count: ->
- @statusCount('mastery') + @statusCount('exceeds')
-
- remedialCount: ->
- @statusCount('remedial')
-
- undefinedCount: ->
- @statusCount('undefined')
-
- status: ->
- if @remedialCount() > 0
- "remedial"
- else
- if @mastery_count() == @count()
- "mastery"
- else if @undefinedCount() == @count()
- "undefined"
- else
- "near"
-
- started: ->
- true
-
- toJSON: ->
- _.extend super,
- count: @count()
- mastery_count: @mastery_count()
- started: @started()
- status: @status()
diff --git a/ui/features/grade_summary/backbone/models/Group.js b/ui/features/grade_summary/backbone/models/Group.js
new file mode 100644
index 00000000000..b26b3ebdae0
--- /dev/null
+++ b/ui/features/grade_summary/backbone/models/Group.js
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2014 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import {Model, Collection} from '@canvas/backbone'
+import natcompare from '@canvas/util/natcompare'
+
+export default class Group extends Model {
+ initialize() {
+ return this.set('outcomes', new Collection([], {comparator: natcompare.byGet('friendly_name')}))
+ }
+
+ count() {
+ return this.get('outcomes').length
+ }
+
+ statusCount(status) {
+ return this.get('outcomes').filter(x => x.status() === status).length
+ }
+
+ mastery_count() {
+ return this.statusCount('mastery') + this.statusCount('exceeds')
+ }
+
+ remedialCount() {
+ return this.statusCount('remedial')
+ }
+
+ undefinedCount() {
+ return this.statusCount('undefined')
+ }
+
+ status() {
+ if (this.remedialCount() > 0) {
+ return 'remedial'
+ } else if (this.mastery_count() === this.count()) {
+ return 'mastery'
+ } else if (this.undefinedCount() === this.count()) {
+ return 'undefined'
+ } else {
+ return 'near'
+ }
+ }
+
+ started() {
+ return true
+ }
+
+ toJSON() {
+ return {
+ ...super.toJSON(...arguments),
+ count: this.count(),
+ mastery_count: this.mastery_count(),
+ started: this.started(),
+ status: this.status(),
+ }
+ }
+}
diff --git a/ui/features/grade_summary/backbone/models/Section.coffee b/ui/features/grade_summary/backbone/models/Section.coffee
deleted file mode 100644
index a29fdfb197e..00000000000
--- a/ui/features/grade_summary/backbone/models/Section.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import {Model, Collection} from '@canvas/backbone'
-import natcompare from '@canvas/util/natcompare'
-
-export default class Section extends Model
- initialize: ->
- @set('groups', new Collection([], comparator: natcompare.byGet('title')))
diff --git a/ui/features/grade_summary/backbone/models/Section.js b/ui/features/grade_summary/backbone/models/Section.js
new file mode 100644
index 00000000000..f228671e222
--- /dev/null
+++ b/ui/features/grade_summary/backbone/models/Section.js
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2014 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import {Model, Collection} from '@canvas/backbone'
+import natcompare from '@canvas/util/natcompare'
+
+export default class Section extends Model {
+ initialize() {
+ return this.set('groups', new Collection([], {comparator: natcompare.byGet('title')}))
+ }
+}
diff --git a/ui/features/grade_summary/backbone/views/AlignmentView.js b/ui/features/grade_summary/backbone/views/AlignmentView.js
index 41ecef7d03b..dd2291c7376 100644
--- a/ui/features/grade_summary/backbone/views/AlignmentView.js
+++ b/ui/features/grade_summary/backbone/views/AlignmentView.js
@@ -16,7 +16,7 @@
// with this program. If not, see .
import Backbone from '@canvas/backbone'
-import ProgressBarView from './ProgressBarView.coffee'
+import ProgressBarView from './ProgressBarView'
import template from '../../jst/alignment.handlebars'
export default class AlignmentView extends Backbone.View {
diff --git a/ui/features/grade_summary/backbone/views/GroupView.coffee b/ui/features/grade_summary/backbone/views/GroupView.coffee
deleted file mode 100644
index 01963dd70a8..00000000000
--- a/ui/features/grade_summary/backbone/views/GroupView.coffee
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import {useScope as useI18nScope} from '@canvas/i18n'
-import {View, Collection} from '@canvas/backbone'
-import _ from 'underscore'
-import CollectionView from '@canvas/backbone-collection-view'
-import OutcomeView from './OutcomeView.coffee'
-import template from '../../jst/group.handlebars'
-
-I18n = useI18nScope('grade_summaryGroupView')
-
-export default class GroupView extends View
- tagName: 'li'
- className: 'group'
-
- els:
- '.outcomes': '$outcomes'
-
- events:
- 'click .group-description': 'expand'
- 'keyclick .group-description': 'expand'
-
- template: template
-
- render: ->
- super
- outcomesView = new CollectionView
- el: @$outcomes
- collection: @model.get('outcomes')
- itemView: OutcomeView
- outcomesView.render()
-
- expand: ->
- @$el.toggleClass('expanded')
- if @$el.hasClass("expanded")
- @$el.children("div.group-description").attr("aria-expanded", "true")
- else
- @$el.children("div.group-description").attr("aria-expanded", "false")
-
- $collapseToggle = $('div.outcome-toggles a.icon-collapse')
- if $('li.group.expanded').length == 0
- $collapseToggle.attr('disabled', 'disabled')
- $collapseToggle.attr('aria-disabled', 'true')
- else
- $collapseToggle.removeAttr('disabled')
- $collapseToggle.attr('aria-disabled', 'false')
-
- $expandToggle = $('div.outcome-toggles a.icon-expand')
- if $('li.group:not(.expanded)').length == 0
- $expandToggle.attr('disabled', 'disabled')
- $expandToggle.attr('aria-disabled', 'true')
- else
- $expandToggle.removeAttr('disabled')
- $expandToggle.attr('aria-disabled', 'false')
-
- statusTooltip: ->
- switch @model.status()
- when 'undefined' then I18n.t('Unstarted')
- when 'remedial' then I18n.t('Well Below Mastery')
- when 'near' then I18n.t('Near Mastery')
- when 'mastery' then I18n.t('Meets Mastery')
- when 'exceeds' then I18n.t('Exceeds Mastery')
-
- toJSON: ->
- json = super
- _.extend json,
- statusTooltip: @statusTooltip()
diff --git a/ui/features/grade_summary/backbone/views/GroupView.js b/ui/features/grade_summary/backbone/views/GroupView.js
new file mode 100644
index 00000000000..b0df833ed22
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/GroupView.js
@@ -0,0 +1,97 @@
+//
+// Copyright (C) 2014 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import {useScope as useI18nScope} from '@canvas/i18n'
+import $ from 'jquery'
+import {View} from '@canvas/backbone'
+import CollectionView from '@canvas/backbone-collection-view'
+import OutcomeView from './OutcomeView'
+import template from '../../jst/group.handlebars'
+
+const I18n = useI18nScope('grade_summaryGroupView')
+
+class GroupView extends View {
+ render() {
+ super.render(...arguments)
+ const outcomesView = new CollectionView({
+ el: this.$outcomes,
+ collection: this.model.get('outcomes'),
+ itemView: OutcomeView,
+ })
+ return outcomesView.render()
+ }
+
+ expand() {
+ this.$el.toggleClass('expanded')
+ if (this.$el.hasClass('expanded')) {
+ this.$el.children('div.group-description').attr('aria-expanded', 'true')
+ } else {
+ this.$el.children('div.group-description').attr('aria-expanded', 'false')
+ }
+
+ const $collapseToggle = $('div.outcome-toggles a.icon-collapse')
+ if ($('li.group.expanded').length === 0) {
+ $collapseToggle.attr('disabled', 'disabled')
+ $collapseToggle.attr('aria-disabled', 'true')
+ } else {
+ $collapseToggle.removeAttr('disabled')
+ $collapseToggle.attr('aria-disabled', 'false')
+ }
+
+ const $expandToggle = $('div.outcome-toggles a.icon-expand')
+ if ($('li.group:not(.expanded)').length === 0) {
+ $expandToggle.attr('disabled', 'disabled')
+ return $expandToggle.attr('aria-disabled', 'true')
+ } else {
+ $expandToggle.removeAttr('disabled')
+ return $expandToggle.attr('aria-disabled', 'false')
+ }
+ }
+
+ statusTooltip() {
+ switch (this.model.status()) {
+ case 'undefined':
+ return I18n.t('Unstarted')
+ case 'remedial':
+ return I18n.t('Well Below Mastery')
+ case 'near':
+ return I18n.t('Near Mastery')
+ case 'mastery':
+ return I18n.t('Meets Mastery')
+ case 'exceeds':
+ return I18n.t('Exceeds Mastery')
+ }
+ }
+
+ toJSON() {
+ return {
+ ...super.toJSON(...arguments),
+ statusTooltip: this.statusTooltip(),
+ }
+ }
+}
+
+GroupView.prototype.template = template
+GroupView.prototype.tagName = 'li'
+GroupView.prototype.className = 'group'
+GroupView.prototype.els = {'.outcomes': '$outcomes'}
+GroupView.prototype.events = {
+ 'click .group-description': 'expand',
+ 'keyclick .group-description': 'expand',
+}
+
+export default GroupView
diff --git a/ui/features/grade_summary/backbone/views/OutcomeDetailView.js b/ui/features/grade_summary/backbone/views/OutcomeDetailView.js
index 786e0fab7ed..a79e175c93f 100644
--- a/ui/features/grade_summary/backbone/views/OutcomeDetailView.js
+++ b/ui/features/grade_summary/backbone/views/OutcomeDetailView.js
@@ -16,18 +16,14 @@
// with this program. If not, see .
import Backbone from '@canvas/backbone'
-import OutcomeResultCollection from '../collections/OutcomeResultCollection.coffee'
+import OutcomeResultCollection from '../collections/OutcomeResultCollection'
import DialogBaseView from '@canvas/dialog-base-view'
import CollectionView from '@canvas/backbone-collection-view'
import AlignmentView from './AlignmentView'
-import ProgressBarView from './ProgressBarView.coffee'
+import ProgressBarView from './ProgressBarView'
import template from '../../jst/outcome_detail.handlebars'
-export default class OutcomeDetailView extends DialogBaseView {
- static initClass() {
- this.prototype.template = template
- }
-
+class OutcomeDetailView extends DialogBaseView {
dialogOptions() {
return {
containerId: 'outcome_detail',
@@ -77,4 +73,7 @@ export default class OutcomeDetailView extends DialogBaseView {
return {...json, progress: this.progress}
}
}
-OutcomeDetailView.initClass()
+
+OutcomeDetailView.prototype.template = template
+
+export default OutcomeDetailView
diff --git a/ui/features/grade_summary/backbone/views/OutcomeDialogView.coffee b/ui/features/grade_summary/backbone/views/OutcomeDialogView.coffee
deleted file mode 100644
index 5b931b54d81..00000000000
--- a/ui/features/grade_summary/backbone/views/OutcomeDialogView.coffee
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Copyright (C) 2015 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import $ from 'jquery'
-import _ from 'underscore'
-import DialogBaseView from '@canvas/dialog-base-view'
-import OutcomeLineGraphView from './OutcomeLineGraphView.coffee'
-import template from '@canvas/outcomes/jst/outcomePopover.handlebars'
-
-export default class OutcomeResultsDialogView extends DialogBaseView
- @optionProperty 'model'
- $target: null
- template: template
-
- initialize: ->
- super
- @outcomeLineGraphView = new OutcomeLineGraphView({
- model: @model
- })
-
- afterRender: ->
- @outcomeLineGraphView.setElement(@$("div.line-graph"))
- @outcomeLineGraphView.render()
-
- dialogOptions: ->
- containerId: "outcome_results_dialog"
- close: @onClose
- buttons: []
- width: 460
-
- show: (e) ->
- return unless (e.type == "click" || @_getKey(e.keyCode))
- @$target = $(e.target)
- e.preventDefault()
- @$el.dialog('option', 'title', @model.get('title'))
- super
- @render()
-
- onClose: =>
- @$target.focus()
- delete @$target
-
- toJSON: ->
- json = super
- _.extend json,
- dialog: true
-
- # Private
- _getKey: (keycode) =>
- keys = {
- 13 : "enter"
- 32 : "spacebar"
- }
- keys[keycode]
diff --git a/ui/features/grade_summary/backbone/views/OutcomeDialogView.js b/ui/features/grade_summary/backbone/views/OutcomeDialogView.js
new file mode 100644
index 00000000000..bd0447b0b59
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/OutcomeDialogView.js
@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2015 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import $ from 'jquery'
+import DialogBaseView from '@canvas/dialog-base-view'
+import OutcomeLineGraphView from './OutcomeLineGraphView'
+import template from '@canvas/outcomes/jst/outcomePopover.handlebars'
+
+class OutcomeResultsDialogView extends DialogBaseView {
+ constructor(...args) {
+ super(...args)
+ this.onClose = this.onClose.bind(this)
+ this._getKey = this._getKey.bind(this)
+ }
+
+ initialize() {
+ super.initialize(...arguments)
+ return (this.outcomeLineGraphView = new OutcomeLineGraphView({
+ model: this.model,
+ }))
+ }
+
+ afterRender() {
+ this.outcomeLineGraphView.setElement(this.$('div.line-graph'))
+ return this.outcomeLineGraphView.render()
+ }
+
+ dialogOptions() {
+ return {
+ containerId: 'outcome_results_dialog',
+ close: this.onClose,
+ buttons: [],
+ width: 460,
+ }
+ }
+
+ show(e) {
+ if (e.type !== 'click' && !this._getKey(e.keyCode)) return
+ this.$target = $(e.target)
+ e.preventDefault()
+ this.$el.dialog('option', 'title', this.model.get('title'))
+ super.show(...arguments)
+ return this.render()
+ }
+
+ onClose() {
+ this.$target.focus()
+ delete this.$target
+ }
+
+ toJSON() {
+ return {
+ ...super.toJSON(...arguments),
+ dialog: true,
+ }
+ }
+
+ // Private
+ _getKey(keycode) {
+ const keys = {
+ 13: 'enter',
+ 32: 'spacebar',
+ }
+ return keys[keycode]
+ }
+}
+
+OutcomeResultsDialogView.optionProperty('model')
+OutcomeResultsDialogView.prototype.$target = null
+OutcomeResultsDialogView.prototype.template = template
+
+export default OutcomeResultsDialogView
diff --git a/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.coffee b/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.coffee
deleted file mode 100644
index 3aa4547c9a5..00000000000
--- a/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.coffee
+++ /dev/null
@@ -1,285 +0,0 @@
-#
-# Copyright (C) 2015 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import _ from 'underscore'
-import Backbone from '@canvas/backbone'
-import I18n from '@canvas/i18n'
-import OutcomeResultCollection from '../collections/OutcomeResultCollection.coffee'
-import d3 from 'd3/d3'
-import accessibleTemplate from '../../jst/accessibleLineGraph.handlebars'
-import '../../sum'
-
-# Trend class based on formulae found here:
-# http://classroom.synonym.com/calculate-trendline-2709.html
-class Trend
- constructor: (@rawData) ->
-
- # Returns: [[x1, y1], [x2, y2]]
- data: ->
- [[
- @xValue(@rawData[0])
- @yIntercept()
- @xValue(_.last(@rawData))
- (@slope() * @xValue(_.last(@rawData))) + @yIntercept()
- ]]
-
- slope: ->
- (@a() - @b()) / (@c() - @d())
-
- yIntercept: ->
- (@e() - @f()) / @n()
-
- # The number of points of data.
- n: ->
- @rawData.length
-
- # `n` times the sum of the products of each x & y pair.
- a: ->
- @n() * _.sum(@rawData, (point) => (@xValue(point) * @yValue(point)))
-
- # The product of the sum of all x values and all y values.
- b: ->
- _.sum(@rawData, @xValue) * _.sum(@rawData, @yValue)
-
- # `n` times the sum of all x values individually squared.
- c: ->
- @n() * _.sum(@rawData, (point) => Math.pow(@xValue(point), 2))
-
- # The sum of all x values squared.
- d: ->
- Math.pow(_.sum(@rawData, @xValue), 2)
-
- # The sum of all y values.
- e: ->
- _.sum(@rawData, @yValue)
-
- # The slope times the sum of all x values.
- f: ->
- @slope() * _.sum(@rawData, @xValue)
-
- xValue: (point) ->
- point.x
-
- yValue: (point) ->
- point.y
-
-export default class OutcomeLineGraphView extends Backbone.View
- @optionProperty 'el'
- @optionProperty 'height'
- @optionProperty 'limit'
- @optionProperty 'margin'
- @optionProperty 'model'
- @optionProperty 'timeFormat'
- defaults:
- height: 200
- limit: 8
- margin: {top: 20, right: 20, bottom: 30, left: 40}
- # 2015-02-06T17:49:08Z
- timeFormat: "%Y-%m-%dT%XZ"
-
- initialize: ->
- super
- @deferred = $.Deferred()
- @collection = new OutcomeResultCollection([], {
- outcome: @model
- })
- @collection.on 'fetched:last', =>
- @deferred.resolve()
- @collection.fetch()
-
- render: ->
- if @deferred.isResolved()
- return @ if @collection.isEmpty()
-
- @_prepareScales()
- @_prepareAxes()
- @_prepareLines()
-
- @svg = d3.select(@el)
- .append("svg")
- .attr("width", @width() + @margin.left + @margin.right)
- .attr("height", @height + @margin.top + @margin.bottom)
- .attr("aria-hidden", true)
- .append("g")
- .attr("transform", "translate(#{@margin.left}, #{@margin.top})")
-
- @_appendAxes()
- @_appendLines()
-
- @$('.screenreader-only').append(accessibleTemplate(@toJSON()))
- else
- @deferred.done(@render)
-
-
- @
-
- toJSON: ->
- current_user_name: ENV.current_user.display_name
- data: @data()
- outcome_name: @model.get('friendly_name')
-
- # Data helpers
- data: ->
- @_data ?= @collection.chain()
- .last(@limit)
- .map((outcomeResult, i) =>
- x: i
- y: @percentageFor(outcomeResult.get('score'))
- date: outcomeResult.get('submitted_or_assessed_at')
- ).value()
-
- masteryPercentage: ->
- if @model.get('points_possible') > 0
- (@model.get('mastery_points') / @model.get('points_possible')) * 100
- else
- 100
-
- percentageFor: (score) ->
- if @model.get('points_possible') > 0
- ((score / @model.get('points_possible')) * 100)
- else
- ((score / @model.get('mastery_points')) * 100)
-
- xValue: (point) =>
- @x(point.x)
-
- yValue: (point) =>
- @y(point.y)
-
- # View helpers
- _appendAxes: ->
- @svg.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(0,#{@height})")
- .call(@xAxis)
-
- @svg.append("g")
- .attr("class", "date-guides")
- .attr("transform", "translate(0,#{@height})")
- .call(@dateGuides)
-
- @svg.append("g")
- .attr("class", "y axis")
- .call(@yAxis)
-
- @svg.append("g")
- .attr("class", "guides")
- .call(@yGuides)
-
- @svg.append("g")
- .attr("class", "mastery-percentage-guide")
- .style("stroke-dasharray", ("3, 3"))
- .call(@masteryPercentageGuide)
-
- _appendLines: ->
- @svg.selectAll("circle")
- .data(@data())
- .enter().append("circle")
- .attr("fill", "black")
- .attr("r", 3)
- .attr("cx", @xValue)
- .attr("cy", @yValue)
-
- @svg.append("path")
- .datum(@data())
- .attr("d", @line)
- .attr("class", "line")
- .attr("stroke", "black")
- .attr("stroke-width", 1)
- .attr("fill", "none")
-
- if @trend?
- @svg.selectAll(".trendline")
- .data(@trend.data())
- .enter()
- .append("line")
- .attr("class", "trendline")
- .attr("x1", (d) => @x(d[0]))
- .attr("y1", (d) => @y(d[1]))
- .attr("x2", (d) => @x(d[2]))
- .attr("y2", (d) => @y(d[3]))
- .attr("stroke-width", 1)
-
- @svg
-
-
- _prepareAxes: ->
- @xAxis = d3.svg.axis()
- .scale(@x)
- .tickFormat('')
- @dateGuides = d3.svg.axis()
- .scale(@xTimeScale)
- .tickValues([
- _.first(@data()).date
- _.last(@data()).date
- ])
- .tickFormat((d) -> Intl.DateTimeFormat(I18n.currentLocale(), { day: 'numeric', month: 'numeric'}).format(d))
- @yAxis = d3.svg.axis()
- .scale(@y)
- .orient("left")
- .tickFormat((d) -> I18n.n(d, { percentage: true }))
- .tickValues([0, 50, 100])
- @yGuides = d3.svg.axis()
- .scale(@y)
- .orient("left")
- .tickValues([50, 100])
- .tickSize(-@width(), 0, 0)
- .tickFormat("")
- @masteryPercentageGuide = d3.svg.axis()
- .scale(@y)
- .orient("left")
- .tickValues([@masteryPercentage()])
- .tickSize(-@width(), 0, 0)
- .tickFormat("")
-
- _prepareLines: ->
- if @data().length >=3
- @trend = new Trend(@data())
-
- @line = d3.svg.line()
- .x(@xValue)
- .y(@yValue)
- .interpolate('linear')
-
- _prepareScales: ->
- @x = d3.scale.linear()
- .range([0, @width()])
- .domain([0, @limit - 1])
- @xTimeScale = d3.time.scale()
- .range([0, @xTimeScaleWidth()])
- .domain([
- _.first(@data()).date
- _.last(@data()).date
- ])
- @y = d3.scale.linear()
- .range([@height, @margin.bottom])
- .domain([0, 100])
-
- width: ->
- @$el.width() - @margin.left - @margin.right - 10
-
- # The width of the axis used to display the first and last date of scores
- # displayed has to be different than the full width, in case the number
- # of points is fewer than the limit (8). What we want is the width of the
- # element reduced by the difference between the limit and the number of
- # points we actually have, multiplied by the width each point represents,
- # based on the element's width and the limit.
- xTimeScaleWidth: ->
- (@width() - (
- (@width() / (@limit - 1)) *
- (@limit - @data().length)
- ))
diff --git a/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.js b/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.js
new file mode 100644
index 00000000000..646d89e16e9
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/OutcomeLineGraphView.js
@@ -0,0 +1,340 @@
+//
+// Copyright (C) 2015 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+/*
+ * decaffeinate suggestions:
+ * DS207: Consider shorter variations of null checks
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
+ */
+
+import $ from 'jquery'
+import {sumBy} from 'lodash'
+import Backbone from '@canvas/backbone'
+import I18n from '@canvas/i18n'
+import OutcomeResultCollection from '../collections/OutcomeResultCollection'
+import d3 from 'd3/d3'
+import accessibleTemplate from '../../jst/accessibleLineGraph.handlebars'
+
+const first = array => array.at(0)
+const last = array => array.at(-1)
+
+// Trend class based on formulae found here:
+// http://classroom.synonym.com/calculate-trendline-2709.html
+class Trend {
+ constructor(rawData) {
+ this.rawData = rawData
+ }
+
+ // Returns: [[x1, y1], [x2, y2]]
+ data() {
+ return [
+ [
+ this.rawData[0].x,
+ this.yIntercept(),
+ last(this.rawData).x,
+ this.slope() * last(this.rawData).x + this.yIntercept(),
+ ],
+ ]
+ }
+
+ slope() {
+ return (this.a() - this.b()) / (this.c() - this.d())
+ }
+
+ yIntercept() {
+ return (this.e() - this.f()) / this.n()
+ }
+
+ // The number of points of data.
+ n() {
+ return this.rawData.length
+ }
+
+ // `n` times the sum of the products of each x & y pair.
+ a() {
+ return this.n() * sumBy(this.rawData, p => p.x * p.y)
+ }
+
+ // The product of the sum of all x values and all y values.
+ b() {
+ return sumBy(this.rawData, p => p.x) * sumBy(this.rawData, p => p.y)
+ }
+
+ // `n` times the sum of all x values individually squared.
+ c() {
+ return this.n() * sumBy(this.rawData, p => p.x * p.x)
+ }
+
+ // The sum of all x values squared.
+ d() {
+ return Math.pow(
+ sumBy(this.rawData, p => p.x),
+ 2
+ )
+ }
+
+ // The sum of all y values.
+ e() {
+ return sumBy(this.rawData, p => p.y)
+ }
+
+ // The slope times the sum of all x values.
+ f() {
+ return this.slope() * sumBy(this.rawData, p => p.x)
+ }
+}
+
+class OutcomeLineGraphView extends Backbone.View {
+ constructor(...args) {
+ super(...args)
+ this.xValue = this.xValue.bind(this)
+ this.yValue = this.yValue.bind(this)
+ }
+
+ initialize() {
+ super.initialize(...arguments)
+ this.deferred = $.Deferred()
+ this.collection = new OutcomeResultCollection([], {
+ outcome: this.model,
+ })
+ this.collection.on('fetched:last', () => {
+ return this.deferred.resolve()
+ })
+ return this.collection.fetch()
+ }
+
+ render() {
+ if (this.deferred.isResolved()) {
+ if (this.collection.isEmpty()) {
+ return this
+ }
+
+ this._prepareScales()
+ this._prepareAxes()
+ this._prepareLines()
+
+ this.svg = d3
+ .select(this.el)
+ .append('svg')
+ .attr('width', this.width() + this.margin.left + this.margin.right)
+ .attr('height', this.height + this.margin.top + this.margin.bottom)
+ .attr('aria-hidden', true)
+ .append('g')
+ .attr('transform', `translate(${this.margin.left}, ${this.margin.top})`)
+
+ this._appendAxes()
+ this._appendLines()
+
+ this.$('.screenreader-only').append(accessibleTemplate(this.toJSON()))
+ } else {
+ this.deferred.done(this.render)
+ }
+
+ return this
+ }
+
+ toJSON() {
+ return {
+ current_user_name: ENV.current_user.display_name,
+ data: this.data(),
+ outcome_name: this.model.get('friendly_name'),
+ }
+ }
+
+ // Data helpers
+ data() {
+ if (this._data === null || typeof this._data === 'undefined') {
+ this._data = this.collection
+ .chain()
+ .last(this.limit)
+ .map((outcomeResult, i) => ({
+ x: i,
+ y: this.percentageFor(outcomeResult.get('score')),
+ date: outcomeResult.get('submitted_or_assessed_at'),
+ }))
+ .value()
+ }
+ return this._data
+ }
+
+ masteryPercentage() {
+ if (this.model.get('points_possible') > 0) {
+ return (this.model.get('mastery_points') / this.model.get('points_possible')) * 100
+ } else {
+ return 100
+ }
+ }
+
+ percentageFor(score) {
+ if (this.model.get('points_possible') > 0) {
+ return (score / this.model.get('points_possible')) * 100
+ } else {
+ return (score / this.model.get('mastery_points')) * 100
+ }
+ }
+
+ xValue(point) {
+ return this.x(point.x)
+ }
+
+ yValue(point) {
+ return this.y(point.y)
+ }
+
+ // View helpers
+ _appendAxes() {
+ this.svg
+ .append('g')
+ .attr('class', 'x axis')
+ .attr('transform', `translate(0,${this.height})`)
+ .call(this.xAxis)
+
+ this.svg
+ .append('g')
+ .attr('class', 'date-guides')
+ .attr('transform', `translate(0,${this.height})`)
+ .call(this.dateGuides)
+
+ this.svg.append('g').attr('class', 'y axis').call(this.yAxis)
+
+ this.svg.append('g').attr('class', 'guides').call(this.yGuides)
+
+ return this.svg
+ .append('g')
+ .attr('class', 'mastery-percentage-guide')
+ .style('stroke-dasharray', '3, 3')
+ .call(this.masteryPercentageGuide)
+ }
+
+ _appendLines() {
+ this.svg
+ .selectAll('circle')
+ .data(this.data())
+ .enter()
+ .append('circle')
+ .attr('fill', 'black')
+ .attr('r', 3)
+ .attr('cx', this.xValue)
+ .attr('cy', this.yValue)
+
+ this.svg
+ .append('path')
+ .datum(this.data())
+ .attr('d', this.line)
+ .attr('class', 'line')
+ .attr('stroke', 'black')
+ .attr('stroke-width', 1)
+ .attr('fill', 'none')
+
+ if (this.trend != null) {
+ this.svg
+ .selectAll('.trendline')
+ .data(this.trend.data())
+ .enter()
+ .append('line')
+ .attr('class', 'trendline')
+ .attr('x1', d => this.x(d[0]))
+ .attr('y1', d => this.y(d[1]))
+ .attr('x2', d => this.x(d[2]))
+ .attr('y2', d => this.y(d[3]))
+ .attr('stroke-width', 1)
+ }
+
+ return this.svg
+ }
+
+ _prepareAxes() {
+ this.xAxis = d3.svg.axis().scale(this.x).tickFormat('')
+ this.dateGuides = d3.svg
+ .axis()
+ .scale(this.xTimeScale)
+ .tickValues([first(this.data()).date, last(this.data()).date])
+ .tickFormat(d =>
+ Intl.DateTimeFormat(I18n.currentLocale(), {day: 'numeric', month: 'numeric'}).format(d)
+ )
+ this.yAxis = d3.svg
+ .axis()
+ .scale(this.y)
+ .orient('left')
+ .tickFormat(d => I18n.n(d, {percentage: true}))
+ .tickValues([0, 50, 100])
+ this.yGuides = d3.svg
+ .axis()
+ .scale(this.y)
+ .orient('left')
+ .tickValues([50, 100])
+ .tickSize(-this.width(), 0, 0)
+ .tickFormat('')
+ return (this.masteryPercentageGuide = d3.svg
+ .axis()
+ .scale(this.y)
+ .orient('left')
+ .tickValues([this.masteryPercentage()])
+ .tickSize(-this.width(), 0, 0)
+ .tickFormat(''))
+ }
+
+ _prepareLines() {
+ if (this.data().length >= 3) {
+ this.trend = new Trend(this.data())
+ }
+
+ return (this.line = d3.svg.line().x(this.xValue).y(this.yValue).interpolate('linear'))
+ }
+
+ _prepareScales() {
+ this.x = d3.scale
+ .linear()
+ .range([0, this.width()])
+ .domain([0, this.limit - 1])
+ this.xTimeScale = d3.time
+ .scale()
+ .range([0, this.xTimeScaleWidth()])
+ .domain([first(this.data()).date, last(this.data()).date])
+ return (this.y = d3.scale.linear().range([this.height, this.margin.bottom]).domain([0, 100]))
+ }
+
+ width() {
+ return this.$el.width() - this.margin.left - this.margin.right - 10
+ }
+
+ // The width of the axis used to display the first and last date of scores
+ // displayed has to be different than the full width, in case the number
+ // of points is fewer than the limit (8). What we want is the width of the
+ // element reduced by the difference between the limit and the number of
+ // points we actually have, multiplied by the width each point represents,
+ // based on the element's width and the limit.
+ xTimeScaleWidth() {
+ return this.width() - (this.width() / (this.limit - 1)) * (this.limit - this.data().length)
+ }
+}
+
+OutcomeLineGraphView.optionProperty('el')
+OutcomeLineGraphView.optionProperty('height')
+OutcomeLineGraphView.optionProperty('limit')
+OutcomeLineGraphView.optionProperty('margin')
+OutcomeLineGraphView.optionProperty('model')
+OutcomeLineGraphView.optionProperty('timeFormat')
+OutcomeLineGraphView.prototype.defaults = {
+ height: 200,
+ limit: 8,
+ margin: {top: 20, right: 20, bottom: 30, left: 40},
+ // 2015-02-06T17:49:08Z
+ timeFormat: '%Y-%m-%dT%XZ',
+}
+
+export default OutcomeLineGraphView
diff --git a/ui/features/grade_summary/backbone/views/OutcomePopoverView.coffee b/ui/features/grade_summary/backbone/views/OutcomePopoverView.coffee
deleted file mode 100644
index 1eafd132b28..00000000000
--- a/ui/features/grade_summary/backbone/views/OutcomePopoverView.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# Copyright (C) 2015 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import Backbone from '@canvas/backbone'
-import Popover from 'jquery-popover'
-import OutcomeLineGraphView from './OutcomeLineGraphView.coffee'
-import template from '@canvas/outcomes/jst/outcomePopover.handlebars'
-
-export default class OutcomePopoverView extends Backbone.View
- TIMEOUT_LENGTH: 50
-
- @optionProperty 'el'
- @optionProperty 'model'
-
- events:
- 'click i': 'mouseleave'
- 'mouseenter i': 'mouseenter'
- 'mouseleave i': 'mouseleave'
- inside: false
-
- initialize: ->
- super
- @outcomeLineGraphView = new OutcomeLineGraphView({
- model: @model
- })
-
- # Overrides
- render: ->
- template(@toJSON())
-
- # Instance methods
- closePopover: (e) ->
- e?.preventDefault()
- return true unless @popover?
- @popover.hide()
- delete @popover
-
- mouseenter: (e) =>
- @openPopover(e)
- @inside = true
-
- mouseleave: (e) =>
- @inside = false
- setTimeout =>
- return if @inside || !@popover
- @closePopover()
- , @TIMEOUT_LENGTH
-
- openPopover: (e) ->
- if @closePopover()
- @popover = new Popover(e, @render(), {
- verticalSide: 'bottom'
- manualOffset: 14
- })
- @outcomeLineGraphView.setElement(@popover.el.find("div.line-graph"))
- @outcomeLineGraphView.render()
-
diff --git a/ui/features/grade_summary/backbone/views/OutcomePopoverView.js b/ui/features/grade_summary/backbone/views/OutcomePopoverView.js
new file mode 100644
index 00000000000..1d4322e799e
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/OutcomePopoverView.js
@@ -0,0 +1,95 @@
+//
+// Copyright (C) 2015 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+/*
+ * decaffeinate suggestions:
+ * DS206: Consider reworking classes to avoid initClass
+ * DS207: Consider shorter variations of null checks
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
+ */
+
+import Backbone from '@canvas/backbone'
+import Popover from 'jquery-popover'
+import OutcomeLineGraphView from './OutcomeLineGraphView'
+import template from '@canvas/outcomes/jst/outcomePopover.handlebars'
+
+const TIMEOUT_LENGTH = 50
+
+class OutcomePopoverView extends Backbone.View {
+ constructor(...args) {
+ super(...args)
+ this.mouseenter = this.mouseenter.bind(this)
+ this.mouseleave = this.mouseleave.bind(this)
+ }
+
+ initialize() {
+ super.initialize(...arguments)
+ return (this.outcomeLineGraphView = new OutcomeLineGraphView({
+ model: this.model,
+ }))
+ }
+
+ // Overrides
+ render() {
+ return template(this.toJSON())
+ }
+
+ // Instance methods
+ closePopover(e) {
+ e?.preventDefault()
+ if (this.popover === null || typeof this.popover === 'undefined') return true
+ this.popover.hide()
+ return delete this.popover
+ }
+
+ mouseenter(e) {
+ this.openPopover(e)
+ this.inside = true
+ return true
+ }
+
+ mouseleave(_e) {
+ this.inside = false
+ setTimeout(() => {
+ if (!this.inside && this.popover) this.closePopover()
+ }, TIMEOUT_LENGTH)
+ }
+
+ openPopover(e) {
+ if (this.closePopover()) {
+ this.popover = new Popover(e, this.render(), {
+ verticalSide: 'bottom',
+ manualOffset: 14,
+ })
+ }
+ this.outcomeLineGraphView.setElement(this.popover.el.find('div.line-graph'))
+ return this.outcomeLineGraphView.render()
+ }
+}
+
+OutcomePopoverView.prototype.events = {
+ 'click i': 'mouseleave',
+ 'mouseenter i': 'mouseenter',
+ 'mouseleave i': 'mouseleave',
+}
+OutcomePopoverView.prototype.inside = false
+OutcomePopoverView.prototype.TIMEOUT_LENGTH = TIMEOUT_LENGTH
+
+OutcomePopoverView.optionProperty('el')
+OutcomePopoverView.optionProperty('model')
+
+export default OutcomePopoverView
diff --git a/ui/features/grade_summary/backbone/views/OutcomeView.coffee b/ui/features/grade_summary/backbone/views/OutcomeView.coffee
deleted file mode 100644
index 821f41637f9..00000000000
--- a/ui/features/grade_summary/backbone/views/OutcomeView.coffee
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import _ from 'underscore'
-import Backbone from '@canvas/backbone'
-import ProgressBarView from './ProgressBarView.coffee'
-import OutcomePopoverView from './OutcomePopoverView.coffee'
-import OutcomeDialogView from './OutcomeDialogView.coffee'
-import template from '../../jst/outcome.handlebars'
-
-export default class OutcomeView extends Backbone.View
- className: 'outcome'
- events:
- 'click .more-details' : 'show'
- 'keydown .more-details' : 'show'
- tagName: 'li'
- template: template
-
- initialize: ->
- super
- @progress = new ProgressBarView(model: @model)
-
- afterRender: ->
- @popover = new OutcomePopoverView({
- el: @$('.more-details')
- model: @model
- })
- @dialog = new OutcomeDialogView({
- model: @model
- })
-
- show: (e) ->
- @dialog.show e
-
- toJSON: ->
- json = super
- _.extend json,
- progress: @progress
-
diff --git a/ui/features/grade_summary/backbone/views/OutcomeView.js b/ui/features/grade_summary/backbone/views/OutcomeView.js
new file mode 100644
index 00000000000..6c3b4a27d5e
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/OutcomeView.js
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2014 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import Backbone from '@canvas/backbone'
+import ProgressBarView from './ProgressBarView'
+import OutcomePopoverView from './OutcomePopoverView'
+import OutcomeDialogView from './OutcomeDialogView'
+import template from '../../jst/outcome.handlebars'
+
+class OutcomeView extends Backbone.View {
+ initialize() {
+ super.initialize(...arguments)
+ return (this.progress = new ProgressBarView({model: this.model}))
+ }
+
+ afterRender() {
+ this.popover = new OutcomePopoverView({
+ el: this.$('.more-details'),
+ model: this.model,
+ })
+ return (this.dialog = new OutcomeDialogView({
+ model: this.model,
+ }))
+ }
+
+ show(e) {
+ return this.dialog.show(e)
+ }
+
+ toJSON() {
+ return {
+ ...super.toJSON(...arguments),
+ progress: this.progress,
+ }
+ }
+}
+
+OutcomeView.prototype.className = 'outcome'
+OutcomeView.prototype.tagName = 'li'
+OutcomeView.prototype.template = template
+OutcomeView.prototype.events = {
+ 'click .more-details': 'show',
+ 'keydown .more-details': 'show',
+}
+
+export default OutcomeView
diff --git a/ui/features/grade_summary/backbone/views/ProgressBarView.coffee b/ui/features/grade_summary/backbone/views/ProgressBarView.coffee
deleted file mode 100644
index ddb5ab5e119..00000000000
--- a/ui/features/grade_summary/backbone/views/ProgressBarView.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2014 - present Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-
-import Backbone from '@canvas/backbone'
-import template from '../../jst/progress_bar.handlebars'
-
-export default class ProgressBarView extends Backbone.View
- className: 'bar'
- template: template
diff --git a/ui/features/grade_summary/backbone/views/ProgressBarView.js b/ui/features/grade_summary/backbone/views/ProgressBarView.js
new file mode 100644
index 00000000000..769f82a688b
--- /dev/null
+++ b/ui/features/grade_summary/backbone/views/ProgressBarView.js
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2014 - present Instructure, Inc.
+//
+// This file is part of Canvas.
+//
+// Canvas is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, version 3 of the License.
+//
+// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see .
+
+import Backbone from '@canvas/backbone'
+import template from '../../jst/progress_bar.handlebars'
+
+class ProgressBarView extends Backbone.View {}
+
+ProgressBarView.prototype.className = 'bar'
+ProgressBarView.prototype.template = template
+
+export default ProgressBarView
diff --git a/ui/features/grade_summary/backbone/views/SectionView.js b/ui/features/grade_summary/backbone/views/SectionView.js
index e7926f66a60..e234ccd6ea3 100644
--- a/ui/features/grade_summary/backbone/views/SectionView.js
+++ b/ui/features/grade_summary/backbone/views/SectionView.js
@@ -17,7 +17,7 @@
import {View} from '@canvas/backbone'
import CollectionView from '@canvas/backbone-collection-view'
-import GroupView from './GroupView.coffee'
+import GroupView from './GroupView'
import template from '../../jst/section.handlebars'
export default class SectionView extends View {