purge performance gradebook

This removes the ill-fated React Gradebook.

This commit is effectively a revert of
b9534edd64

closes CNVS-32394

test plan:
* ensure 'Gradebook Performance' is no longer available
* ensure other Gradebooks are still functional
    * Default Gradebook basic happy path
    * Individual Gradebook (SRGB) basic happy path

Change-Id: Ie71ab4dfb17f494c2a7c17a27cd551a84e7efb96
Reviewed-on: https://gerrit.instructure.com/94005
Tested-by: Jenkins
Reviewed-by: Spencer Olson <solson@instructure.com>
QA-Review: KC Naegle <knaegle@instructure.com>
Product-Review: Christi Wruck
This commit is contained in:
Jeremy Neander 2016-10-31 10:16:38 -05:00
parent 2c1ae23b89
commit d0aa529dc6
129 changed files with 140 additions and 16089 deletions

View File

@ -2,11 +2,10 @@ require [
'jquery'
'Backbone'
'compiled/userSettings'
'compiled/gradebook2/ReactGradebook'
'compiled/gradebook2/Gradebook'
'compiled/views/gradebook/NavigationPillView'
'compiled/views/gradebook/OutcomeGradebookView'
], ($, Backbone, userSettings, ReactGradebook, Gradebook, NavigationPillView, OutcomeGradebookView) ->
], ($, Backbone, userSettings, Gradebook, NavigationPillView, OutcomeGradebookView) ->
class GradebookRouter extends Backbone.Router
routes:
@ -16,18 +15,11 @@ require [
initialize: ->
@isLoaded = false
@views = {}
@gradebook_performance_enabled = ENV.GRADEBOOK_OPTIONS.gradebook_performance_enabled
@views.assignment = @gradebook()
@views.assignment = new Gradebook(ENV.GRADEBOOK_OPTIONS)
if ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled
@views.outcome = @initOutcomes()
gradebook: ->
if @gradebook_performance_enabled
new ReactGradebook(ENV.GRADEBOOK_OPTIONS)
else
new Gradebook(ENV.GRADEBOOK_OPTIONS)
initOutcomes: ->
book = new OutcomeGradebookView(
el: $('.outcome-gradebook-container'),
@ -47,7 +39,7 @@ require [
@navigation.setActiveView(viewName) if @navigation
$('.assignment-gradebook-container, .outcome-gradebook-container').addClass('hidden')
$(".#{viewName}-gradebook-container").removeClass('hidden')
@views[viewName].onShow() unless @gradebook_performance_enabled
@views[viewName].onShow()
userSettings.contextSet 'gradebook_tab', viewName
@router = new GradebookRouter

View File

@ -1 +0,0 @@
require ['jsx/gradebook/grid/app']

View File

@ -2,7 +2,7 @@ define [
'ember'
'underscore'
'compiled/gradebook2/GradebookHelpers'
'jsx/gradebook/grid/constants'
'jsx/gradebook/shared/constants'
], (Ember, _, GradebookHelpers, GradebookConstants) ->
CustomColumnCellComponent = Ember.Component.extend

View File

@ -3,7 +3,7 @@ define [
'../start_app'
'../shared_ajax_fixtures'
'compiled/gradebook2/GradebookHelpers'
'jsx/gradebook/grid/constants'
'jsx/gradebook/shared/constants'
], (Ember, startApp, fixtures, GradebookHelpers, GradebookConstants) ->
{run} = Ember

View File

@ -36,7 +36,7 @@ define [
'compiled/views/gradebook/SectionMenuView'
'compiled/views/gradebook/GradingPeriodMenuView'
'compiled/gradebook2/GradebookKeyboardNav'
'jsx/gradebook/grid/helpers/columnArranger'
'jsx/gradebook/shared/helpers/assignmentHelper'
'compiled/api/gradingPeriodsApi'
'jst/_avatar' #needed by row_student_name
'jquery.ajaxJSON'
@ -53,14 +53,14 @@ define [
'jqueryui/sortable'
'compiled/jquery.kylemenu'
'compiled/jquery/fixDialogButtons'
], ($, _, Backbone, tz, DataLoader, React, ReactDOM, LongTextEditor,
KeyboardNavDialog, KeyboardNavTemplate, Slick, TotalColumnHeaderView, round,
InputFilterView, I18n, GRADEBOOK_TRANSLATIONS, GradeCalculator, UserSettings,
Spinner, SubmissionDetailsDialog, AssignmentGroupWeightsDialog,
GradeDisplayWarningDialog, PostGradesFrameDialog, SubmissionCell,
GradebookHeaderMenu, NumberCompare, htmlEscape, PostGradesStore, PostGradesApp,
SubmissionStateMap, ColumnHeaderTemplate, GroupTotalCellTemplate, RowStudentNameTemplate,
SectionMenuView, GradingPeriodMenuView, GradebookKeyboardNav, ColumnArranger, GradingPeriodsAPI) ->
], (
$, _, Backbone, tz, DataLoader, React, ReactDOM, LongTextEditor, KeyboardNavDialog, KeyboardNavTemplate, Slick,
TotalColumnHeaderView, round, InputFilterView, I18n, GRADEBOOK_TRANSLATIONS, GradeCalculator, UserSettings,
Spinner, SubmissionDetailsDialog, AssignmentGroupWeightsDialog, GradeDisplayWarningDialog, PostGradesFrameDialog,
SubmissionCell, GradebookHeaderMenu, NumberCompare, htmlEscape, PostGradesStore, PostGradesApp,
SubmissionStateMap, ColumnHeaderTemplate, GroupTotalCellTemplate, RowStudentNameTemplate, SectionMenuView,
GradingPeriodMenuView, GradebookKeyboardNav, assignmentHelper, GradingPeriodsAPI
) ->
class Gradebook
columnWidths =
@ -440,7 +440,7 @@ SectionMenuView, GradingPeriodMenuView, GradebookKeyboardNav, ColumnArranger, Gr
compareAssignmentDueDates: (a, b) ->
firstAssignment = a.object
secondAssignment = b.object
ColumnArranger.compareByDueDate(firstAssignment, secondAssignment)
assignmentHelper.compareByDueDate(firstAssignment, secondAssignment)
makeCompareAssignmentCustomOrderFn: (sortOrder) =>
sortMap = {}

View File

@ -10,7 +10,7 @@ define [
'jst/re_upload_submissions_form'
'underscore'
'compiled/behaviors/authenticity_token'
'jsx/gradebook/grid/helpers/messageStudentsWhoHelper'
'jsx/gradebook/shared/helpers/messageStudentsWhoHelper'
'jquery.instructure_forms'
'jqueryui/dialog'
'jquery.instructure_misc_helpers'

View File

@ -1,7 +1,7 @@
define [
'jquery'
'i18n!gradebook2'
'jsx/gradebook/grid/constants'
'jsx/gradebook/shared/constants'
], ($, I18n, GradebookConstants) ->
FLASH_ERROR_CLASS: '.ic-flash-error'

View File

@ -1,272 +0,0 @@
define [
'react'
'react-dom'
'compiled/util/round'
'compiled/views/InputFilterView'
'i18n!gradebook2'
'compiled/gradebook2/GradebookTranslations'
'jquery'
'underscore'
'compiled/userSettings'
'spin.js'
'str/htmlEscape'
# 'compiled/gradebook2/PostGradesDialog'
'jsx/gradebook/SISGradePassback/PostGradesStore'
'jsx/gradebook/SISGradePassback/PostGradesApp'
'jst/gradebook2/column_header'
'compiled/views/gradebook/SectionMenuView'
'compiled/views/gradebook/GradingPeriodMenuView'
'jsx/gradebook/grid/constants'
'jsx/gradebook/grid/actions/gradebookToolbarActions'
'jsx/gradebook/grid/actions/studentEnrollmentsActions'
'jsx/gradebook/grid/stores/assignmentGroupsStore'
'jsx/gradebook/grid/actions/assignmentGroupsActions'
'compiled/gradebook2/AssignmentGroupWeightsDialog'
'jsx/gradebook/grid/stores/sectionsStore'
'jsx/gradebook/grid/actions/sectionsActions'
'jsx/gradebook/grid/actions/tableActions'
'jst/_avatar' #needed by row_student_name
'jquery.ajaxJSON'
'jquery.instructure_date_and_time'
'jqueryui/dialog'
'jqueryui/tooltip'
'compiled/behaviors/tooltip'
'jquery.instructure_misc_helpers'
'jquery.instructure_misc_plugins'
'vendor/jquery.ba-tinypubsub'
'jqueryui/mouse'
'jqueryui/position'
'jqueryui/sortable'
'compiled/jquery.kylemenu'
'compiled/jquery/fixDialogButtons'
'vendor/jquery.ba-tinypubsub'
], (React, ReactDOM, round, InputFilterView, I18n, GRADEBOOK_TRANSLATIONS, $, _,
userSettings, Spinner, htmlEscape, PostGradesStore, PostGradesApp,
columnHeaderTemplate, SectionMenuView, GradingPeriodMenuView,
GradebookConstants, GradebookToolbarActions, StudentEnrollmentsActions,
AssignmentGroupsStore, AssignmentGroupActions, AssignmentGroupWeightsDialog,
SectionsStore, SectionsActions, TableActions) ->
class Gradebook
constructor: (@options) ->
@initGradingPeriods()
@initSections()
@initPostGradesStore()
@showPostGradesButton()
@initSettingsDropdown()
@initHeader()
@attachSearchBarEventHandlers()
@attachSetWeightsDialogHandlers()
initGradingPeriods: ->
@gradingPeriods = @options.active_grading_periods
initSections: ->
@sections = {}
$.subscribe 'currentSection/change', @onSectionChange
SectionsStore.listen (sectionStoreData) =>
sections = sectionStoreData.sections
@sectionToShow = sectionStoreData.selected
@sections = {}
if (sections != null && sections != undefined)
for section in sections
htmlEscape(section)
@sections[section.id] = section
if sections.length > 2 # 2 because a filler "All sections" is inserted
@drawSectionSelectButton(@sections)
onSectionChange: (section) ->
TableActions.enterLoadingState()
SectionsActions.selectSection(section)
@sectionToShow = section
if @sectionToShow
userSettings.contextSet('grading_show_only_section', @sectionToShow)
else
userSettings.contextRemove('grading_show_only_section', @sectionToShow)
initPostGradesStore: ->
@postGradesStore = PostGradesStore
course:
id: @options.context_id
sis_id: @options.context_sis_id
@postGradesStore.setSelectedSection @sectionToShow
showPostGradesButton: ->
app = React.createElement(PostGradesApp, store: @postGradesStore)
$placeholder = $('.post-grades-placeholder')
if ($placeholder.length > 0)
ReactDOM.render(app, $placeholder[0])
initSettingsDropdown: () ->
preferences = @getInitialToolbarPreferences()
@initCheckboxes(preferences)
@initStudentNamesOption(preferences)
@initArrangeByOption(preferences)
@initNotesColumnOption(preferences)
@attachSettingsDropdownEventHandlers(preferences)
sectionList: ->
_.map @sections, (section, id) =>
if(section.passback_status)
date = new Date(section.passback_status.sis_post_grades_status.grades_posted_at)
{ name: section.name, id: id, passback_status: section.passback_status, date: date, checked: @sectionToShow == id }
drawSectionSelectButton: (sections) ->
@sectionMenu = new SectionMenuView(
el: $('.section-button-placeholder'),
sections: sections,
course: {name: @options.course_name},
showSections: sections,
currentSection: @sectionToShow)
@sectionMenu.render()
getInitialToolbarPreferences: () ->
storedSortOrder = @options.gradebook_column_order_settings ||
{ sortType: 'assignment_group' }
savedPreferences =
hideStudentNames: userSettings.contextGet('hideStudentNames')
hideNotesColumn: !@options.teacher_notes || @options.teacher_notes.hidden
showAttendanceColumns: userSettings.contextGet('showAttendanceColumns')
showConcludedEnrollments: userSettings.contextGet('showConcludedEnrollments')
showInactiveEnrollments: userSettings.contextGet('showInactiveEnrollments')
arrangeBy: storedSortOrder.sortType
_.defaults(savedPreferences, GradebookConstants.DEFAULT_TOOLBAR_PREFERENCES)
initCheckboxes: (preferences) ->
$('#show_attendance').prop('checked', preferences.showAttendanceColumns)
$('#show_concluded_enrollments').prop('checked', (
@options.course_is_concluded || preferences.showConcludedEnrollments)
)
$('#show_inactive_enrollments').prop('checked', (
@options.course_is_concluded || preferences.showInactiveEnrollments)
)
initStudentNamesOption: (preferences) ->
namesHidden = preferences.hideStudentNames
if namesHidden then $('#student_names_toggle').addClass('hide_students')
displayText = if namesHidden then I18n.t('Show Student Names') else I18n.t('Hide Student Names')
$('#student_names_toggle').text(displayText)
initArrangeByOption: (preferences) ->
$arrangeBy = $('#arrange_by_toggle')
if preferences.arrangeBy == 'due_date'
arrangeByData = 'due_date'
displayText = I18n.t('Arrange Columns by Assignment Group')
else
arrangeByData = 'assignment_group'
displayText = I18n.t('Arrange Columns by Due Date')
$arrangeBy.data('arrange_by', arrangeByData)
$arrangeBy.text(displayText)
initNotesColumnOption: (preferences) ->
notesHidden = preferences.hideNotesColumn
if notesHidden then $('#notes_toggle').addClass('hide_notes')
displayText = if notesHidden then I18n.t('Show Notes Column') else I18n.t('Hide Notes Column')
$('#notes_toggle').text(displayText)
attachSettingsDropdownEventHandlers: () ->
$('#student_names_toggle').click(@studentNamesToggle)
$('#arrange_by_toggle').click(@arrangeByToggle)
$('#notes_toggle').click(@notesToggle)
$('#show_concluded_enrollments').change(@concludedEnrollmentsChange)
$('#show_inactive_enrollments').change(@inactiveEnrollmentsChange)
attachSetWeightsDialogHandlers: () ->
$.subscribe('assignment_group_weights_changed', @updateAssignmentGroupWeights)
$('#set-group-weights').click @openSetAssignmentGroupWeightsDialog
updateAssignmentGroupWeights: (options) ->
AssignmentGroupActions.replaceAssignmentGroups(options.assignmentGroups)
openSetAssignmentGroupWeightsDialog: () =>
assignmentGroups = AssignmentGroupsStore.assignmentGroups.data
params =
context: @options
assignmentGroups: assignmentGroups
new AssignmentGroupWeightsDialog params
attachSearchBarEventHandlers: () ->
$('.gradebook_filter').keyup ->
searchTerm = $('#gradebook-filter-input').val()
StudentEnrollmentsActions.search(searchTerm)
studentNamesToggle: (event) =>
event.preventDefault()
$studentNames = $(event.target)
$studentNames.toggleClass('hide_students')
hideStudents = $studentNames.hasClass('hide_students')
displayText = if hideStudents then I18n.t('Show Student Names') else I18n.t('Hide Student Names')
$studentNames.text(displayText)
GradebookToolbarActions.toggleStudentNames(hideStudents)
arrangeByToggle: (event) =>
event.preventDefault()
$arrangeBy = $(event.target)
if $arrangeBy.data('arrange_by') == 'due_date'
arrangeByData = 'assignment_group'
displayText = I18n.t('Arrange Columns by Due Date')
else
arrangeByData = 'due_date'
displayText = I18n.t('Arrange Columns by Assignment Group')
$arrangeBy.data('arrange_by', arrangeByData)
$arrangeBy.text(displayText)
GradebookToolbarActions.arrangeColumnsBy(arrangeByData)
notesToggle: (event) =>
event.preventDefault()
$notes = $(event.target)
$notes.toggleClass('hide_notes')
hideNotesColumn = $notes.hasClass('hide_notes')
displayText = if hideNotesColumn then I18n.t('Show Notes Column') else I18n.t('Hide Notes Column')
$notes.text(displayText)
GradebookToolbarActions.toggleNotesColumn(hideNotesColumn)
concludedEnrollmentsChange: () =>
$showConcludedEnrollments = $('#show_concluded_enrollments')
userSettings.contextSet 'showConcludedEnrollments', $showConcludedEnrollments.prop('checked')
StudentEnrollmentsActions.load()
inactiveEnrollmentsChange: () =>
$showInactiveEnrollments = $('#show_inactive_enrollments')
userSettings.contextSet 'showInactiveEnrollments', $showInactiveEnrollments.prop('checked')
StudentEnrollmentsActions.load()
initHeader: ->
@gradingPeriodToShow = @getGradingPeriodToShow()
@drawGradingPeriodSelectButton() if @options.multiple_grading_periods_enabled
$('#gradebook_settings').kyleMenu()
$('#download_csv').kyleMenu()
drawGradingPeriodSelectButton: () ->
@gradingPeriodMenu = new GradingPeriodMenuView(
el: $('.multiple-grading-periods-selector-placeholder'),
periods: @gradingPeriodList(),
currentGradingPeriod: @gradingPeriodToShow)
@gradingPeriodMenu.render()
gradingPeriodList: ->
_.map @options.active_grading_periods, (period) =>
{ title: period.title, id: period.id, checked: @gradingPeriodToShow == period.id }
getGradingPeriodToShow: () ->
currentPeriodId = userSettings.contextGet('gradebook_current_grading_period')
if currentPeriodId && (@isAllGradingPeriods(currentPeriodId) || @gradingPeriodIsActive(currentPeriodId))
currentPeriodId
else
ENV.GRADEBOOK_OPTIONS.current_grading_period_id
isAllGradingPeriods: (currentPeriodId) ->
currentPeriodId == "0"
gradingPeriodIsActive: (gradingPeriodId) ->
activePeriodIds = _.pluck(@gradingPeriods, 'id')
_.contains(activePeriodIds, gradingPeriodId)

View File

@ -1,18 +0,0 @@
define [
'compiled/util/round'
], (round) ->
GradeFormatter = (score, possibleScore, gradeAsPoints) ->
@score = score
@possibleScore = possibleScore
@gradeAsPoints = gradeAsPoints
GradeFormatter.prototype.toString = () ->
maxDecimals = round.DEFAULT
percentGrade = @score / @possibleScore * 100
if (not @score || percentGrade == Infinity || isNaN(percentGrade))
return '-'
else
return if @gradeAsPoints then @score else round(percentGrade, maxDecimals) + '%'
return GradeFormatter

View File

@ -1,5 +1,4 @@
define [
'jsx/gradebook/grid/actions/gradingPeriodsActions'
'compiled/userSettings'
'i18n!gradebook2'
'jquery'
@ -8,7 +7,7 @@ define [
'jst/gradebook2/grading_period_to_show_menu'
'compiled/jquery.kylemenu'
'vendor/jquery.ba-tinypubsub'
], (GradingPeriodsActions, userSettings, I18n, $, _, {View}, template) ->
], (userSettings, I18n, $, _, {View}, template) ->
class GradingPeriodMenuView extends View
@ -44,7 +43,6 @@ define [
)
onGradingPeriodChange: (period) =>
GradingPeriodsActions.select({ id: period }) if ENV.GRADEBOOK_OPTIONS.gradebook_performance_enabled
@currentGradingPeriod = period
@updateGradingPeriods()
@storePeriodSetting period

View File

@ -6,8 +6,7 @@ define [
'jst/gradebook2/section_to_show_menu'
'compiled/jquery.kylemenu'
'vendor/jquery.ba-tinypubsub'
'jsx/gradebook/grid/actions/sectionsActions'
], (I18n, $, _, {View}, template, SectionsActions) ->
], (I18n, $, _, {View}, template) ->
class SectionMenuView extends View

View File

@ -197,11 +197,10 @@ class GradebooksController < ApplicationController
set_js_env
@course_is_concluded = @context.completed?
@post_grades_tools = post_grades_tools
gradebook_version = @context.feature_enabled?(:gradebook_performance) ? :react_gradebook : :gradebook2
case @current_user.preferred_gradebook_version
when "2"
render gradebook_version
render :gradebook2
return
when "srgb"
render :screenreader
@ -373,7 +372,6 @@ class GradebooksController < ApplicationController
:gradebook_column_size_settings_url => change_gradebook_column_size_course_gradebook_url,
:gradebook_column_order_settings => @current_user.preferences[:gradebook_column_order].try(:[], @context.id),
:gradebook_column_order_settings_url => save_gradebook_column_order_course_gradebook_url,
:gradebook_performance_enabled => @context.feature_enabled?(:gradebook_performance),
:all_grading_periods_totals => @context.feature_enabled?(:all_grading_periods_totals),
:sections => sections_json(@context.active_course_sections, @current_user, session),
:settings_update_url => api_v1_course_gradebook_settings_update_url(@context),

View File

@ -1,20 +0,0 @@
define([
'reflux',
'jsx/gradebook/grid/constants',
'jquery'
], function (Reflux, GradebookConstants, $) {
var AssignmentGroupsActions = Reflux.createActions({
load: { asyncResult: true },
replaceAssignmentGroups: { asyncResult: false },
replaceAssignment: {asyncResult: false}
});
AssignmentGroupsActions.load.listen(function() {
var self = this;
$.getJSON(GradebookConstants.assignment_groups_url)
.done((json) => self.completed(json))
.fail((jqxhr, textStatus, error) => self.failed(error));
});
return AssignmentGroupsActions;
});

View File

@ -1,81 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/helpers/depaginator',
'jquery',
'i18n!gradebook2',
'jquery.ajaxJSON'
], function(Reflux, _, GradebookConstants, depaginate, $, I18n) {
let CustomColumnsActions = Reflux.createActions({
load: { asyncResult: true },
loadColumnData: { asyncResult: true },
loadTeacherNotes: { asyncResult: true }
});
let onTeacherNotesCompleted = function(notesData) {
GradebookConstants.teacher_notes = notesData;
this.completed();
};
let loadTeacherNotesFromServer = function() {
let data, method, onComplete, url;
url = GradebookConstants.custom_columns_url;
method = 'POST';
data = {
'column[title]': I18n.t('notes', 'Notes'),
'column[position]': 1,
'column[teacher_notes]': true
};
$.ajaxJSON(url, method, data)
.done(onTeacherNotesCompleted.bind(this));
};
let loadTeacherNotesFromENV = function() {
let teacherNotesUrl =
GradebookConstants
.custom_column_data_url
.replace(/:id/, GradebookConstants.teacher_notes.id);
depaginate(teacherNotesUrl, { include_hidden: true })
.then(this.completed);
};
let loadColumnsData = function(columns) {
_.each(columns, function(column) {
if (!column.hidden) {
CustomColumnsActions.loadColumnData(column.id);
}
this.completed(columns);
}.bind(this));
};
CustomColumnsActions.loadTeacherNotes.listen(function() {
if (!GradebookConstants.teacher_notes) {
(loadTeacherNotesFromServer.bind(this))();
} else {
(loadTeacherNotesFromENV.bind(this))();
}
});
CustomColumnsActions.load.listen(function() {
$.getJSON(GradebookConstants.custom_columns_url)
.done(loadColumnsData.bind(this))
.fail(this.failed);
});
CustomColumnsActions.loadColumnData.listen(function(columnId) {
let url;
url = GradebookConstants.custom_column_data_url.replace(/:id/, columnId);
$.getJSON(url)
.done(data => this.completed(data, columnId))
.fail((jqxhr, textStatus, error) => this.failed(error));
});
return CustomColumnsActions;
});

View File

@ -1,55 +0,0 @@
define([
'reflux',
'jquery',
'underscore',
'i18n!gradebook2',
'compiled/userSettings',
'jsx/gradebook/grid/constants'
], function (Reflux, $, _, I18n, userSettings, GradebookConstants) {
var GradebookToolbarActions = Reflux.createActions({
toggleStudentNames: { asyncResult: false },
toggleNotesColumn: { asyncResult: false },
toggleTreatUngradedAsZero: { asyncResult: false },
toggleTotalColumnInFront: { asyncResult: false },
arrangeColumnsBy: { asyncResult: false },
showTotalGradeAsPoints: { asyncResult: false },
hideTotalDisplayWarning: { asyncResult: false }
});
GradebookToolbarActions.toggleStudentNames.preEmit = (hideStudentNames) => {
userSettings.contextSet('hideStudentNames', hideStudentNames);
};
GradebookToolbarActions.toggleTreatUngradedAsZero.preEmit = (treatUngradedAsZero) => {
userSettings.contextSet('treatUngradedAsZero', treatUngradedAsZero);
};
GradebookToolbarActions.toggleTotalColumnInFront.preEmit = (totalColumnInFront) => {
userSettings.contextSet('total_column_in_front', totalColumnInFront);
};
GradebookToolbarActions.arrangeColumnsBy.preEmit = (criteria) => {
var columnOrderUrl = GradebookConstants.gradebook_column_order_settings_url;
var arrangeColumnsData = { column_order: { sortType: criteria } };
$.ajaxJSON(columnOrderUrl, 'POST', arrangeColumnsData);
};
GradebookToolbarActions.showTotalGradeAsPoints.preEmit = (showAsPoints) => {
$.ajaxJSON(
GradebookConstants.setting_update_url, "PUT",
{ show_total_grade_as_points: showAsPoints }
);
};
GradebookToolbarActions.hideTotalDisplayWarning.preEmit = (hideWarning) => {
userSettings.contextSet('warned_about_totals_display', hideWarning);
};
GradebookToolbarActions.toggleNotesColumn.preEmit = (hideNotesColumn) => {
var url = GradebookConstants.custom_column_url.replace(/:id/, GradebookConstants.teacher_notes.id);
$.ajaxJSON(url, 'PUT', { 'column[hidden]': hideNotesColumn });
};
return GradebookToolbarActions;
});

View File

@ -1,10 +0,0 @@
define([
'reflux',
'jquery'
], function (Reflux, $) {
var GradingPeriodsActions = Reflux.createActions([
'select'
]);
return GradingPeriodsActions;
});

View File

@ -1,12 +0,0 @@
define([
'reflux',
'jquery'
], function (Reflux, $) {
var KeyboardNavigationActions = Reflux.createActions([
'setActiveCell',
'constructKeyboardNavManager',
'handleKeyboardEvent'
]);
return KeyboardNavigationActions;
});

View File

@ -1,23 +0,0 @@
define([
'reflux',
'jquery',
'jsx/gradebook/grid/constants'
], function (Reflux, $, GradebookConstants) {
var SectionsActions = Reflux.createActions({
load: {asyncResult: true},
selectSection: {asyncResult: false}
})
SectionsActions.load.listen(function() {
var url, self;
self = this;
url = GradebookConstants.sections_url;
$.getJSON(url)
.then(this.completed)
.fail((jqxhr, textStatus, error) => self.failed(error));
});
return SectionsActions;
});

View File

@ -1,21 +0,0 @@
define([
'reflux',
'jquery',
'jsx/gradebook/grid/constants'
], function (Reflux, $, GradebookConstants) {
var SAVE_COLUMN_SIZE_URL = GradebookConstants.gradebook_column_size_settings_url;
var SettingsActions = Reflux.createActions([
'resize',
'saveColumnSize'
]);
SettingsActions.saveColumnSize.preEmit = (newColumnWidth, dataKey) => {
$.ajaxJSON(SAVE_COLUMN_SIZE_URL, 'POST', {
column_id: dataKey,
column_size: newColumnWidth
});
};
return SettingsActions;
});

View File

@ -1,36 +0,0 @@
define([
'reflux',
'compiled/userSettings',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/helpers/enrollmentsUrlHelper',
'jsx/gradebook/grid/helpers/depaginator',
], function (Reflux, UserSettings, GradebookConstants, EnrollmentsUrlHelper, depaginate) {
var StudentEnrollmentsActions = Reflux.createActions({
load: { asyncResult: true },
search: { asyncResult: false }
});
function showConcluded() {
return UserSettings.contextGet('showConcludedEnrollments') ||
GradebookConstants.course_is_concluded;
}
function showInactive() {
return UserSettings.contextGet('showInactiveEnrollments');
}
StudentEnrollmentsActions.load.listen(function() {
var self = this,
urlKey = EnrollmentsUrlHelper({
showConcluded: showConcluded(),
showInactive: showInactive()
});
depaginate(GradebookConstants[urlKey])
.then(self.completed)
.fail((jqxhr, textStatus, error) => self.failed(error));
});
return StudentEnrollmentsActions;
});

View File

@ -1,84 +0,0 @@
define([
'react',
'reflux',
'../constants',
'jquery',
'underscore',
'vendor/jquery.ba-tinypubsub'
], function (React, Reflux, GradebookConstants, $, _) {
var SubmissionsActions = Reflux.createActions({
load: { asyncResult: true },
updateGrade: { asyncResult: true },
updatedSubmissionsReceived: { asyncResult: false }
});
var splitUpIds = function(studentIds) {
var splitIds, idSet;
splitIds = [];
while (studentIds.length > 0) {
idSet = studentIds.splice(0, GradebookConstants.PAGINATION_COUNT);
splitIds.push(idSet);
}
return splitIds;
};
SubmissionsActions.load.listen(function (studentIds) {
var submissionsUrl, splitIds, deferreds, params;
submissionsUrl = GradebookConstants.submissions_url;
splitIds = splitUpIds(studentIds);
deferreds = _.map(splitIds, function(idArray) {
params = {};
params.student_ids = idArray;
params.response_fields = GradebookConstants.SUBMISSION_RESPONSE_FIELDS;
return $.ajaxJSON(submissionsUrl, 'GET', params);
});
$.when.apply($, deferreds).then(function() {
var results, allResults, responses;
allResults = [];
responses = arguments;
if (responses.length > 1 && responses[1] === 'success') {
responses = [responses];
}
// Orders responses so they're in the same order as requested
responses = _.map(deferreds, (deferred) =>
_.find(responses, function(result) {
var isDeferred = (result[2] === deferred);
return isDeferred;
}
));
for (var responseNumber = 0; responseNumber < responses.length; responseNumber++) {
results = responses[responseNumber][0];
allResults = allResults.concat(results);
}
this.completed(allResults);
}.bind(this));
});
SubmissionsActions.updateGrade.listen(function (submission) {
var url = GradebookConstants.change_grade_url
.replace(':assignment', submission.assignmentId)
.replace(':submission', submission.userId);
$.ajaxJSON(url, 'PUT', { 'submission[posted_grade]': submission.postedGrade })
.done((response) => this.completed(submission, response))
.fail((jqxhr, textStatus, error) => this.failed(error));
});
$.subscribe('submissions_updated', function (updatedSubmissions) {
if (updatedSubmissions.length > 0) {
SubmissionsActions.updatedSubmissionsReceived(updatedSubmissions);
}
});
return SubmissionsActions;
});

View File

@ -1,9 +0,0 @@
define([
'reflux',
], function (Reflux) {
var TableActions = Reflux.createActions([
'enterLoadingState'
]);
return TableActions;
});

View File

@ -1,9 +0,0 @@
define([
'react',
'react-dom',
'./components/gradebook'
], function (React, ReactDOM, Gradebook) {
let MOUNT_ELEMENT = document.getElementById('gradebook_grid');
ReactDOM.render(<Gradebook/>, MOUNT_ELEMENT);
});

View File

@ -1,99 +0,0 @@
define([
'react',
'compiled/SubmissionDetailsDialog',
'jsx/gradebook/grid/constants'
], function (React, SubmissionDetailsDialog, GradebookConstants) {
var GRADED = 'graded',
ICON_CLASS = 'icon-',
LATE_CLASS = 'late',
RESUBMITTED_CLASS = 'resubmiited',
SUBMISSION_TYPES = {
discussion_topic: 'discussion',
online_url: 'link',
online_text_entry: 'text',
online_upload: 'document',
online_quiz: 'quiz',
media_recording: 'media'
};
var AssignmentGradeCell = React.createClass({
propTypes: {
activeCell: React.PropTypes.bool.isRequired,
rowData: React.PropTypes.object.isRequired,
renderer: React.PropTypes.func.isRequired,
columnData: React.PropTypes.object
},
hasIconDefined() {
var submission = this.props.cellData;
if (!submission || submission.workflow_state === GRADED) {
return false;
}
return submission.submission_type in SUBMISSION_TYPES;
},
shouldRenderIcon() {
return this.hasIconDefined() && !this.props.activeCell;
},
openDialog(assignment, student) {
SubmissionDetailsDialog.open(assignment, student, GradebookConstants);
},
handleSubmissionCommentClick(event) {
var assignment, submission, student;
assignment = this.props.columnData.assignment;
student = this.props.rowData.student;
submission = this.props.cellData;
student["assignment_" + assignment.id] = submission;
this.openDialog(assignment, student);
},
renderIcon() {
var submissionType = this.props.cellData.submission_type,
className = ICON_CLASS + SUBMISSION_TYPES[submissionType];
return <i ref="icon" className={className}/>;
},
renderGradeCell() {
var Renderer = this.props.renderer;
return <Renderer isActiveCell={this.props.activeCell}
cellData={this.props.cellData}
submission={this.props.cellData}
columnData={this.props.columnData}
rowData={this.props.rowData}/>;
},
render() {
var className, child;
className = "gradebook-cell-comment";
child = this.shouldRenderIcon() ? this.renderIcon() : this.renderGradeCell();
if (this.props.activeCell) {
className += ' active';
}
return (
<div className="assignment-grade-cell">
<a ref="detailsDialog"
href="#"
onClick={this.handleSubmissionCommentClick}
className={className}>
<span className="gradebook-cell-comment-label">
I18n.t('submission comments')
</span>
</a>
{child}
</div>
);
}
});
return AssignmentGradeCell;
});

View File

@ -1,67 +0,0 @@
define([
'reflux',
'react',
'compiled/grade_calculator',
'compiled/gradebook2/gradeFormatter',
'../../stores/gradebookToolbarStore',
'../../stores/submissionsStore',
'../../helpers/currentOrFinal',
'underscore'
], function (Reflux, React, GradeCalculator, GradeFormatter,
GradebookToolbarStore, SubmissionsStore, currentOrFinal, _) {
var AssignmentGroupColumn = React.createClass({
propTypes: {
cellData: React.PropTypes.any.isRequired,
rowData: React.PropTypes.object.isRequired
},
mixins: [
Reflux.connect(GradebookToolbarStore, 'toolbarOptions')
],
formatTitle(assignmentGroupScore) {
return assignmentGroupScore.score + " / " + assignmentGroupScore.possible;
},
render() {
var assignmentGroupsIndex, submissions, assignmentGroups,
assignmentGroupGradeData, assignmentGroup, gradeFormatter, toolbarOptions,
groupSums, relevantAssignmentGroups, currentAssignmentGroup, assignmentGroupScore;
submissions = this.props.cellData.submissions;
submissions = _.flatten(_.values(submissions));
submissions = SubmissionsStore.submissionsInCurrentPeriod(submissions);
let assignmentGroupColumnId = this.props.cellData.columnId;
assignmentGroups = this.props.rowData.assignmentGroups;
currentAssignmentGroup = this.props.cellData.assignmentGroup;
relevantAssignmentGroups = SubmissionsStore.assignmentGroupsForSubmissions(submissions, assignmentGroups);
toolbarOptions = this.state.toolbarOptions;
if (this.groupIsInCurrentPeriod(currentAssignmentGroup, relevantAssignmentGroups)) {
assignmentGroupGradeData = GradeCalculator.calculate(submissions, assignmentGroups);
groupSums = assignmentGroupGradeData.group_sums;
groupSums = _.find(groupSums, sum => sum.group.columnId === assignmentGroupColumnId);
assignmentGroupScore = groupSums[currentOrFinal(toolbarOptions)];
} else {
assignmentGroupScore = {score: 0, possible: 0};
}
gradeFormatter = new GradeFormatter(assignmentGroupScore.score, assignmentGroupScore.possible, false);
return (
<div className='assignment-group-grade' title={this.formatTitle(assignmentGroupScore)} ref='cell'>
{ gradeFormatter.toString() }
</div>
);
},
groupIsInCurrentPeriod(assignmentGroup, relevantAssignmentGroups) {
return _.contains(relevantAssignmentGroups, assignmentGroup);
}
});
return AssignmentGroupColumn;
});

View File

@ -1,44 +0,0 @@
define([
'react',
'../../mixins/gradeCellMixin',
'../../mixins/standardGradeInputMixin',
'../../mixins/standardCellFocusMixin',
'../../mixins/standardRenderMixin'
], function (
React,
GradeCellMixin,
StandardGradeInputMixin,
StandardCellFocusMixin,
StandardRenderMixin
) {
var AssignmentLetterGrade = React.createClass({
mixins: [
GradeCellMixin,
StandardGradeInputMixin,
StandardCellFocusMixin,
StandardRenderMixin
],
renderViewGrade() {
var submission = this.props.cellData;
if (submission && submission.grade) {
var gradingType = this.props.cellData.grading_type,
score = (gradingType == 'letter_grade') ? submission.score : '';
return (
<div ref="grade">
{submission.grade}
<span className="letter-grade-points">
{score}
</span>
</div>
);
} else {
return <div className='grade' ref="grade">-</div>;
}
}
});
return AssignmentLetterGrade;
});

View File

@ -1,68 +0,0 @@
define([
'react',
'underscore',
'../../mixins/gradeCellMixin',
'../../actions/submissionsActions'
], function (React, _, GradeCellMixin, SubmissionsActions) {
var GRADEBOOK_CHECKBOX_CLASS = 'gradebook-checkbox';
var NEXT_GRADE_TYPE = {
'' : 'complete',
'complete' : 'incomplete',
'incomplete': ''
};
var AssignmentPassFail = React.createClass({
mixins: [GradeCellMixin],
getCurrentGrade() {
var previousGrade = (this.props.cellData) ? this.props.cellData.grade : null,
grade;
if (this.state.gradeToPost || this.state.gradeToPost === "") {
grade = this.state.gradeToPost;
} else {
grade = previousGrade;
}
return grade;
},
getClassName() {
var className = 'grade';
if (this.getCurrentGrade() || this.getCurrentGrade() === '' || this.props.isActiveCell) {
className += ' ' + GRADEBOOK_CHECKBOX_CLASS + ' '
+ GRADEBOOK_CHECKBOX_CLASS + '-'
+ this.getCurrentGrade();
}
if (this.props.isActiveCell) {
className += ' editable';
}
return className;
},
handleClick() {
var currentGrade = this.getCurrentGrade() || '',
isActiveCell = this.props.isActiveCell,
gradeToPost = (isActiveCell) ? NEXT_GRADE_TYPE[currentGrade]
: currentGrade;
this.setState({gradeToPost: gradeToPost}, this.sendSubmission);
},
render() {
var cellContent = (!this.props.cellData || this.isSubmissionGradedAsNull()) ? '-' : '';
return (
<div style={{width: '100%', height: '100%'}} onClick={this.handleClick}>
<div ref="grade" className={this.getClassName()}>
{cellContent}
</div>
</div>
);
}
});
return AssignmentPassFail;
});

View File

@ -1,35 +0,0 @@
define([
'react',
'../../actions/submissionsActions',
'../../mixins/gradeCellMixin',
'../../mixins/standardGradeInputMixin',
'../../mixins/standardCellFocusMixin',
'../../mixins/standardRenderMixin'
], function (
React,
SubmissionsActions,
GradeCellMixin,
StandardGradeInputMixin,
StandardCellFocusMixin,
StandardRenderMixin
) {
var AssignmentPercentage = React.createClass({
mixins: [
GradeCellMixin,
StandardGradeInputMixin,
StandardCellFocusMixin,
StandardRenderMixin
],
renderViewGrade() {
return (
<div className="grade" ref="grade">
{this.getDisplayGrade().replace("%", "")}
</div>
);
}
});
return AssignmentPercentage;
});

View File

@ -1,52 +0,0 @@
define([
'react',
'../../mixins/gradeCellMixin',
'../../mixins/standardRenderMixin',
'../../mixins/standardGradeInputMixin',
'../../mixins/standardCellFocusMixin'
], function (React, GradeCellMixin, StandardRenderMixin, StandardGradeInputMixin,
StandardCellFocusMixin) {
var AssignmentPoints = React.createClass({
mixins: [
GradeCellMixin,
StandardRenderMixin,
StandardCellFocusMixin
],
handleOnChange(event) {
this.setState({gradeToPost: event.target.value});
},
renderEditGrade() {
return (
<div className="points-input">
<div className="out-of-float">
<input
type="text"
onChange={this.handleOnChange}
className="grade out-of-grade"
ref="gradeInput"
value={this.state.gradeToPost || this.getDisplayGrade()}/>
</div>
<div className="out-of-float out-of-points">
<span className="divider">/</span>
<span ref="pointsPossible">
{this.props.columnData.assignment.points_possible}
</span>
</div>
</div>
);
},
renderViewGrade() {
return (
<div className="grade" ref="grade">
{this.getDisplayGrade()}
</div>
);
}
});
return AssignmentPoints;
});

View File

@ -1,41 +0,0 @@
define([
'react',
'reflux',
'jsx/gradebook/grid/stores/customColumnsStore',
'jsx/gradebook/grid/components/column_types/teacherNote'
], function(React, Reflux, CustomColumnsStore, TeacherNote) {
let CustomColumn = React.createClass({
propTypes: {
columnData: React.PropTypes.object.isRequired,
rowData: React.PropTypes.object.isRequired,
cellData: React.PropTypes.object
},
content(columnDatum) {
if (columnDatum === null || columnDatum === undefined) {
return '';
}
return columnDatum.content;
},
render() {
let columnDatum, columnId, content, studentName, userId;
columnId = this.props.columnData.customColumnData.id;
userId = this.props.rowData.student.user_id;
columnDatum = this.props.cellData;
content = this.content(columnDatum);
studentName= this.props.rowData.student.user.name;
return (
<TeacherNote
key={'custom_columns-' + userId + '-' + columnId}
note={content} userId={userId} studentName={studentName}
columnId={columnId} />
);
}
});
return CustomColumn;
});

View File

@ -1,148 +0,0 @@
define([
'react',
'underscore',
'i18n!gradebook',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/components/dropdown_components/gradebookKyleMenu',
'jquery',
'jsx/gradebook/grid/components/dropdown_components/assignmentHeaderDropdownOptions',
'jsx/gradebook/grid/components/dropdown_components/totalHeaderDropdownOptions',
'jquery.instructure_date_and_time'
], function(React, _, I18n, GradebookConstants, GradebookKyleMenu, $, AssignmentHeaderDropdownOptions, TotalHeaderDropdownOptions) {
var HeaderRenderer = React.createClass({
propTypes: {
label: React.PropTypes.string.isRequired,
columnData: React.PropTypes.object.isRequired
},
prettyDate(date) {
return $.dateString(date, { localized: true });
},
getHeaderDateFromOverrides(overrides) {
var overrideWithDueAt;
if (overrides.length > 1) { return I18n.t('Multiple due dates'); }
overrideWithDueAt = _.find(overrides, override => override.due_at);
if (overrideWithDueAt) {
return I18n.t('Due %{dueAt}', { dueAt: this.prettyDate(overrideWithDueAt.due_at) });
} else {
return I18n.t('No due date');
}
},
headerDate(columnData) {
var assignment, dateContent;
assignment = columnData.assignment;
if (!assignment) {
dateContent = undefined;
} else if (assignment.due_at) {
dateContent = I18n.t('Due %{dueAt}', { dueAt: this.prettyDate(assignment.due_at) });
} else if (assignment.overrides) {
dateContent = this.getHeaderDateFromOverrides(assignment.overrides);
} else {
dateContent = I18n.t('No due date');
}
return dateContent;
},
shouldDisplayAssignmentWarning() {
var assignment = this.props.columnData.assignment;
return assignment.shouldShowNoPointsWarning
&& GradebookConstants.group_weighting_scheme === 'percent';
},
getTitle() {
if (this.shouldDisplayAssignmentWarning()) {
return I18n.t('Assignments in this group have no points possible and cannot be included in grade calculation');
}
},
getWidth() {
var paddingAdjustment = GradebookConstants.DEFAULT_LAYOUTS.headers.paddingAdjustment;
return this.props.width - paddingAdjustment;
},
label(columnData) {
var assignment, label;
assignment = columnData.assignment;
label = this.props.label;
if (assignment) {
var className = 'assignment-name' + ((assignment.muted) ? ' muted' : '');
return (
<div title={label} className='gradebook-label' style={{width: this.getWidth()}}>
<a className={className} href={assignment.html_url}>
{ this.shouldDisplayAssignmentWarning() && <i ref='icon' title={this.getTitle()} className='icon-warning'></i> }
{label}
</a>
</div>
);
} else {
return (
<div title={label} className='gradebook-label' style={{width: this.getWidth()}}>
{label}
</div>
);
}
return label;
},
renderDropdown(columnData) {
let columnType = columnData.columnType,
assignment = columnData.assignment,
enrollments = columnData.enrollments,
submissions = columnData.submissions,
key, dropdownOptionsId;
if (assignment) {
key = 'assignment-' + assignment.id;
dropdownOptionsId = key + '-options';
return (
<GradebookKyleMenu key={key} dropdownOptionsId={dropdownOptionsId}
idToAppendTo='gradebook_grid' screenreaderText={I18n.t('Assignment Options')}
defaultClassNames='gradebook-header-drop' options={{ noButton: true }}>
<AssignmentHeaderDropdownOptions key={dropdownOptionsId}
idAttribute={dropdownOptionsId} assignment={assignment}
enrollments={enrollments} submissions={submissions}/>
</GradebookKyleMenu>
);
} else if (columnType === 'total') {
return (
<GradebookKyleMenu key='total' dropdownOptionsId='total-options'
idToAppendTo='gradebook_grid' screenreaderText={I18n.t('Total Column Options')}
defaultClassNames='gradebook-header-drop' options={{ noButton: true }}>
<TotalHeaderDropdownOptions key='total-options' idAttribute='total-options'/>
</GradebookKyleMenu>
);
}
},
render() {
let columnData = this.props.columnData,
dueDate = this.headerDate(columnData),
className = (dueDate) ? '' : ' title';
return (
<div style={{width: this.getWidth()}}
title={this.props.label}
className={'gradebook-label gradebook-header-column' + className}>
{this.label(columnData)}
{this.renderDropdown(columnData)}
<div title={dueDate} className='assignment-due-date' ref='dueDate'>
{dueDate}
</div>
</div>
);
}
});
return HeaderRenderer;
});

View File

@ -1,28 +0,0 @@
define([
'react',
'jsx/gradebook/grid/components/column_types/teacherNote',
'jsx/gradebook/grid/constants'
], function(React, TeacherNote, GradebookConstants) {
let NotesColumn = React.createClass({
propTypes: {
rowData: React.PropTypes.object.isRequired
},
render() {
let student = this.props.rowData.student,
studentName = this.props.rowData.studentName,
teacherNote = this.props.rowData.teacherNote,
columnId = GradebookConstants.teacher_notes.id;
return (
<TeacherNote
key={'notes' + student.user_id} note={teacherNote}
userId={student.user_id} studentName={studentName}
columnId={columnId} />
);
}
});
return NotesColumn;
});

View File

@ -1,73 +0,0 @@
define([
'react',
'i18n!gradebook2',
'reflux',
'../../stores/gradebookToolbarStore',
'../../constants'
], function(React, I18n, Reflux, GradebookToolbarStore, GradebookConstants) {
let StudentNameColumn = React.createClass({
mixins: [Reflux.connect(GradebookToolbarStore, "toolbarOptions")],
rowData() {
return this.props.rowData;
},
renderHiddenName() {
const hiddenText = I18n.t('Hidden');
return <span title={hiddenText}>{hiddenText}</span>;
},
isConcludedOrInactive() {
return this.isConcluded() || this.isInactive();
},
isConcluded() {
return this.rowData().isConcluded;
},
isInactive() {
return this.rowData().isInactive;
},
renderEnrollmentStatus() {
let enrollmentStatus;
const labelTitle = I18n.t('This user is currently not able to access the course');
if(this.isConcluded()) {
enrollmentStatus = I18n.t('concluded');
} else if(this.isInactive()) {
enrollmentStatus = I18n.t('inactive');
}
return <span ref="enrollmentStatus" className='label'
title={labelTitle}>{enrollmentStatus}</span>
},
renderStudentName() {
var displayName = this.rowData().studentName;
return <a ref="gradesUrl" title={displayName}
href={this.rowData().student.grades.html_url}>{displayName}</a>
},
renderHiddenOrStudentName() {
var hideStudentNames = this.state.toolbarOptions.hideStudentNames;
if(hideStudentNames) {
return this.renderHiddenName();
} else {
return this.renderStudentName();
}
},
render() {
return (
<div ref="studentName" className="student-name">
{this.renderHiddenOrStudentName()} {this.isConcludedOrInactive() ? this.renderEnrollmentStatus() : ''}
</div>
);
}
});
return StudentNameColumn;
});

View File

@ -1,150 +0,0 @@
define([
'react',
'underscore',
'jquery',
'i18n!gradebook2',
'reflux',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/actions/customColumnsActions',
'react-modal',
'jsx/gradebook/grid/constants',
'compiled/gradebook2/GradebookHelpers',
'compiled/jquery.rails_flash_notifications'
], function(React, _, $, I18n, Reflux, GradebookToolbarStore,
CustomColumnsActions, Modal, GradebookConstants, GradebookHelpers) {
const modalOverrides = {
overlay : {
backgroundColor: 'rgba(0,0,0,0.5)'
},
content : {
position: 'static',
top: '0',
left: '0',
right: 'auto',
bottom: 'auto',
borderRadius: '0',
border: 'none',
padding: '0'
}
};
var TeacherNote = React.createClass({
propTypes: {
note: React.PropTypes.string.isRequired,
userId: React.PropTypes.string.isRequired,
studentName: React.PropTypes.string.isRequired,
columnId: React.PropTypes.string.isRequired
},
mixins: [
Reflux.connect(GradebookToolbarStore, 'toolbarOptions')
],
getInitialState() {
return {
showModal: false,
content: this.props.note
};
},
showModal() {
this.setState({ showModal: true });
},
hideModal() {
this.setState({ showModal: false, content: this.props.note });
},
updateContent(event) {
let newNote = event.target.value;
if (GradebookHelpers.textareaIsLessThanOrEqualToMaxLength(newNote.length)) {
this.setState({ content: newNote });
} else if(GradebookHelpers.maxLengthErrorShouldBeShown(newNote.length)) {
GradebookHelpers.flashMaxLengthError();
}
},
handleSubmit() {
var columnId = this.props.columnId,
regexReplace = columnId + '/data/' + this.props.userId,
url = GradebookConstants.custom_column_datum_url.replace(/:id\/data\/:user_id/, regexReplace);
$.ajaxJSON(url, 'PUT', { 'column_data[content]': this.state.content },
() => {
this.setState({ showModal: false });
},
() => {
$.flashError(I18n.t('There was an error saving the note. Please try again.'));
}
);
},
nameToDisplay() {
var hideNames = this.state.toolbarOptions.hideStudentNames;
return hideNames ? I18n.t('student (name hidden)') : this.props.studentName;
},
render() {
var close, save, title;
close = I18n.t('Close');
title = I18n.t('Notes for %{name}', { name: this.nameToDisplay() });
save = I18n.t('Save');
return (
<div ref='noteCell' className='teacher-note' onClick={this.showModal}>
<span>{this.state.content}</span>
<Modal
className='ReactModal__Content--canvas ReactModal__Content--mini-modal'
overlayClassName='ReactModal__Overlay--canvas'
style={modalOverrides}
isOpen={this.state.showModal}
onRequestClose={this.hideModal}>
<div>
<div className="ReactModal__Layout">
<div className="ReactModal__InnerSection ReactModal__Header">
<div className="ReactModal__Header-Title">
<h4 ref='studentName'>
{title}
</h4>
</div>
<div className="ReactModal__Header-Actions">
<button className="Button Button--icon-action"
type="button" onClick={this.hideModal}>
<i className="icon-x"></i>
<span className="screenreader-only">
{close}
</span>
</button>
</div>
</div>
<div className="ReactModal__InnerSection ReactModal__Body notes-body"
onClick={this.makeTextEditable}>
<textarea className='notes-textarea'
value={this.state.content} onChange={this.updateContent}/>
</div>
<div className="ReactModal__InnerSection ReactModal__Footer">
<div className="ReactModal__Footer-Actions">
<button type="button" className="btn btn-default"
onClick={this.hideModal}>
{I18n.t('Cancel')}
</button>
<button type="submit" className="btn btn-primary"
disabled={this.state.content === this.props.note}
onClick={this.handleSubmit}>
{save}
</button>
</div>
</div>
</div>
</div>
</Modal>
</div>
);
},
});
return TeacherNote;
});

View File

@ -1,139 +0,0 @@
define([
'reflux',
'react',
'jquery',
'underscore',
'i18n!gradebook2',
'compiled/grade_calculator',
'jsx/gradebook/grid/constants',
'compiled/gradebook2/gradeFormatter',
'../../stores/gradebookToolbarStore',
'../../stores/submissionsStore',
'../../helpers/currentOrFinal',
], function (Reflux, React, $, _, I18n, GradeCalculator, GRADEBOOK_CONSTANTS,
GradeFormatter, GradebookToolbarStore, SubmissionsStore, currentOrFinal) {
function generateGroupHasNoPointsWarning(groupNames) {
return I18n.t({
one: 'Score does not include %{groups} because it has no points possible',
other: 'Score does not include %{groups} because they have no points possible'
}, {
groups: $.toSentence(groupNames),
count: groupNames.length // TODO: delete me?
});
}
var TotalColumn = React.createClass({
propTypes: {
rowData: React.PropTypes.object.isRequired
},
mixins: [
Reflux.connect(GradebookToolbarStore, 'toolbarOptions')
],
assignments() {
return _.flatten(_.pluck(this.assignmentGroups(), 'assignments'));
},
visibleAssignments() {
return _.reject(this.assignments(), (a) =>
_.contains(a.submission_types, 'not_graded'));
},
getWarning() {
let result = '';
if (this.anyMutedAssignments()) {
result = I18n.t("This grade differs from the student's view of the grade because some assignments are muted");
} else if (ENV.GRADEBOOK_OPTIONS.group_weighting_scheme === 'percent') {
const invalidGroups = _.filter(this.assignmentGroups(), group => group.shouldShowNoPointsWarning);
if (invalidGroups.length > 0) {
const groupNames = _.pluck(invalidGroups, 'name');
result = generateGroupHasNoPointsWarning(groupNames);
}
} else if (this.noPointsPossible()) {
result = I18n.t("Can't compute score until an assignment has points possible");
}
return result;
},
anyMutedAssignments() {
return _.any(this.visibleAssignments(), (va) => va.muted);
},
noPointsPossible() {
const pointsPossible = _.inject(
this.assignments(),
(sum, a) => sum + (a.points_possible || 0), 0
);
return pointsPossible === 0;
},
iconClassNames() {
let result = '';
if (this.anyMutedAssignments()) {
result = 'icon-muted final-warning';
} else if (this.noPointsPossible()) {
result = 'icon-warning final-warning';
}
return result;
},
submissions() {
return SubmissionsStore.submissionsInCurrentPeriod(_.flatten(_.values(this.props.rowData.submissions)));
},
assignmentGroups() {
return this.props.rowData.assignmentGroups;
},
assignmentGroupsForSubmissions() {
return SubmissionsStore.assignmentGroupsForSubmissions(
this.submissions(),
this.assignmentGroups()
);
},
totalGradeData() {
const groupWeightingScheme = ENV.GRADEBOOK_OPTIONS.group_weighting_scheme;
return GradeCalculator.calculate(
this.submissions(),
this.assignmentGroupsForSubmissions(),
groupWeightingScheme
);
},
toolbarOptions() {
return this.state.toolbarOptions;
},
total() {
return this.totalGradeData()[currentOrFinal(this.toolbarOptions())];
},
gradeFormatter() {
const showPoints = this.toolbarOptions().showTotalGradeAsPoints,
score = this.total().score,
possible = this.total().possible;
return new GradeFormatter(score, possible, showPoints);
},
render() {
return (
<div ref="cell" title={this.getWarning()}>
<i ref="icon" className={this.iconClassNames()} />
<span className="total-grade" ref="totalGrade">{ this.gradeFormatter().toString() }</span>
</div>
);
}
});
return TotalColumn;
});

View File

@ -1,102 +0,0 @@
define([
'react',
'underscore',
'i18n!gradebook',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/components/dropdown_components/setDefaultGradeOption',
'jsx/gradebook/grid/components/dropdown_components/muteAssignmentOption',
'jsx/gradebook/grid/components/dropdown_components/messageStudentsWhoOption'
], function (React, _, I18n, HeaderDropdownOption, GradebookConstants, SetDefaultGradeOption, MuteAssignmentOption, MessageStudentsWhoOption) {
var AssignmentHeaderDropdownOptions = React.createClass({
propTypes: {
assignment: React.PropTypes.object.isRequired,
submissions: React.PropTypes.object.isRequired,
idAttribute: React.PropTypes.string.isRequired,
enrollments: React.PropTypes.array.isRequired
},
getDropdownOptions() {
var assignment = this.props.assignment,
assignmentUrl = assignment.html_url,
downloadableSubmissions = ['online_upload', 'online_text_entry', 'online_url'],
hasDownloadableSubmissions = _.any(_.intersection(downloadableSubmissions, assignment.submission_types)),
dropdownOptions = [
{ title: I18n.t('Assignment Details'), action: 'showAssignmentDetails', url: assignmentUrl },
{ title: I18n.t('Message Students Who...'), action: 'messageStudentsWho' },
{ action: 'setDefaultGrade' },
{ title: I18n.t('Curve Grades'), action: 'curveGrades' }
],
downloadSubmissionsOption, reuploadSubmissionsOption, speedGraderUrl, speedGraderOption,
muteAssignmentOption;
if (hasDownloadableSubmissions && assignment.has_submitted_submissions) {
downloadSubmissionsOption = { title: I18n.t('Download Submissions'), action: 'downloadSubmissions' };
dropdownOptions.push(downloadSubmissionsOption);
}
if (GradebookConstants.gradebook_is_editable && assignment.submissions_downloads > 0 ) {
reuploadSubmissionsOption = { title: I18n.t('Re-Upload Submissions'), action: 'reuploadSubmissions' };
dropdownOptions.push(reuploadSubmissionsOption);
}
if (GradebookConstants.speed_grader_enabled) {
speedGraderUrl = assignment.speedgrader_url;
speedGraderOption = { title: I18n.t('Speedgrader'), url: speedGraderUrl, action: 'openSpeedgrader' };
dropdownOptions.splice(1, 0, speedGraderOption);
}
return dropdownOptions;
},
render() {
var options = this.getDropdownOptions(),
assignment = this.props.assignment,
enrollments = this.props.enrollments,
submissions = this.props.submissions,
key;
return (
<ul id={this.props.idAttribute} className="gradebook-header-menu">
{
// this map is temporary and will go away. eventually we'll have a
// renderer for each dropdown option. for now, we render a generic
// HeaderDropDownOption for yet-to-be-implemented dropdown options.
_.map(options, (listItem) => {
key = listItem.action + '-' + assignment.id;
if (listItem.action === 'setDefaultGrade') {
return (
<SetDefaultGradeOption
key={key}
assignment={assignment}
enrollments={enrollments}
contextId={GradebookConstants.context_id}/>
);
} else if (listItem.action === 'messageStudentsWho') {
return (
<MessageStudentsWhoOption
key={key} title={listItem.title} assignment={assignment}
enrollments={enrollments} submissions={submissions}/>
);
} else {
return(
<HeaderDropdownOption
key={key} title={listItem.title}
dataAction={listItem.action} url={listItem.url}
ref={listItem.action}/>
);
}
})
}
<MuteAssignmentOption
key={'muteAssignment-' + assignment.id}
assignment={assignment}/>
</ul>
);
}
});
return AssignmentHeaderDropdownOptions;
});

View File

@ -1,34 +0,0 @@
define([
'react',
'reflux',
'i18n!gradebook',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/actions/gradebookToolbarActions',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption'
], function (React, Reflux, I18n, GradebookToolbarStore, GradebookToolbarActions, HeaderDropdownOption) {
var CurrentOrFinalGradeToggle = React.createClass({
mixins: [
Reflux.connect(GradebookToolbarStore, 'toolbarOptions'),
],
showFinalGrade() {
return this.state.toolbarOptions.treatUngradedAsZero;
},
toggle(event) {
event.preventDefault();
var showFinalGrade = !this.showFinalGrade();
GradebookToolbarActions.toggleTreatUngradedAsZero(showFinalGrade);
},
render() {
var text = this.showFinalGrade() ? I18n.t('Show Current Grade') : I18n.t('Show Final Grade');
return (
<HeaderDropdownOption title={text} handleClick={this.toggle} ref='gradeToggle'/>
);
}
});
return CurrentOrFinalGradeToggle;
});

View File

@ -1,61 +0,0 @@
define([
'react',
'jquery',
], function (React, $) {
var GradebookKyleMenu = React.createClass({
propTypes: {
dropdownOptionsId: React.PropTypes.string.isRequired,
idToAppendTo: React.PropTypes.string.isRequired,
screenreaderText: React.PropTypes.string.isRequired,
defaultClassNames: React.PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired,
options: React.PropTypes.object
},
getInitialState() {
return { showMenu: false, menuOpen: false };
},
handleMenuPopup(event) {
this.setState({ menuOpen: event.type === 'popupopen' });
},
handleDropdownClick(event) {
var $link = $(event.target);
var idToAppendTo = '#' + this.props.idToAppendTo;
var kyleMenuOptions = this.props.options || {};
var menuId = '#' + this.props.children.props.idAttribute;
this.setState({ showMenu: true }, function(){
var $menu = $(menuId);
$link.kyleMenu(kyleMenuOptions);
$menu.appendTo(idToAppendTo).bind('popupopen popupclose', (event) => {
this.handleMenuPopup(event);
}).popup('open');
});
},
cssClassNames() {
var classNames = this.props.defaultClassNames;
if (this.state.menuOpen) classNames += ' ui-menu-trigger-menu-is-open';
return classNames;
},
render() {
return (
<div>
<a className={this.cssClassNames()}
href='#'
ref='dropdownLink'
onClick={this.handleDropdownClick}>
{this.props.screenreaderText}
</a>
{this.state.showMenu && this.props.children}
</div>
);
}
});
return GradebookKyleMenu;
});

View File

@ -1,30 +0,0 @@
define([
'react',
'i18n!gradebook'
], function (React, I18n) {
var HeaderDropdownOption = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
dataAction: React.PropTypes.string,
url: React.PropTypes.string,
handleClick: React.PropTypes.func
},
render() {
return (
<li>
<a data-action={this.props.dataAction}
href={this.props.url || '#'}
onClick={this.props.handleClick}
ref='link'>
{this.props.title}
</a>
</li>
);
}
});
return HeaderDropdownOption;
});

View File

@ -1,51 +0,0 @@
define([
'react',
'underscore',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption',
'jsx/gradebook/grid/helpers/submissionsHelper',
'jsx/gradebook/grid/helpers/enrollmentsHelper',
'timezone',
'jsx/gradebook/grid/helpers/messageStudentsWhoHelper',
'message_students'
], function (React, _, HeaderDropdownOption, SubmissionsHelper, EnrollmentsHelper, tz, MessageStudentsWhoHelper) {
let MessageStudentsWhoOption = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
assignment: React.PropTypes.object.isRequired,
enrollments: React.PropTypes.array.isRequired,
submissions: React.PropTypes.object.isRequired
},
openDialog() {
let studentsForAssignment = EnrollmentsHelper.studentsThatCanSeeAssignment(this.props.enrollments, this.props.assignment);
let students = this.combineStudentsWithScores(studentsForAssignment);
let settings = MessageStudentsWhoHelper.settings(this.props.assignment, students);
messageStudents(settings);
},
combineStudentsWithScores(students) {
let submissions = SubmissionsHelper.submissionsForAssignment(this.props.submissions, this.props.assignment);
return _.map(students, function(student, studentId) {
let studentWithScore = _.extend({ score: null, submitted_at: null }, student);
let submission = submissions[studentId];
if (submission) {
studentWithScore.score = submission.score;
studentWithScore.submitted_at = tz.parse(submission.submitted_at);
}
return studentWithScore;
});
},
render() {
return(
<HeaderDropdownOption
handleClick={this.openDialog}
key={'messageStudentsWho' + this.props.assignment.id}
title={this.props.title}/>
);
}
});
return MessageStudentsWhoOption;
});

View File

@ -1,36 +0,0 @@
define([
'react',
'reflux',
'i18n!gradebook',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/actions/gradebookToolbarActions'
], function (React, Reflux, I18n, HeaderDropdownOption, GradebookToolbarStore, GradebookToolbarActions) {
var TO_END = I18n.t("Move to end"),
TO_FRONT = I18n.t("Move to front");
var MoveTotalColumnToggle = React.createClass({
mixins: [
Reflux.connect(GradebookToolbarStore, 'toolbarOptions'),
],
isTotalColumnInFront() {
return this.state.toolbarOptions.totalColumnInFront;
},
handleClick(event) {
GradebookToolbarActions.toggleTotalColumnInFront(!this.isTotalColumnInFront());
},
render() {
var title = (this.isTotalColumnInFront()) ? TO_END : TO_FRONT;
return <HeaderDropdownOption key="moveToFront"
title={title}
handleClick={this.handleClick}
ref="moveToFront"/>
}
});
return MoveTotalColumnToggle;
});

View File

@ -1,60 +0,0 @@
define([
'react',
'jquery',
'reflux',
'underscore',
'i18n!gradebook',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption',
'compiled/AssignmentMuter',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/actions/assignmentGroupsActions'
], function (
React,
$,
Reflux,
_,
I18n,
HeaderDropdownOption,
AssignmentMuter,
GradebookConstants,
AssignmentGroupsActions
) {
var MUTE = I18n.t('Mute Assignment'),
UNMUTE = I18n.t('Unmute Assignment'),
MUTING_EVENT = 'assignment_muting_toggled';
var MuteAssignmentOption = React.createClass({
propTypes: {
assignment: React.PropTypes.object.isRequired
},
openDialog() {
var assignment = this.props.assignment,
contextUrl = GradebookConstants.context_url,
options = {openDialogInstantly: true},
id = assignment.id,
url = contextUrl + "/assignments/" + id + "/mute";
new AssignmentMuter(null, assignment, url, null, options);
$.subscribe(MUTING_EVENT, (assignment) => {
AssignmentGroupsActions.replaceAssignment(assignment);
$.unsubscribe(MUTING_EVENT);
});
},
render() {
var title = (this.props.assignment.muted) ? UNMUTE : MUTE;
return(
<HeaderDropdownOption
handleClick={this.openDialog}
key={'muteAssignment' + this.props.assignment.id}
title={title}/>
);
}
});
return MuteAssignmentOption;
});

View File

@ -1,56 +0,0 @@
define([
'react',
'reflux',
'i18n!gradebook',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/actions/gradebookToolbarActions',
'compiled/gradebook2/GradeDisplayWarningDialog',
], function (React, Reflux, I18n, HeaderDropdownOption, GradebookToolbarStore, GradebookToolbarActions, GradeDisplayWarningDialog) {
var PointsOrPercentageToggle = React.createClass({
propTypes: {},
mixins:[Reflux.connect(GradebookToolbarStore, 'toolbarOptions')],
totalShowingAsPoints() {
return this.state.toolbarOptions.showTotalGradeAsPoints;
},
toggle() {
var showAsPoints = !this.totalShowingAsPoints();
GradebookToolbarActions.showTotalGradeAsPoints(showAsPoints);
},
toggleAndHideWarning() {
this.toggle();
GradebookToolbarActions.hideTotalDisplayWarning(true);
},
changeTotalDisplay(event) {
event.preventDefault();
var showWarning = !this.state.toolbarOptions.warnedAboutTotalsDisplay,
dialogOptions;
if (showWarning) {
dialogOptions = { showing_points: this.totalShowingAsPoints(),
unchecked_save: this.toggle, checked_save: this.toggleAndHideWarning };
new GradeDisplayWarningDialog(dialogOptions);
} else {
this.toggle();
}
},
render() {
var title = this.totalShowingAsPoints() ?
I18n.t('Switch to Percentage') : I18n.t('Switch to Points');
return(
<HeaderDropdownOption key='pointsOrPercentage'
title={title} dataAction='pointsOrPercentage'
handleClick={this.changeTotalDisplay}
ref='dropdownOption'/>
);
}
});
return PointsOrPercentageToggle;
});

View File

@ -1,55 +0,0 @@
define([
'react',
'reflux',
'underscore',
'i18n!gradebook',
'compiled/gradebook2/SetDefaultGradeDialog',
'jsx/gradebook/grid/components/dropdown_components/headerDropdownOption'
], function (React, Reflux, _, I18n, SetDefaultGradeDialog, HeaderDropdownOption) {
var SetDefaultGradeOption = React.createClass({
// TODO: make selectedSection isRequired in the ticket to filter by section
// (or, even better, pre-filter the students we pass into SetDefaultGradeDialog
// so we dont need to pass in a selectedSection)
propTypes: {
assignment: React.PropTypes.object.isRequired,
enrollments: React.PropTypes.array.isRequired,
contextId: React.PropTypes.string.isRequired,
selectedSection: React.PropTypes.object
},
students() {
return _.pluck(this.props.enrollments, 'user');
},
studentsThatCanSeeAssignment(students, assignment) {
var studentIds = assignment.assignment_visibility;
return _.filter(students, student => _.contains(studentIds, student.id));
},
openDialog() {
var assignment = this.props.assignment,
students = this.studentsThatCanSeeAssignment(this.students(), assignment);
// TODO: pass in a selectedSection once the ticket for section filtering is
// implemented
return new SetDefaultGradeDialog({
assignment: assignment,
students: students,
selected_section: this.props.selectedSection,
context_id: this.props.contextId
});
},
render() {
return(
<HeaderDropdownOption
key={'setDefaultGrade-' + this.props.assignment.id}
handleClick={this.openDialog}
title={I18n.t('Set Default Grade')}/>
);
}
});
return SetDefaultGradeOption;
});

View File

@ -1,31 +0,0 @@
define([
'react',
'i18n!gradebook',
'jsx/gradebook/grid/components/dropdown_components/currentOrFinalGradeToggle',
'jsx/gradebook/grid/components/dropdown_components/pointsOrPercentageToggle',
'jsx/gradebook/grid/components/dropdown_components/moveTotalColumnToggle',
'jsx/gradebook/grid/constants'
], function (React, I18n, CurrentOrFinalGradeToggle, PointsOrPercentageToggle, MoveTotalColumnToggle, GradebookConstants) {
var TotalHeaderDropdownOptions = React.createClass({
propTypes: {
idAttribute: React.PropTypes.string.isRequired
},
render() {
var moveToFrontToggle = { title: I18n.t('Move to Front'), action: 'moveToFront' },
showPointsToggle = GradebookConstants.group_weighting_scheme !== 'percent';
return (
<ul id={this.props.idAttribute} className="gradebook-header-menu">
{showPointsToggle &&
<PointsOrPercentageToggle key='pointsOrPercentageToggle' ref='switchToPoints'/>}
<MoveTotalColumnToggle key='moveTotalColumn' ref='moveToFront'/>
<CurrentOrFinalGradeToggle key='currentOrFinalToggle' ref='currentOrFinalToggle'/>
</ul>
);
}
});
return TotalHeaderDropdownOptions;
});

View File

@ -1,307 +0,0 @@
define([
'react',
'fixed-data-table',
'jquery',
'underscore',
'reflux',
'i18n!gradebook2',
'jsx/gradebook/grid/wrappers/columnFactory',
'jsx/gradebook/grid/wrappers/headerWrapper',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/actions/assignmentGroupsActions',
'jsx/gradebook/grid/stores/settingsStore',
'jsx/gradebook/grid/actions/settingsActions',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/stores/gradingPeriodsStore',
'jsx/gradebook/grid/actions/studentEnrollmentsActions',
'jsx/gradebook/grid/actions/submissionsActions',
'jsx/gradebook/grid/actions/customColumnsActions',
'jsx/gradebook/grid/stores/keyboardNavigationStore',
'jsx/gradebook/grid/actions/keyboardNavigationActions',
'jsx/gradebook/grid/stores/tableStore',
'jsx/gradebook/grid/actions/sectionsActions',
'jsx/gradebook/grid/helpers/columnArranger',
'spin.js',
'jsx/gradebook/grid/helpers/submissionsHelper'
], function (
React,
FixedDataTable,
$,
_,
Reflux,
I18n,
ColumnFactory,
HeaderWrapper,
GradebookConstants,
AssignmentGroupsActions,
SettingsStore,
SettingsActions,
GradebookToolbarStore,
GradingPeriodsStore,
StudentEnrollmentsActions,
SubmissionsActions,
CustomColumnsActions,
KeyboardNavigationStore,
KeyboardNavigationActions,
TableStore,
SectionsActions,
ColumnArranger,
Spinner,
SubmissionsHelper
){
var Table = FixedDataTable.Table,
Column = FixedDataTable.Column,
isColumnResizing = false,
spinner;
var Gradebook = React.createClass({
mixins: [
Reflux.connect(KeyboardNavigationStore, 'keyboardNav'),
Reflux.connect(SettingsStore, 'settings'),
Reflux.connect(GradebookToolbarStore, 'toolbarOptions'),
Reflux.connect(TableStore, 'tableData')
],
componentWillMount() {
AssignmentGroupsActions.load();
StudentEnrollmentsActions.load()
.then((studentEnrollments) => {
var studentIds = _.pluck(studentEnrollments, 'user_id');
SubmissionsActions.load(studentIds);
});
SectionsActions.load();
CustomColumnsActions.loadTeacherNotes();
CustomColumnsActions.load();
},
componentDidMount() {
SettingsActions.resize();
$(window).resize(SettingsActions.resize);
},
componentDidUpdate() {
KeyboardNavigationActions.constructKeyboardNavManager();
},
handleKeyDown(event) {
var reactGradebook = document.getElementById('react-gradebook-canvas');
var knownCodes = GradebookConstants.RECOGNIZED_KEYBOARD_CODES;
if (_.contains(knownCodes, event.keyCode)) {
event.nativeEvent.preventDefault();
event.persist();
KeyboardNavigationActions.handleKeyboardEvent(event);
$(reactGradebook).focus();
}
},
assignments() {
var arrangeBy, comparator, assignments;
arrangeBy = this.state.toolbarOptions.arrangeColumnsBy;
comparator = ColumnArranger.getComparator(arrangeBy);
assignments = _.chain(this.state.tableData.assignments.data)
.filter(assignment =>
GradingPeriodsStore.assignmentIsInPeriod(assignment, GradingPeriodsStore.selected()))
.value();
return assignments.sort(comparator);
},
getColumnWidth(column) {
var customWidths = this.state.settings.columnWidths,
defaultWidth = GradebookConstants.DEFAULT_LAYOUTS.headers.width,
width = (customWidths && customWidths[column]) || defaultWidth;
return parseInt(width);
},
handleColumnResize(newColumnWidth, dataKey) {
SettingsActions.saveColumnSize(newColumnWidth, dataKey);
isColumnResizing = false;
},
rowGetter(index) {
return this.state.tableData.rows[index];
},
isColumnFixed(columnType) {
return columnType === GradebookConstants.STUDENT_COLUMN_ID
|| columnType === GradebookConstants.SECONDARY_COLUMN_ID
|| columnType === GradebookConstants.TOTAL_COLUMN_ID
&& this.state.toolbarOptions.totalColumnInFront;
},
renderColumn(columnName, columnType, columnId, cellDataGetter, assignment, customColumnData) {
var columnIdentifier = columnId || columnType,
columnWidth = this.getColumnWidth(columnIdentifier),
enrollments = this.state.tableData.students,
submissions = this.state.tableData.submissions,
columnData = {
columnType: columnType,
activeCell: this.state.keyboardNav.currentCellIndex,
setActiveCell: KeyboardNavigationActions.setActiveCell,
assignment: assignment,
enrollments: enrollments,
submissions: submissions,
customColumnData: customColumnData
};
return (
<Column
label={columnName}
fixed={this.isColumnFixed(columnType)}
cellDataGetter={cellDataGetter}
width={columnWidth}
dataKey={columnIdentifier}
columnData={columnData}
headerRenderer={HeaderWrapper.getHeader}
cellRenderer={ColumnFactory.getRenderer}
isResizable={true}
minWidth={90}
key={columnIdentifier}/>
);
},
renderAssignmentGroupColumns(assignmentGroups) {
var cellDataGetter;
cellDataGetter = function(columnId, rowData) {
var assignmentGroups, assignmentGroup, submissions;
assignmentGroups = this.state.tableData.assignmentGroups;
assignmentGroup = _.find(assignmentGroups, group => group.columnId === columnId);
submissions = rowData.submissions;
return {
assignmentGroup: assignmentGroup,
submissions: submissions,
columnId: columnId
};
}.bind(this);
return _.map(assignmentGroups, (assignmentGroup, index) => {
var columnId = assignmentGroup.columnId;
return this.renderColumn(assignmentGroup.name,
GradebookConstants.ASSIGNMENT_GROUP_COLUMN_ID,
columnId, cellDataGetter);
});
},
renderAssignmentColumns(assignments) {
var cellDataGetter;
cellDataGetter = function(columnId, rowData) {
var submissions, submission;
submissions = rowData.submissions[columnId];
if (submissions && submissions.length > 0) {
submission = submissions[0];
}
return submission;
}.bind(this);
return _.map(assignments, (assignment) => {
var columnId = assignment.id;
return this.renderColumn(assignment.name, assignment.grading_type, columnId, cellDataGetter, assignment);
});
},
hasStoreErrorOccured() {
return this.state.tableData.error;
},
renderSpinner() {
spinner = new Spinner();
$(spinner.spin().el).css({
opacity: 0.5,
top: '55px',
left: '50%'
}).addClass('use-css-transitions-for-show-hide').appendTo('#main');
},
removeSpinner() {
if (spinner) {
$(spinner.el).remove();
spinner = null;
}
},
renderNotesColumn() {
if (!this.state.toolbarOptions.hideNotesColumn) {
return this.renderColumn(I18n.t('Notes'), GradebookConstants.NOTES_COLUMN_ID, 'notesColumn');
}
},
renderCustomColumns(customColumns) {
let customColumnData, mapFunction;
customColumnData = customColumns.customColumns.data;
mapFunction = function(customColumn) {
var columnId, columnData;
columnId = 'customColumn_' + customColumn.id;
columnData = this.state.tableData.customColumns.customColumns.columnData;
return this.renderColumn(customColumn.title, GradebookConstants.CUSTOM_COLUMN_ID, columnId, (columnId, rowData) => columnData[customColumn.id][rowData.student.user_id], null, customColumn);
};
return _.map(customColumnData, mapFunction.bind(this));
},
renderAllColumns() {
var arrangeBy, columns, comparator, showTotalInFront, total;
arrangeBy = this.state.toolbarOptions.arrangeColumnsBy;
comparator = ColumnArranger.getComparator(arrangeBy);
total = this.renderColumn(I18n.t('Total'), 'total');
showTotalInFront = this.state.toolbarOptions.totalColumnInFront,
columns = [
this.renderColumn(I18n.t('Student Name'), GradebookConstants.STUDENT_COLUMN_ID),
this.renderNotesColumn(),
this.renderCustomColumns(this.state.tableData.customColumns),
this.renderAssignmentColumns(_.flatten(_.values(this.state.tableData.assignments)).sort(comparator), this.state.tableData.submissions),
this.renderAssignmentGroupColumns(this.state.tableData.assignmentGroups),
];
(showTotalInFront) ? columns.splice(1, 0, total) : columns.push(total);
return columns;
},
render() {
if (this.hasStoreErrorOccured()) {
$.flashError(I18n.t('There was a problem loading the gradebook.'));
}
else if (!this.state.tableData.loading) {
this.removeSpinner();
return (
<div id="react-gradebook-canvas"
onKeyDown={this.handleKeyDown}
tabIndex="0">
<Table
rowGetter={this.rowGetter}
rowsCount={this.state.tableData.students.length}
scrollToColumn={this.state.keyboardNav.currentColumnIndex}
scrollToRow={this.state.keyboardNav.currentRowIndex}
onColumnResizeEndCallback={this.handleColumnResize}
isColumnResizing={isColumnResizing}
rowHeight={GradebookConstants.DEFAULT_LAYOUTS.rows.height}
height={this.state.settings.height}
width={this.state.settings.width}
headerHeight={GradebookConstants.DEFAULT_LAYOUTS.headers.height}>
{this.renderAllColumns()}
</Table>
</div>
);
} else {
if (!spinner) {
this.renderSpinner();
}
return <div/>;
}
}
});
return Gradebook;
});

View File

@ -1,114 +0,0 @@
define([
'react',
'underscore',
'./assignmentGradeCell'
], function (React, _, AssignmentGradeCell) {
var GRADEBOOK_CELL_CLASS = 'gradebook-cell',
ACTIVE_CLASS = ' active',
LATE_CLASS = ' late',
RESUBMIITED_CLASS = ' resubmitted',
CONCLUDED_OR_INACTIVE_CLASS = ' grayed-out',
ASSIGNMENT_TYPES = [
'percent',
'pass_fail',
'letter_grade',
'points',
'gpa_scale',
];
var GridCell = React.createClass({
propTypes: {
activeCell: React.PropTypes.number.isRequired,
cellData: React.PropTypes.any,
cellIndex: React.PropTypes.number.isRequired,
columnData: React.PropTypes.object,
rowData: React.PropTypes.object.isRequired,
renderer: React.PropTypes.func.isRequired
},
getInitialState() {
return {
cellIndex: this.props.cellIndex
};
},
handleClick() {
this.props.setActiveCell(this.state.cellIndex);
},
isAssignment(columnData) {
return _.contains(ASSIGNMENT_TYPES, columnData.columnType);
},
isConcluded() {
return this.props.rowData.isConcluded;
},
isInactive() {
return this.props.rowData.isInactive;
},
isConcludedOrInactive() {
return this.isConcluded() || this.isInactive();
},
getClassName(isActiveCell, cellData, isAssignment) {
var className = GRADEBOOK_CELL_CLASS;
if (isActiveCell) {
className += ACTIVE_CLASS;
}
if (isAssignment && cellData) {
if (cellData.late) {
className += LATE_CLASS;
} else if (cellData.grade_matches_current_submission !== null && !(cellData.grade_matches_current_submission)) {
className += RESUBMIITED_CLASS;
}
}
if (this.isConcludedOrInactive()) {
className += CONCLUDED_OR_INACTIVE_CLASS;;
}
return className;
},
renderAssignmentCell(Renderer, isActiveCell) {
return (<AssignmentGradeCell
activeCell={isActiveCell}
cellData={this.props.cellData}
columnData={this.props.columnData}
renderer={Renderer}
rowData={this.props.rowData} />);
},
renderGenericCell(Renderer, isActiveCell) {
return (<Renderer isActiveCell={isActiveCell}
cellData={this.props.cellData}
columnData={this.props.columnData}
rowData={this.props.rowData} />);
},
render() {
var Renderer = this.props.renderer,
className = GRADEBOOK_CELL_CLASS,
isAssignmentCell = this.isAssignment(this.props.columnData),
isActiveCell = this.props.activeCell === this.state.cellIndex,
renderCell = (isAssignmentCell) ? this.renderAssignmentCell : this.renderGenericCell,
submission = this.props.cellData;
return (
<div className={this.getClassName(isActiveCell, submission, isAssignmentCell)}
onKeyDown={this.handleKeyPress}
onClick={this.handleClick}
key='null'>
{renderCell(Renderer, isActiveCell)}
</div>
);
}
});
return GridCell;
});

View File

@ -1,58 +0,0 @@
define([
'underscore'
], function (_) {
var GRADEBOOK_CONSTANTS = {
STUDENT_COLUMN_ID: 'student',
NOTES_COLUMN_ID: 'notes',
PERCENT_COLUMN_ID: 'percent',
PASS_FAIL_COLUMN_ID: 'pass_fail',
LETTER_GRADE_COLUMN_ID: 'letter_grade',
POINTS_COLUMN_ID: 'points',
GPA_SCALE_COLUMN_ID: 'gpa_scale',
TOTAL_COLUMN_ID: 'total',
CUSTOM_COLUMN_ID: 'custom',
ASSIGNMENT_GROUP_COLUMN_ID: 'assignment_group',
MOUNT_ELEMENT: document.getElementById('gradebook-grid-wrapper'),
DEFAULT_LAYOUTS: {
headers: { width: 150, height: 40, flexGrow: 0, paddingAdjustment: 20 },
rows: { height: 36 }
},
SUBMISSION_RESPONSE_FIELDS: [
'id',
'user_id',
'url',
'score',
'grade',
'submission_type',
'submitted_at',
'assignment_id',
'grade_matches_current_submission',
'attachments',
'late',
'workflow_state'
],
DEFAULT_TOOLBAR_PREFERENCES: {
hideStudentNames: false,
hideNotesColumn: true,
treatUngradedAsZero: false,
totalColumnInFront: false,
arrangeColumnsBy: 'assignment_group',
warnedAboutTotalsDisplay: false,
showTotalGradeAsPoints: false
},
ASSIGNMENT_DATES: ['created_at', 'updated_at', 'due_at', 'lock_at', 'unlock_at'],
OVERRIDE_DATES: ['all_day_date', 'due_at', 'lock_at', 'unlock_at'],
PAGINATION_COUNT: 50,
MAX_NOTE_LENGTH: 255,
// keyboard codes: tab, enter, left arrow, up arrow, right arrow, down arrow
RECOGNIZED_KEYBOARD_CODES: [9,13,37,38,39,40],
refresh: function() {
// For testing
_.extend(this, ENV.GRADEBOOK_OPTIONS);
}
};
var CONSTANTS = _.extend({}, GRADEBOOK_CONSTANTS, ENV.GRADEBOOK_OPTIONS);
return CONSTANTS;
});

View File

@ -1,60 +0,0 @@
define([
'underscore'
], function (_) {
var ColumnArranger = {
compareByDueDate: function(a, b) {
var aDate = this.getDueDateFromAssignment(a);
var bDate = this.getDueDateFromAssignment(b);
var aDateIsNull = _.isNull(aDate);
var bDateIsNull = _.isNull(bDate);
if (aDateIsNull && !bDateIsNull) return 1;
if (!aDateIsNull && bDateIsNull) return -1;
if (aDateIsNull && bDateIsNull) {
if (this.hasMultipleDueDates(a) && !this.hasMultipleDueDates(b)) return -1;
if (!this.hasMultipleDueDates(a) && this.hasMultipleDueDates(b)) return 1;
}
aDate = +aDate;
bDate = +bDate;
if (aDate === bDate) {
var aName = a.name.toLowerCase();
var bName = b.name.toLowerCase();
if (aName === bName) return 0;
return aName > bName ? 1 : -1;
}
return aDate - bDate;
},
hasMultipleDueDates: function(assignment) {
return !!(
assignment.has_overrides &&
assignment.overrides &&
assignment.overrides.length > 1
);
},
getDueDateFromAssignment: function(assignment) {
if (assignment.due_at) return new Date(assignment.due_at);
var overrides = assignment.overrides;
if (!overrides || overrides.length > 1) return null;
var overrideWithDueAt = _.find(overrides, override => override.due_at);
return overrideWithDueAt ? new Date(overrideWithDueAt.due_at) : null;
},
compareByAssignmentGroup: function(a, b) {
var diffOfAssignmentGroupPosition = a.assignment_group_position - b.assignment_group_position;
if (diffOfAssignmentGroupPosition === 0) {
var diffOfAssignmentPosition = a.position - b.position;
if (diffOfAssignmentPosition === 0) return 0;
return diffOfAssignmentPosition;
}
return diffOfAssignmentGroupPosition;
},
getComparator: function(arrangeBy) {
if (arrangeBy === 'due_date') return this.compareByDueDate.bind(this);
if (arrangeBy === 'assignment_group') return this.compareByAssignmentGroup.bind(this);
}
};
return ColumnArranger;
});

View File

@ -1,11 +0,0 @@
define([], function() {
var currentOrFinal = function(toolbarOptions) {
if (toolbarOptions.treatUngradedAsZero) {
return 'final';
}
return 'current';
};
return currentOrFinal;
})

View File

@ -1,86 +0,0 @@
define([
'jquery',
'underscore',
'jquery.ajaxJSON'
], function($, _) {
let exists, Depaginator, depaginate;
depaginate = function(url, data) {
let deferred, depaginator;
deferred = $.Deferred();
depaginator = new Depaginator(url, deferred, data);
depaginator.retrieve();
return deferred.promise();
};
exists = function(object) {
return object !== null && object !== undefined;
};
let notExists = function(object) {
return !exists(object);
}
Depaginator = function(url, deferred, data) {
this.url = url;
this.deferred = deferred;
this.data = data;
};
Depaginator.prototype.retrieve = function() {
return $.getJSON(this.url, this.data)
.done(this.handleInitialRequest.bind(this));
};
Depaginator.prototype.handleInitialRequest = function(resultData, status, xhr) {
let paginationLinks, lastLink;
paginationLinks = xhr.getResponseHeader('Link');
lastLink = paginationLinks.match(/<[^>]+>; *rel="last"/);
lastLink = lastLink[0].match(/<[^>]+>;/);
let currentLink = paginationLinks.match(/<[^>]+>; *rel="current"/);
currentLink = currentLink[0].match(/<[^>]+>;/);
if (notExists(lastLink) || lastLink[0] === currentLink[0]) {
this.deferred.resolve(resultData);
} else {
this.depaginate(resultData, lastLink);
}
};
Depaginator.prototype.depaginate = function(firstPageData, lastLink) {
let lastPage, requests;
lastPage = lastLink[0].match(/page=(\d+)/)[1];
lastPage = parseInt(lastPage, 10);
requests = this.getAllRequests(lastPage);
this.makeRequests(requests).then(function() {
let allOtherPagesData = _.chain(arguments)
.map(response => response[0])
.flatten()
.value();
let allData = firstPageData.concat(allOtherPagesData);
this.deferred.resolve(allData);
}.bind(this));
};
Depaginator.prototype.getAllRequests = function(lastPage) {
let requests = [], pageNumber, request;
for (pageNumber = 2; pageNumber <= lastPage; pageNumber++) {
request = this.fetchResources(pageNumber);
requests.push(request);
}
return requests;
};
Depaginator.prototype.fetchResources = function(pageNumber) {
return $.ajaxJSON(this.url, 'GET', {page: pageNumber});
};
Depaginator.prototype.makeRequests = function(requests) {
return $.when.apply($, requests);
};
return depaginate;
});

View File

@ -1,33 +0,0 @@
define([
'compiled/collections/DateGroupCollection',
'underscore'
], function(DateGroupCollection, _) {
var DueDateCalculator, exists;
exists = function(value) {
return value !== null && value !== undefined;
};
DueDateCalculator = function(assignment) {
this.assignment = assignment;
};
DueDateCalculator.prototype.dueDate = function() {
var dueAt, allDates, dueDate;
dueAt = this.assignment.due_at;
allDates = this.assignment.all_dates;
if (!exists(dueAt)) {
dueDate = _.find(allDates, section =>
exists(section.due_at));
if (exists(dueDate)) return dueDate.due_at.toISOString();
} else {
return dueAt;
}
return null;
};
return DueDateCalculator;
});

View File

@ -1,22 +0,0 @@
define([
'underscore',
], function (_) {
let EnrollmentsHelper = {
studentsThatCanSeeAssignment: function(enrollments, assignment) {
let visibleStudentIds = assignment.assignment_visibility;
let students = this.students(enrollments);
return _.pick(students, visibleStudentIds);
},
students: function(enrollments) {
let studentEnrollments = this.studentEnrollments(enrollments);
let students = _.pluck(studentEnrollments, 'user');
return _.indexBy(students, 'id');
},
studentEnrollments: function(enrollments) {
return _.where(enrollments, { type: 'StudentEnrollment' });
}
};
return EnrollmentsHelper;
});

View File

@ -1,16 +0,0 @@
define([
], function () {
function EnrollmentsUrlHelper({ showConcluded = false, showInactive = false }) {
if(showConcluded && showInactive) {
return 'enrollments_with_concluded_and_inactive_url';
} else if(showConcluded) {
return 'enrollments_with_concluded_url';
} else if(showInactive) {
return 'enrollments_with_inactive_url';
} else {
return 'enrollments_url'
}
};
return EnrollmentsUrlHelper;
});

View File

@ -1,185 +0,0 @@
define([
], function () {
function KeyboardNavManager(dimensions) {
this.boundaries = {
left: 0,
right: dimensions.width - 1,
top: 0,
bottom: dimensions.height - 1
};
}
KeyboardNavManager.prototype.makeMove = function(event, currentCellIndex) {
var name = this.getKeyboardEventName(event);
var newIndex;
if (currentCellIndex === -1) {
newIndex = 0;
} else if (name === 'tab' || name === 'rightArrow') {
newIndex = this.attemptMoveRight(currentCellIndex);
} else if (name === 'shiftTab' || name === 'leftArrow') {
newIndex = this.attemptMoveLeft(currentCellIndex);
} else if (name === 'enter' || name === 'downArrow') {
newIndex = this.attemptMoveDown(currentCellIndex);
} else if (name === 'shiftEnter' || name === 'upArrow') {
newIndex = this.attemptMoveUp(currentCellIndex);
}
return newIndex;
};
KeyboardNavManager.prototype.getKeyboardEventName = function(event) {
var shiftKey = event.shiftKey ? 'shift' : 'noShift';
var code = event.keyCode;
var eventNames = {
9: { noShift: 'tab', shift: 'shiftTab' },
13: { noShift: 'enter', shift: 'shiftEnter' },
37: { noShift: 'leftArrow' },
38: { noShift: 'upArrow' },
39: { noShift: 'rightArrow' },
40: { noShift: 'downArrow' }
};
return eventNames[code] && eventNames[code][shiftKey];
};
KeyboardNavManager.prototype.restrictedDirections = function(coords) {
return {
left: coords.x <= this.boundaries.left,
right: coords.x >= this.boundaries.right,
up: coords.y <= this.boundaries.top,
down: coords.y >= this.boundaries.bottom
};
};
KeyboardNavManager.prototype.indexToCoords = function(currentCellIndex) {
var rowLength = this.boundaries.right + 1;
var x = currentCellIndex % rowLength;
var y = Math.floor(currentCellIndex / rowLength);
this.invalidCellIndexCheck(currentCellIndex);
return { x, y };
};
KeyboardNavManager.prototype.coordsToIndex = function(coords) {
var rowLength = this.boundaries.right + 1;
var yIndexVal = rowLength * coords.y;
return coords.x + yIndexVal;
};
KeyboardNavManager.prototype.invalidCellIndexCheck = function(cellIndex) {
var maxCoords = { x: this.boundaries.right, y: this.boundaries.bottom };
var maxIndex = this.coordsToIndex(maxCoords);
var message;
if (cellIndex < 0 || cellIndex > maxIndex) {
message = 'Invalid cell index of ' + cellIndex + ' provided. The cell ' +
'index must be between 0 and ' + maxIndex + ', inclusive.';
throw new Error(message);
}
};
KeyboardNavManager.prototype.invalidMovementCheck = function(coords, direction) {
var message;
if (this.restrictedDirections(coords)[direction]) {
message = 'Boundary restriction: cannot move ' + direction;
throw new Error(message);
}
};
KeyboardNavManager.prototype.moveLeft = function(coords) {
this.invalidMovementCheck(coords, 'left');
return { x: coords.x - 1, y: coords.y };
};
KeyboardNavManager.prototype.moveRight = function(coords) {
this.invalidMovementCheck(coords, 'right');
return { x: coords.x + 1, y: coords.y };
};
KeyboardNavManager.prototype.moveUp = function(coords) {
this.invalidMovementCheck(coords, 'up');
return { x: coords.x, y: coords.y - 1 };
};
KeyboardNavManager.prototype.moveDown = function(coords) {
this.invalidMovementCheck(coords, 'down');
return { x: coords.x, y: coords.y + 1 };
};
KeyboardNavManager.prototype.snapLeft = function(coords) {
return { x: this.boundaries.left, y: coords.y };
};
KeyboardNavManager.prototype.snapRight = function(coords) {
return { x: this.boundaries.right, y: coords.y };
};
KeyboardNavManager.prototype.snapTop = function(coords) {
return { x: coords.x, y: this.boundaries.top };
};
KeyboardNavManager.prototype.snapBottom = function(coords) {
return { x: coords.x, y: this.boundaries.bottom };
};
KeyboardNavManager.prototype.attemptMoveLeft = function(currentCellIndex) {
var coords = this.indexToCoords(currentCellIndex);
var restricted = this.restrictedDirections(coords);
if (!restricted.left) {
coords = this.moveLeft(coords);
} else if (restricted.up) {
coords = this.snapBottom(this.snapRight(coords));
} else {
coords = this.moveUp(this.snapRight(coords));
}
return this.coordsToIndex(coords);
};
KeyboardNavManager.prototype.attemptMoveRight = function(currentCellIndex) {
var coords = this.indexToCoords(currentCellIndex);
var restricted = this.restrictedDirections(coords);
if (!restricted.right) {
coords = this.moveRight(coords);
} else if (restricted.down) {
coords = this.snapTop(this.snapLeft(coords));
} else {
coords = this.moveDown(this.snapLeft(coords));
}
return this.coordsToIndex(coords);
};
KeyboardNavManager.prototype.attemptMoveUp = function(currentCellIndex) {
var coords = this.indexToCoords(currentCellIndex);
var restricted = this.restrictedDirections(coords);
if (!restricted.up) {
coords = this.moveUp(coords);
} else if (restricted.left) {
coords = this.snapRight(this.snapBottom(coords));
} else {
coords = this.moveLeft(this.snapBottom(coords));
}
return this.coordsToIndex(coords);
};
KeyboardNavManager.prototype.attemptMoveDown = function(currentCellIndex) {
var coords = this.indexToCoords(currentCellIndex);
var restricted = this.restrictedDirections(coords);
if (!restricted.down) {
coords = this.moveDown(coords);
} else if (restricted.right) {
coords = this.snapLeft(this.snapTop(coords));
} else {
coords = this.moveRight(this.snapTop(coords));
}
return this.coordsToIndex(coords);
};
return KeyboardNavManager;
});

View File

@ -1,22 +0,0 @@
define([
'underscore',
], function (_) {
let SubmissionsHelper = {
submissionsForAssignment: function(submissionGroups, assignment) {
let submissions = this.extractSubmissions(submissionGroups);
let subsForAssignment = submissions[assignment.id]
return subsForAssignment ? _.indexBy(subsForAssignment, 'user_id') : {};
},
extractSubmissions: function(submissionGroups) {
return _.chain(submissionGroups)
.values()
.flatten()
.pluck('submissions')
.flatten()
.groupBy('assignment_id')
.value();
}
};
return SubmissionsHelper;
});

View File

@ -1,38 +0,0 @@
define([
'underscore',
'../actions/submissionsActions'
], function (_, SubmissionsActions) {
var GradeCellMixin = {
getInitialState() {
return {
submission: this.props.submission,
};
},
componentWillReceiveProps(nextProps) {
this.setState({submission: nextProps.submission});
},
getDisplayGrade() {
var submission = this.props.cellData;
return (submission && submission.grade) ? submission.grade : '-';
},
isSubmissionGradedAsNull() {
return this.props.cellData && _.isNull(this.props.cellData.grade);
},
sendSubmission() {
var submission = {
userId: this.props.rowData.student.user_id,
assignmentId: this.props.columnData.assignment.id,
postedGrade: this.state.gradeToPost
};
SubmissionsActions.updateGrade(submission);
this.setState({gradeToPost: null});
},
};
return GradeCellMixin;
});

View File

@ -1,22 +0,0 @@
define([], function () {
var StandardCellFocusMixin = {
componentDidUpdate(previousProps, previousState) {
var isActiveCell = this.props.isActiveCell,
gradeHasChanged = (this.state.gradeToPost != previousState.grade);
if (previousProps.isActiveCell && !isActiveCell) {
var gradeToPost = this.state.gradeToPost;
if (gradeToPost && this.getDisplayGrade() !== gradeToPost) {
this.sendSubmission();
}
}
if(isActiveCell && !gradeHasChanged && !this.isConcluded()) {
var gradeInput = this.refs.gradeInput;
gradeInput.select();
}
},
};
return StandardCellFocusMixin;
});

View File

@ -1,20 +0,0 @@
define(['react'], function (React) {
var StandardGradeInputMixin = {
handleOnChange(event) {
this.setState({gradeToPost: event.target.value});
},
renderEditGrade() {
return (
<input
onChange={this.handleOnChange}
className="grade"
ref="gradeInput"
type="text"
value={this.state.gradeToPost || this.getDisplayGrade()}/>
);
},
};
return StandardGradeInputMixin;
});

View File

@ -1,13 +0,0 @@
define([], function () {
var StandardRenderMixin = {
isConcluded() {
return this.props.rowData.isConcluded;
},
render() {
return (this.props.isActiveCell && !this.isConcluded()) ? this.renderEditGrade() : this.renderViewGrade();
}
};
return StandardRenderMixin;
});

View File

@ -1,109 +0,0 @@
define([
'reflux',
'underscore',
'../actions/assignmentGroupsActions',
'jsx/shared/helpers/dateHelper',
'../constants'
], function (Reflux, _, AssignmentGroupsActions, DateHelper, GradebookConstants) {
var AssignmentGroupsStore = Reflux.createStore({
listenables: [AssignmentGroupsActions],
init() {
this.state = {
data: null,
error: null
};
},
getInitialState() {
if (this.state === undefined) {
this.init();
}
return this.state;
},
onLoadFailed(error) {
this.state.error = error;
this.trigger(this.state);
},
onLoadCompleted(json) {
var assignmentGroups;
this.setNoPointsWarning(json);
assignmentGroups = this.formatAssignmentGroups(json);
this.state.data = assignmentGroups;
this.trigger(this.state);
},
onReplaceAssignmentGroups(updatedAssignmentGroups) {
this.state.data = updatedAssignmentGroups;
this.trigger(this.state);
},
setNoPointsWarning(assignmentGroups) {
_.each(assignmentGroups, (group) => {
var pointsPossible = _.inject(group.assignments, (sum, assignment) => {
return sum + (assignment.points_possible || 0);
}, 0);
group.shouldShowNoPointsWarning = (pointsPossible === 0);
});
},
onReplaceAssignment(updatedAssignment) {
var assignments = _.flatten(_.pluck(this.state.data, 'assignments')),
assignment = _.find(assignments, assignment => updatedAssignment.id === assignment.id);
assignment.muted = updatedAssignment.muted;
this.trigger(this.state);
},
formatAssignmentGroups(groups) {
return _.map(groups, (group) => {
group.assignments = _.chain(group.assignments)
.reject(assignment => _.contains(assignment.submission_types, 'not_graded'))
.map(assignment => this.formatAssignment(assignment, group))
.value();
return group;
});
},
formatAssignment(assignment, assignmentGroup) {
assignment = DateHelper.parseDates(assignment, GradebookConstants.ASSIGNMENT_DATES);
assignment.assignment_group_position = assignmentGroup.position;
assignment.speedgrader_url = GradebookConstants.context_url + '/gradebook/speed_grader?assignment_id=' + assignment.id;
assignment.submissions_downloads = 0;
assignment.shouldShowNoPointsWarning = assignmentGroup.shouldShowNoPointsWarning;
if (assignment.has_overrides) {
assignment.overrides = _.map(
assignment.overrides,
override => DateHelper.parseDates(override, GradebookConstants.OVERRIDE_DATES)
);
}
return assignment;
},
/*
["id"] -> [Assignment]
Given a list of assignment ids, retrieves the specified assignments
*/
assignments(assignmentIds) {
var assignmentGroups, assignments, allAssignments;
assignmentGroups = this.state.data;
allAssignments = _.map(assignmentGroups, group => group.assignments);
allAssignments = _.flatten(allAssignments);
assignments = _.map(assignmentIds, assignmentId =>
_.find(allAssignments, assignment =>
assignment.id === assignmentId));
assignments = _.reject(assignments, assignment => assignment === undefined);
return assignments;
}
});
return AssignmentGroupsStore;
});

View File

@ -1,78 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/actions/customColumnsActions'
], function (Reflux, _, CustomColumnsActions) {
var CustomColumnsStore = Reflux.createStore({
listenables: [CustomColumnsActions],
init() {
this.state = {
teacherNotes: null,
customColumns: {
data: [],
columnData: {}
}
};
},
getInitialState() {
if(this.state === undefined) {
this.init();
}
return this.state;
},
customColumns(data) {
return _.isUndefined(data) ? [] : _.reject(data, column => column.hidden || column.teacher_notes);
},
onLoadCompleted(data) {
this.state.customColumns.data = this.customColumns(data);
this.trigger(this.state);
},
onLoadTeacherNotesCompleted(teacherNotes) {
var notes = _.isUndefined(teacherNotes) ? [] : teacherNotes;
this.state.teacherNotes = notes;
this.trigger(this.state);
},
onLoadColumnDataCompleted(data, columnId) {
if (this.state.customColumns.columnData[columnId] === undefined) {
this.state.customColumns.columnData[columnId] = {};
}
_.each(data, function(columnDatum) {
this.state.customColumns.columnData[columnId][columnDatum.user_id] = columnDatum;
}.bind(this));
},
getColumnDatum(columnId, userId) {
var columnData, columnDatum;
columnData = this.state.customColumns.columnData[columnId];
if (columnData !== undefined) {
columnDatum = columnData[userId];
return columnDatum;
}
return undefined;
},
onUpdateTeacherNote(noteData) {
var teacherNotes = this.state.teacherNotes,
existingNote = _.find(teacherNotes, note => note.user_id === noteData.user_id);
if (existingNote) {
existingNote.content = noteData.content;
} else {
teacherNotes.push(noteData);
}
this.trigger(this.state);
}
});
return CustomColumnsStore;
});

View File

@ -1,77 +0,0 @@
define([
'reflux',
'../actions/gradebookToolbarActions',
'jquery',
'underscore',
'i18n!gradebook2',
'compiled/userSettings',
'../constants'
], function (Reflux, GradebookToolbarActions, $, _, I18n, userSettings, GradebookConstants) {
var GradebookToolbarStore = Reflux.createStore({
listenables: [GradebookToolbarActions],
init() {
var storedSortOrder = GradebookConstants.gradebook_column_order_settings ||
{ sortType: 'assignment_group' };
var savedOptions = {
hideStudentNames: userSettings.contextGet('hideStudentNames'),
hideNotesColumn: !GradebookConstants.teacher_notes || GradebookConstants.teacher_notes.hidden,
arrangeColumnsBy: storedSortOrder.sortType,
treatUngradedAsZero: userSettings.contextGet('treatUngradedAsZero'),
totalColumnInFront: userSettings.contextGet('total_column_in_front'),
warnedAboutTotalsDisplay: userSettings.contextGet('warned_about_totals_display'),
showTotalGradeAsPoints: GradebookConstants.show_total_grade_as_points
};
this.toolbarOptions = _.defaults(savedOptions, GradebookConstants.DEFAULT_TOOLBAR_PREFERENCES);
},
getInitialState() {
if (this.toolbarOptions === null || this.toolbarOptions === undefined) {
this.init();
}
return this.toolbarOptions;
},
onToggleStudentNames(hideStudentNames) {
this.toolbarOptions.hideStudentNames = hideStudentNames;
this.trigger(this.toolbarOptions);
},
onToggleNotesColumn(hideNotesColumn) {
this.toolbarOptions.hideNotesColumn = hideNotesColumn;
this.trigger(this.toolbarOptions);
},
onArrangeColumnsBy(criteria) {
this.toolbarOptions.arrangeColumnsBy = criteria;
this.trigger(this.toolbarOptions);
},
onToggleTreatUngradedAsZero(treatUngradedAsZero) {
this.toolbarOptions.treatUngradedAsZero = treatUngradedAsZero;
this.trigger(this.toolbarOptions);
},
onToggleTotalColumnInFront(totalColumnInFront) {
this.toolbarOptions.totalColumnInFront = totalColumnInFront;
this.trigger(this.toolbarOptions);
},
onShowTotalGradeAsPoints(showAsPoints) {
this.toolbarOptions.showTotalGradeAsPoints = showAsPoints;
this.trigger(this.toolbarOptions);
},
onHideTotalDisplayWarning(hideWarning) {
this.toolbarOptions.warnedAboutTotalsDisplay = hideWarning;
this.trigger(this.toolbarOptions);
}
});
return GradebookToolbarStore;
});

View File

@ -1,149 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/actions/gradingPeriodsActions',
'jsx/gradebook/grid/stores/gradingPeriodsStore',
'jsx/gradebook/grid/helpers/dueDateCalculator',
'jsx/gradebook/grid/constants',
'compiled/userSettings'
], function (Reflux, _, GradingPeriodsActions, GradingPeriodsStore,
DueDateCalculator, GradebookConstants, userSettings) {
var GradePeriodsStore = Reflux.createStore({
listenables: [GradingPeriodsActions],
selected() {
return _.find(this.gradingPeriods.data, function(period) {
return period.id === this.gradingPeriods.selected;
}.bind(this));
},
init() {
var allPeriodsOption, activeGradingPeriods;
allPeriodsOption = {
end_date: new Date().setYear(3000),
id: '0',
is_last: false,
start_date: new Date().setYear(0),
title: 'All Grading Periods'
};
activeGradingPeriods = GradebookConstants.active_grading_periods;
this.gradingPeriods = {
data: [allPeriodsOption].concat(activeGradingPeriods),
selected: null,
error: null
};
this.gradingPeriods.selected = this.gradePeriodOnLoad();
},
getInitialState() {
if (this.gradingPeriods === null || this.gradingPeriods === undefined) {
this.init();
}
return this.gradingPeriods;
},
// handler for selecting a grading period
onSelect(periodData) {
var selectedPeriod, allPeriods, periodMatcher;
allPeriods = this.gradingPeriods.data;
periodMatcher = period => period.id === periodData.id;
selectedPeriod = _.find(allPeriods, periodMatcher);
if (selectedPeriod !== null && selectedPeriod !== undefined) {
this.gradingPeriods.selected = selectedPeriod.id;
this.trigger(this.gradingPeriods);
}
},
/*
([Assignment], GradingPeriod) -> [Assignment]
Given a list of assignments and a grading period, returns the assignments
which are in the grading period
*/
assignmentsInPeriod(assignments, period) {
var assignmentList;
assignmentList = _.filter(assignments, assignment =>
this.assignmentIsInPeriod(assignment, period));
return assignmentList;
},
/*
(Assignment, GradingPeriod) -> Boolean
Given an assignment and a grading period, checks if assignment is in the
given grading period
*/
assignmentIsInPeriod(assignment, period) {
var dueDateString, assignmentDueDate, periodStartDate,
periodEndDate, result;
dueDateString = new DueDateCalculator(assignment).dueDate();
assignmentDueDate = new Date(dueDateString);
periodStartDate = new Date(period.start_date);
periodEndDate = new Date(period.end_date);
result = (assignmentDueDate >= periodStartDate && assignmentDueDate <= periodEndDate)
|| ((assignmentDueDate === null || dueDateString === null) && period === this.lastPeriod())
|| period.id === '0';
return result;
},
/*
"Integer" -> Boolean
Given the id of a grading period (string format), checks whether the
grading period is active.
*/
periodIsActive(periodId) {
var result;
result = _.chain(this.gradingPeriods.data)
.map(period => period.id)
.contains(periodId)
.value();
return result;
},
/*
() -> GradingPeriod
Returns the last (active) grading period of the course
*/
lastPeriod() {
var last;
last = _.find(this.gradingPeriods.data, gradingPeriod => gradingPeriod.is_last);
return last;
},
/*
() -> "Integer"
Determines which grading period should be loaded when gradebook is opened
*/
gradePeriodOnLoad() {
var currentPeriodId = userSettings.contextGet('gradebook_current_grading_period');
if (!(currentPeriodId &&
(currentPeriodId === '0' || this.periodIsActive(currentPeriodId)))) {
currentPeriodId = GradebookConstants.current_grading_period_id;
}
if (currentPeriodId === null || currentPeriodId === undefined) {
currentPeriodId = '0';
}
return currentPeriodId;
}
});
return GradePeriodsStore;
});

View File

@ -1,62 +0,0 @@
define([
'reflux',
'underscore',
'jquery',
'jsx/gradebook/grid/actions/keyboardNavigationActions',
'jsx/gradebook/grid/helpers/keyboardNavManager'
], function (Reflux, _, $, KeyboardNavigationActions, KeyboardNavigationManager) {
var KeyboardNavigationStore = Reflux.createStore({
listenables: [KeyboardNavigationActions],
init() {
this.state = {
currentCellIndex: -1,
currentColumnIndex: -1,
currentRowIndex: -1
};
},
getInitialState() {
if(this.state === undefined) {
this.init();
}
return this.state;
},
rowCount(columnCount) {
var cellCount;
if (columnCount === 0) return 0;
cellCount = $('.gradebook-cell').size();
return cellCount / columnCount;
},
columnCount() {
return $('.fixedDataTableCellLayout_columnResizerContainer').size();
},
onConstructKeyboardNavManager() {
var columnCount = this.columnCount();
var rowCount = this.rowCount(columnCount);
var dimensions = { width: columnCount, height: rowCount };
this.navManager = new KeyboardNavigationManager(dimensions);
},
onHandleKeyboardEvent(event) {
var newIndex = this.navManager.makeMove(event, this.state.currentCellIndex);
if (_.isNumber(newIndex)) this.onSetActiveCell(newIndex);
},
onSetActiveCell(cellIndex) {
var coords = this.navManager.indexToCoords(cellIndex);
this.state.currentCellIndex = cellIndex;
this.state.currentColumnIndex = coords.x;
this.state.currentRowIndex = coords.y;
this.trigger(this.state);
}
});
return KeyboardNavigationStore;
});

View File

@ -1,68 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/actions/sectionsActions',
'compiled/userSettings'
], function(Reflux, _, SectionsActions, userSettings) {
var SectionsStore = Reflux.createStore({
listenables: [SectionsActions],
init() {
this.state = {
sections: null,
error: null,
selected: this.sectionOnLoad()
};
},
getInitialState() {
if (this.state === undefined) {
this.init();
}
return this.state;
},
onLoadCompleted(sectionData) {
var allSectionsOption;
allSectionsOption = {
id: '0',
name: 'All Sections'
};
sectionData.unshift(allSectionsOption);
this.state.sections = sectionData;
this.trigger(this.state);
},
onLoadFailed(error) {
this.state.error = error;
this.trigger(this.state);
},
onSelectSection(sectionId) {
this.state.selected = sectionId;
this.trigger(this.state);
},
sectionOnLoad() {
var defaultSectionId = '0';
return userSettings.contextGet('grading_show_only_section') || defaultSectionId;
},
selected() {
var selectedId, currentSection, sections;
selectedId = this.state.selected;
sections = this.state.sections;
currentSection = _.find(sections, section => section.id === selectedId);
return currentSection;
},
});
return SectionsStore;
});

View File

@ -1,52 +0,0 @@
define([
'reflux',
'jquery',
'../actions/settingsActions'
], function (Reflux, $, SettingsActions) {
var MOUNT_ELEMENT = document.getElementById('gradebook-grid-wrapper'),
PADDING = 20,
TOOLBAR_HEIGHT = $('#gradebook-toolbar').height(),
TOOLBAR_OFFSET = $('#gradebook-toolbar').offset().top;
var SettingsStore = Reflux.createStore({
listenables: [SettingsActions],
init () {
this.columnWidths = ENV.GRADEBOOK_OPTIONS.gradebook_column_size_settings || {};
},
getInitialState() {
if(this.settings === undefined) {
this.settings = {
width: this.getGradebookWidth(),
height: this.getGradebookHeight(),
columnWidths: this.columnWidths
};
}
return this.settings;
},
onResize() {
this.settings.width = this.getGradebookWidth();
this.settings.height = this.getGradebookHeight();
this.trigger(this.settings);
},
onSaveColumnSize(newColumnWidth, dataKey) {
this.columnWidths[dataKey] = newColumnWidth;
this.trigger(this.settings);
},
getGradebookWidth() {
return $(MOUNT_ELEMENT).width();
},
getGradebookHeight() {
var windowHeight = $(window).innerHeight();
return windowHeight - (TOOLBAR_HEIGHT + TOOLBAR_OFFSET + PADDING);
}
});
return SettingsStore;
});

View File

@ -1,95 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/actions/studentEnrollmentsActions',
'jsx/gradebook/grid/stores/sectionsStore'
], function (Reflux, _, StudentEnrollmentsActions, SectionsStore) {
var StudentEnrollmentsStore = Reflux.createStore({
listenables: [
StudentEnrollmentsActions
],
init() {
this.listenTo(SectionsStore, this.onSectionSelected);
this.state = {
data: null,
error: null,
all: null
}
},
getInitialState() {
if (this.state === undefined) {
this.init();
}
return this.state;
},
onLoadFailed(error) {
this.state.error = error;
this.trigger(this.state);
},
onLoadCompleted(studentEnrollmentData) {
this.state.all = studentEnrollmentData;
this.state.inCurrentSection = this.studentsInSection(SectionsStore.selected());
this.state.data = this.state.inCurrentSection;
this.trigger(this.state);
},
onSearch(searchTerm) {
this.applySearch(searchTerm);
this.trigger(this.state);
},
onSectionSelected() {
var selectedSection, studentsInSection;
selectedSection = SectionsStore.selected();
studentsInSection = this.studentsInSection(selectedSection);
if (studentsInSection === null || studentsInSection === undefined) {
return;
}
this.state.inCurrentSection = studentsInSection;
this.state.data = studentsInSection;
this.applySearch(this.state.searchTerm);
this.trigger(this.state);
},
applySearch(searchTerm) {
if (searchTerm !== null && searchTerm !== undefined) {
var pattern = new RegExp(searchTerm.toLowerCase()), predicate = function(enrollment) {
var user = enrollment.user;
return (user.name && user.name.toLowerCase().match(pattern)) ||
(user.sis_login_id && user.sis_login_id.toLowerCase().match(pattern)) ||
(user.login_id && user.login_id.toLowerCase().match(pattern));
};
this.state.searchTerm = searchTerm;
this.state.data = _.filter(this.state.inCurrentSection, predicate);
}
},
studentsInSection(selectedSection) {
var students, filteredStudents;
students = this.state.all;
if (_.isUndefined(students)) {
return undefined;
} else if (_.isUndefined(selectedSection) || selectedSection.id === '0'){
return students;
}
filteredStudents = _.filter(students, student => student.course_section_id === selectedSection.id);
return filteredStudents;
}
});
return StudentEnrollmentsStore;
});

View File

@ -1,160 +0,0 @@
define([
'reflux',
'underscore',
'jquery',
'../actions/submissionsActions',
'../stores/assignmentGroupsStore',
'../stores/gradingPeriodsStore',
'compiled/gradebook2/GradebookTranslations',
'compiled/jquery.rails_flash_notifications'
], function (Reflux, _, $, SubmissionsActions, AssignmentGroupsStore,
GradingPeriodsStore, GRADEBOOK_TRANSLATIONS) {
var SubmissionsStore = Reflux.createStore({
listenables: [SubmissionsActions],
init() {
this.state = {
data: null,
error: null,
selected: null
};
},
getInitialState() {
if (this.state === null || this.state === undefined) {
this.init();
}
return this.state;
},
onUpdateGradeCompleted(postedGrade, response) {
var userSubmissions =
_.find(this.state.data, (s) => s.user_id === postedGrade.userId)
.submissions;
var submission = _.find(userSubmissions, (s) => s.id === response.id);
if (submission) {
var submissionIndex = _.indexOf(_.pluck(userSubmissions, 'id'), response.id);
userSubmissions[submissionIndex] = response;
} else {
userSubmissions.push(response);
}
this.trigger(this.state);
},
onUpdateGradeFailed() {
$.flashError(GRADEBOOK_TRANSLATIONS.submission_update_error);
},
onLoadFailed(error) {
this.state.error = error;
this.trigger(this.state);
},
onLoadCompleted(submissions) {
this.state.data = submissions;
this.trigger(this.state);
},
updateSubmissions(currentSubs, updatedSubs) {
var mergedSubmissions = _.extend(
_.indexBy(currentSubs, 'id'),
_.indexBy(updatedSubs, 'id')
);
return _.values(mergedSubmissions);
},
onUpdatedSubmissionsReceived(updatedSubs) {
var updatedSubsForGroup;
this.state.data = _.map(this.state.data, (submissionGroup) => {
updatedSubsForGroup = _.filter(
updatedSubs,
updatedSub => updatedSub.user_id === submissionGroup.user_id
);
submissionGroup.submissions = this.updateSubmissions(submissionGroup.submissions, updatedSubsForGroup);
return submissionGroup;
});
this.trigger(this.state);
},
/*
([Submission], [Assignment]) -> [Submission]
Given a list of submissions and a list of assignments, filters out the
submissions which don't belong to an assignment in the list of
assignments
*/
filterSubmissions(submissions, assignments) {
var assignmentIds, filteredSubmissions;
assignmentIds = _.map(assignments, assignment => assignment.id);
filteredSubmissions = _.filter(submissions, submission =>
_.contains(assignmentIds, submission.assignment_id));
return filteredSubmissions;
},
/*
([Submission], [AssignmentGroup]) -> [AssignmentGroup]
Given a list of submissions and assignment groups, Returns the assignment
groups which have a submission in the list.
*/
assignmentGroupsForSubmissions(submissions, assignmentGroups) {
var assignmentIds, relevantGroups;
assignmentIds = _.map(submissions, s => s.assignment_id);
relevantGroups = _.filter(assignmentGroups, assignmentGroup =>
_.filter(assignmentGroup.assignments, a =>
_.contains(assignmentIds, a.id)).length > 0
);
return relevantGroups;
},
/*
[Submission] -> [Assignment]
Given a list of submissions, returns a list of assignments which those
submissions belong to. Assignments are not guaranteed to be unique. If
uniqueness is required, use `_.uniq` on the result.
*/
assignmentsForSubmissions(submissions) {
var assignmentIds, allAssignments;
assignmentIds = _.map(submissions, submission => submission.assignment_id);
allAssignments = AssignmentGroupsStore.assignments(assignmentIds);
return allAssignments;
},
/*
([Submission], GradingPeriod) -> [Submission]
Takes a list of submissions and returns the submissions in that list
which are in the given grading period
*/
submissionsInPeriod(submissions, period) {
var periodAssignments, periodSubmissions, assignments;
assignments = this.assignmentsForSubmissions(submissions);
periodAssignments = GradingPeriodsStore.assignmentsInPeriod(assignments, period);
periodSubmissions = this.filterSubmissions(submissions, periodAssignments);
return periodSubmissions;
},
/*
[Submission] -> [Submission]
Takes a list of submissions and returns the submissions in that list
which are in the current grading period
*/
submissionsInCurrentPeriod(submissions) {
var currentPeriod = GradingPeriodsStore.selected();
return this.submissionsInPeriod(submissions, currentPeriod);
}
});
return SubmissionsStore;
});

View File

@ -1,165 +0,0 @@
define([
'reflux',
'underscore',
'jsx/gradebook/grid/constants',
'jsx/gradebook/grid/stores/studentEnrollmentsStore',
'jsx/gradebook/grid/stores/gradebookToolbarStore',
'jsx/gradebook/grid/stores/assignmentGroupsStore',
'jsx/gradebook/grid/stores/gradingPeriodsStore',
'jsx/gradebook/grid/stores/submissionsStore',
'jsx/gradebook/grid/stores/customColumnsStore',
'jsx/gradebook/grid/actions/tableActions'
], function (Reflux, _, GradebookConstants, StudentEnrollmentsStore, GradebookToolbarStore,
AssignmentGroupsStore, GradingPeriodsStore, SubmissionsStore,
CustomColumnsStore, TableActions) {
var TableStore = Reflux.createStore({
init() {
this.state = {
students: null,
allAssignments: null,
assignments: null,
submissions: null,
toolbarOptions: GradebookToolbarStore.toolbarOptions,
gradingPeriods: null,
assignmentGroups: null,
error: null,
rows: null,
customColumns: null,
loading: true
};
this.listenToMany(TableActions);
this.listenTo(GradebookToolbarStore, this.onToolbarOptionsChanged);
this.listenTo(StudentEnrollmentsStore, this.onEnrollmentsChanged);
this.listenTo(AssignmentGroupsStore, this.onAssignmentGroupsChanged);
this.listenTo(SubmissionsStore, this.onSubmissionsChanged);
this.listenTo(GradingPeriodsStore, this.onGradingPeriodsChanged);
this.listenTo(CustomColumnsStore, this.onCustomColumnsChanged);
},
getInitialState() {
if (this.state === undefined) {
this.init();
}
return this.state;
},
onEnterLoadingState() {
this.state.loading = true;
this.trigger(this.state);
},
onEnrollmentsChanged(enrollmentData) {
this.state.students = enrollmentData.data;
this.constructTableData();
this.trigger(this.state);
},
onAssignmentGroupsChanged(assignmentGroupData) {
var arrangeBy, assignments;
arrangeBy = this.state.toolbarOptions.arrangeColumnsBy;
assignments = _.chain(assignmentGroupData.data)
.map(assignmentGroup => assignmentGroup.assignments)
.flatten()
.reject(assignment => _.contains(assignment.submission_types, 'attendence'))
.filter(assignment => assignment.published)
.value();
this.state.allAssignments = _.groupBy(assignments, assignment => assignment.id);
this.state.assignments = this.state.allAssignments
this.state.assignmentGroups = _.map(assignmentGroupData.data, group => {
group.columnId = 'assignment_group_' + group.id;
return group;
});
this.constructTableData();
this.filterAssignmentsByPeriod();
this.trigger(this.state);
},
filterAssignmentsByPeriod() {
var assignments;
if (this.state.allAssignments === null || this.state.gradingPeriods === null) {
return;
}
assignments = _.flatten(_.values(this.state.allAssignments));
assignments = _.filter(assignments, assignment =>
GradingPeriodsStore.assignmentIsInPeriod(assignment, GradingPeriodsStore.selected()));
this.state.assignments = assignments;
},
onSubmissionsChanged(submissionsData) {
var submissions;
submissions = _.groupBy(submissionsData.data, submission => submission.user_id);
this.state.submissions = submissions;
this.constructTableData();
this.trigger(this.state);
},
onToolbarOptionsChanged(toolbarOptionsData) {
this.state.toolbarOptions = toolbarOptionsData;
this.trigger(this.state);
},
onGradingPeriodsChanged(gradingPeriodsData) {
this.state.gradingPeriods = gradingPeriodsData
this.filterAssignmentsByPeriod();
this.trigger(this.state);
},
onCustomColumnsChanged(customColumnsData) {
if (customColumnsData.error) {
this.state.error = customColumnsData.error;
}
this.state.customColumns = customColumnsData;
this.constructTableData();
this.trigger(this.state);
},
constructTableData() {
var students, assignments, submissions, assignmentGroups, customColumns;
students = this.state.students;
assignments = this.state.assignments;
submissions = this.state.submissions;
assignmentGroups = this.state.assignmentGroups;
customColumns = this.state.customColumns;
if (students && assignments && submissions && customColumns) {
this.state.rows = _.map(students, student => {
var displayName, rowData, userSubmissions, teacherNote;
displayName = GradebookConstants.list_students_by_sortable_name_enabled ?
student.user.sortable_name : student.user.name;
userSubmissions = _.flatten(_.map(submissions[student.user_id], s => s.submissions));
userSubmissions = _.groupBy(userSubmissions, s => s.assignment_id);
teacherNote = _.find(
customColumns.teacherNotes,
note => note.user_id === student.user_id
);
rowData = {
studentName: displayName,
submissions: userSubmissions,
assignmentGroups: assignmentGroups,
student: student,
isConcluded: student.enrollment_state === "completed",
isInactive: student.enrollment_state === "inactive",
teacherNote: teacherNote ? teacherNote.content : ''
}
return rowData;
});
this.state.loading = false;
}
}
});
return TableStore;
});

View File

@ -1,71 +0,0 @@
define([
'react',
'underscore',
'../components/gridCell',
'../components/column_types/studentNameColumn',
'../components/column_types/notesColumn',
'../components/column_types/assignmentPercentage',
'../components/column_types/assignmentPassFail',
'../components/column_types/assignmentLetterGrade',
'../components/column_types/assignmentPoints',
'../components/column_types/totalColumn',
'../components/column_types/assignmentGroupColumn',
'../components/column_types/customColumn',
'i18n!gradebook',
'../constants'
], function(
React,
_,
GridCell,
StudentNameColumn,
NotesColumn,
AssignmentPercentColumn,
AssignmentPassFailColumn,
AssignmentLetterGradeColumn,
AssignmentPointsColumn,
TotalColumn,
AssignmentGroupColumn,
CustomColumn,
I18n,
GradebookConstants
) {
var cellIndex = 0;
var renderers = {};
renderers[GradebookConstants.STUDENT_COLUMN_ID] = StudentNameColumn;
renderers[GradebookConstants.NOTES_COLUMN_ID] = NotesColumn;
renderers[GradebookConstants.PERCENT_COLUMN_ID] = AssignmentPercentColumn;
renderers[GradebookConstants.PASS_FAIL_COLUMN_ID] = AssignmentPassFailColumn;
renderers[GradebookConstants.GPA_SCALE_COLUMN_ID] = AssignmentLetterGradeColumn;
renderers[GradebookConstants.LETTER_GRADE_COLUMN_ID] = AssignmentLetterGradeColumn;
renderers[GradebookConstants.POINTS_COLUMN_ID] = AssignmentPointsColumn;
renderers[GradebookConstants.TOTAL_COLUMN_ID] = TotalColumn;
renderers[GradebookConstants.ASSIGNMENT_GROUP_COLUMN_ID] = AssignmentGroupColumn;
renderers[GradebookConstants.CUSTOM_COLUMN_ID] = CustomColumn;
function getRenderer (cellData, cellDataKey, rowData, rowIndex, columnData) {
var Renderer = renderers[columnData.columnType];
if (Renderer) {
var key = columnData.columnType + cellDataKey;
return (<GridCell
activeCell={columnData.activeCell}
cellIndex={cellIndex++}
columnData={columnData}
cellData={cellData}
key={key}
renderer={Renderer}
rowData={rowData}
setActiveCell={columnData.setActiveCell}/>);
} else {
var message = 'Cell Renderer Not Registered. ' +
'Register "' + columnData.columnType +
'" in the renderers Object (columnRenderer.jsx)';
throw new Error(message);
}
}
return {getRenderer: getRenderer};
});

View File

@ -1,14 +0,0 @@
define([
'react',
'../components/column_types/headerRenderer'
], function(React, HeaderRenderer) {
var getHeader = function(label, cellDataKey, columnData, _, width) {
return (
<HeaderRenderer key={cellDataKey + '_header'} label={label}
columnData={columnData} width={width}/>
);
};
return { getHeader: getHeader };
});

View File

@ -0,0 +1,7 @@
define(function () {
const constants = {
MAX_NOTE_LENGTH: 255
};
return constants;
});

View File

@ -0,0 +1,66 @@
define([
'underscore'
], function (_) {
const getDueDateFromAssignment = function (assignment) {
if (assignment.due_at) {
return new Date(assignment.due_at);
}
const overrides = assignment.overrides;
if (!overrides || overrides.length > 1) { return null }
const overrideWithDueAt = _.find(overrides, override => override.due_at);
return overrideWithDueAt ? new Date(overrideWithDueAt.due_at) : null;
};
const assignmentHelper = {
compareByDueDate (a, b) {
let aDate = getDueDateFromAssignment(a);
let bDate = getDueDateFromAssignment(b);
const aDateIsNull = _.isNull(aDate);
const bDateIsNull = _.isNull(bDate);
if (aDateIsNull && !bDateIsNull) { return 1 }
if (!aDateIsNull && bDateIsNull) { return -1 }
if (aDateIsNull && bDateIsNull) {
if (this.hasMultipleDueDates(a) && !this.hasMultipleDueDates(b)) { return -1 }
if (!this.hasMultipleDueDates(a) && this.hasMultipleDueDates(b)) { return 1 }
}
aDate = +aDate;
bDate = +bDate;
if (aDate === bDate) {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
if (aName === bName) { return 0 }
return aName > bName ? 1 : -1;
}
return aDate - bDate;
},
hasMultipleDueDates (assignment) {
return !!(
assignment.has_overrides &&
assignment.overrides &&
assignment.overrides.length > 1
);
},
getComparator (arrangeBy) {
if (arrangeBy === 'due_date') {
return this.compareByDueDate.bind(this);
}
if (arrangeBy === 'assignment_group') {
return this.compareByAssignmentGroup.bind(this);
}
},
compareByAssignmentGroup (a, b) {
const diffOfAssignmentGroupPosition = a.assignment_group_position - b.assignment_group_position;
if (diffOfAssignmentGroupPosition === 0) {
const diffOfAssignmentPosition = a.position - b.position;
if (diffOfAssignmentPosition === 0) { return 0 }
return diffOfAssignmentPosition;
}
return diffOfAssignmentGroupPosition;
}
};
return assignmentHelper;
});

View File

@ -3,4 +3,3 @@
@import "pages/gradebook2/learning_outcome_gradebook.scss";
@import "pages/gradebook2/gradebook2.scss";
@import "pages/gradebook2/grade_passback.scss";
@import "vendor/fixed-data-table.scss";

View File

@ -1,6 +0,0 @@
@import "pages/shared/message_students.scss";
@import "base/environment";
@import "pages/gradebook2/grade_passback.scss";
@import "pages/gradebook2/learning_outcome_gradebook.scss";
@import "pages/react_gradebook/react_gradebook.scss";
@import "vendor/fixed-data-table.scss";

View File

@ -1,595 +0,0 @@
$gradebook_letter-grade-font: lighten($ic-color-dark, 8%);
$gradebook_even-row-color: lighten($ic-color-neutral, 6);
$gradebook_border-color: darken($ic-color-neutral, 7);
$gradebook_late-color: saturate(darken($ic-bg-light-danger, 5), 20);
$gradebook_resubmitted-color: desaturate(darken($ic-bg-light-alert, 7), 2);
.react-gradebook {
.teacher-note {
height: 100%;
}
}
// pretty much everything in this file should eventually
// go within .react-gradebook, but the notes styling is here
// because the notes modal attaches to the 'application'
// div outside of the .react-gradebook
.notes-textarea {
width: 95%;
height: 60%;
resize: none;
}
.notes-body {
height: 200px;
}
a#accessibility_warning {
@include accessibility-prompt;
@include fontSize(14px);
}
#footer {
display: none;
}
#main {
margin-bottom: 0;
}
$cell_height: 33px;
.gradebook2 .ic-layout-contentMain {
position: relative;
padding: 0 $ic-sp*2;
}
#gradebook-grid-wrapper {
position: relative;
}
.student-name {
color: #1b7eda;
text-shadow: #fbf8f8 0 0 1px;
}
.student-grades-link {
@if $use_high_contrast { color: darken($ic-link-color, 5%); }
}
.meta-cell .avatar {
width: 25px;
height: 25px;
margin-top: 3px;
margin-right: 4px;
float: left;
position: relative;
z-index: 1;
}
.student-placeholder {
display: none;
}
.student-section {
margin-top: -7px;
@include fontSize(12px);
}
.secondary_identifier_cell, .custom_column {
color: #333333;
@include fontSize(12px);
text-align: center;
line-height: 35px;
}
// show and hide student names using a parent css class.
.hide-students {
.student-name, .avatar {
display: none;
}
.student-placeholder {
display: block;
}
.secondary_identifier_cell {
text-indent: -9999em;
}
}
.gradebook-header-column {
background-color: #f3f4f8;
background-image: none;
padding: 10px;
@include fontSize(12px);
text-align: center;
font-weight: normal;
// override jqueryUI style
&.ui-state-default {
height: 30px;
}
&.hovered-column,
&.ui-state-hover {
@if $use_high_contrast { @include vertical-gradient(#eef5fc, #d5e2ed); }
@else { @include vertical-gradient(#e0ecf9, #bdd2e3); }
}
a.assignment-name {
font-weight: normal;
text-shadow: white 0 0 1px;
//this is for the ellipses
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@if $use_high_contrast {
&:hover, &:focus { text-decoration: underline; }
}
@else { color: $ic-link-color; }
}
.muted {
background: url("/images/icon-sound-muted.svg") no-repeat left center;
background-size: 16px;
padding-left: 18px;
}
.assignment-due-date {
position: relative;
bottom: 4px;
@include fontSize(12px);
// If high contrast, default to regular dark text
@if $use_high_contrast == false { color: lighten($ic-font-color-dark, 15%); }
}
.gradebook-header-drop {
//put it above the "out of"
z-index: 1;
width: 14px;
height: 14px;
position: absolute;
outline: none;
right: 3px;
bottom: 3px;
text-indent: 999px;
opacity: 0;
//override jqueryui blue border
border: 0 none;
@if $use_high_contrast {
background: url("/images/jqueryui/gradebook-header-drop2-high-contrast.png") no-repeat top left;
@media (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5),
(min-resolution: 1.5dppx) {
background-image: url("/images/jqueryui/gradebook-header-drop2-high-contrast@2x.png");
background-size: 14px 28px;
}
}
@else {
background: url("/images/jqueryui/gradebook-header-drop2.png") no-repeat top left;
@media (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5),
(min-resolution: 1.5dppx) {
background-image: url("/images/jqueryui/gradebook-header-drop2@2x.png");
background-size: 14px 28px;
}
}
&:hover, &.ui-menu-trigger-menu-is-open {
background-position: bottom left;
opacity: 1;
}
}
&:hover .gradebook-header-drop {
opacity: 1;
}
}
.gradebook-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gradebook-cell-turnitin {
position: absolute;
top: 2px;
right: 2px;
z-index: 1;
width: 20px;
height: 16px;
&.no-score {
background-image: url(/images/turnitin_no_score.png);
}
&.none-score {
background-image: url(/images/turnitin_none_score.png);
}
&.acceptable-score {
background-image: url(/images/turnitin_acceptable_score.png);
}
&.warning-score {
background-image: url(/images/turnitin_warning_score.png);
}
&.problem-score {
background-image: url(/images/turnitin_problem_score.png);
}
&.failure-score {
background-image: url(/images/turnitin_failure_score.png);
}
}
.gradebook-cell-comment {
position: absolute;
top: -1px;
right: -1px;
height: 12px;
width: 12px;
z-index: 2;
overflow: hidden;
visibility: hidden;
@if $use_high_contrast {
background: url("/images/gradebook-comments-sprite3-high-contrast.png") no-repeat 100% 0;
@media (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5),
(min-resolution: 1.5dppx) {
background-image: url("/images/gradebook-comments-sprite3-high-contrast@2x.png");
background-size: 17px 105px;
}
}
@else {
background: url("/images/gradebook-comments-sprite3.png") no-repeat 100% 0;
@media (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5),
(min-resolution: 1.5dppx) {
background-image: url("/images/gradebook-comments-sprite3@2x.png");
background-size: 17px 105px;
}
}
&.active {
visibility: visible;
background-position: 100% -41px;
width: 25px;
height: 25px;
&:hover {
background-position: 100% -88px !important;
width: 25px;
height: 25px;
}
}
}
.gradebook-cell:hover a.gradebook-cell-comment {
visibility: visible;
background-position: 100% -41px;
width: 25px;
height: 25px;
}
.gradebook-cell:hover a.gradebook-cell-comment:hover {
background-position: 100% -88px !important;
}
.gradebook-cell-comment-label {
@include hide-text;
display: inline-block;
}
.gradebook-cell-editable {
height: $cell_height - 1px -8px;
padding-top: 8px;
margin: 0;
border: 1px solid #35a5e5;
background-color: white;
box-shadow: 0 0 5px rgba(81, 203, 238, 1);
}
.gradebook-cell {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
.grade {
border: none;
text-align: center;
outline: none;
@include fontSize(12px);
width: 100%;
padding: 0;
margin: 0;
background: none;
box-shadow: none !important;
}
.grade::-webkit-outer-spin-button {
display: none;
}
.grade::-ms-clear {
display: none;
}
}
.points-input {
.out-of-grade {
text-align: right;
}
.out-of-float {
float: left;
width: 50%;
.divider {
padding: 0 2px 0 2px;
}
.grade::-webkit-outer-spin-button {
display: none;
}
}
.out-of-points {
padding-top: 1px;
text-align: left;
@if $use_high_contrast == false { color: #888888; }
}
}
$gradebook_checkbox_width: 16px;
.gradebook-checkbox {
@include hide-text;
display: block;
position: absolute;
left: 50%;
top: 50%;
width: $gradebook_checkbox_width;
height: $gradebook_checkbox_width;
margin-top: -$gradebook_checkbox_width / 2;
margin-left: -$gradebook_checkbox_width / 2;
background: url("/images/checkbox_sprite3.png") no-repeat 0 0;
&.gradebook-checkbox-complete {
background-position: -48px 0;
}
&.gradebook-checkbox-incomplete {
background-position: -64px 0;
}
&.editable {
&.gradebook-checkbox-complete {
background-position: -16px 0;
}
&.gradebook-checkbox-incomplete {
background-position: -32px 0;
}
}
}
#hide_warning {
vertical-align: top;
}
.minimized {
background-image: url("/images/4_percent_opacity.png");
.gradebook-cell {
@include fontSize(0px);
* {
display: none;
}
}
}
#gradebook-toolbar {
position: relative;
background-color: transparent;
border: transparent;
padding: $ic-sp;
border-top: none;
@include breakpoint(desktop) {
display: flex;
align-items: center;
}
}
.gradebook_dropdowns {
display: inline-block;
}
.gradebook_filter {
display: inline-block;
input {
width: 248px;
}
@include breakpoint(desktop) {
margin-left: $ic-sp/3;
}
}
.gradebook_menu {
margin-top: $ic-sp/3;
@include breakpoint(desktop) {
white-space: nowrap;
margin-top: 0;
flex: 1;
text-align: right;
}
}
@mixin gradebook_menu_label($fontsize) {
@include fontSize($fontsize);
font-weight: bold;
margin-bottom: 0;
vertical-align: 0px !important;
position: relative;
line-height: 1.5;
}
#section-to-show-menu {
width: 350px;
overflow-x: hidden;
overflow-y: auto;
max-height: 275px;
label {
@include gradebook_menu_label(12px);
}
.ui-state-focus:last-child {
margin: 0 !important;
}
}
.post-grades-placeholder {
display: inline-block;
}
.submission_type_icon {
display: block;
width: 100%;
height: 22px;
background-position: center center;
background-repeat: no-repeat;
.online_text_entry & {
background-image: url("/images/text_entry.png");
}
.discussion_topic & {
background-image: url("/images/word_bubble.png");
}
.online_upload & {
background-image: url("/images/file.png");
}
.pending_review & {
background-image: url("/images/pending_review.png");
}
.media_comment &, .media_recording & {
background-image: url("/images/media_comment.gif");
}
.quiz & {
background-image: url("/images/quiz.png");
}
}
.letter-grade-points,
.gpa-scale-points {
position: absolute;
@include fontSize(12px);
padding-left: 8px;
line-height: 19px;
color: $gradebook_letter-grade-font;
}
.final-warning {
margin-left: -16px;
}
.gradebook_dropdown, .export_dropdown {
display:none;
li, label {
@include gradebook_menu_label(13px);
cursor:pointer;
}
}
.ui-menu .ui-menu-item.ui-state-disabled {
padding: 0;
margin: 0;
line-height: 1;
}
.ui-menu-item.ui-state-disabled label {
margin-bottom: 0;
vertical-align: 0;
}
.post-grades-menu {
li.external-tools-dialog,
li.post-grades-placeholder {
cursor: pointer;
}
li.external-tools-dialog.ellip {
cursor: default;
}
}
// Very specific declaration to get Choose a Section text to have better text:background contrast in Section dropdown
#section-to-show-menu {
li:first-of-type.ui-state-disabled {
opacity: 0.8;
}
}
// Fixed Data Table overrides
#gradebook_grid {
[data-reactid$="$header"] {
box-shadow: 0px 1px 4px rgba(28, 28, 28, 0.2);
}
.gradebook-cell{
overflow-x: hidden;
white-space: nowrap;
height: 36px;
box-sizing: border-box;
margin-right: 1px;
padding: 5px;
border: 1px solid transparent;
text-align: center;
border-bottom: 1px solid $gradebook_border_color;
.student-name {
text-align: left;
}
&.active {
border-color: $ic-brand-primary !important;
}
&.late {
background-color: $gradebook_late-color;
}
&.resubmitted {
background-color: $gradebook_resubmitted-color;
}
&.grayed-out {
background-color: $grayLighter;
}
}
.assignment-grade-cell {
font-weight: 400;
}
.gradebook-header-column {
background-color: $gradebook_even-row-color;
height: 39px;
padding-top: 2px;
padding-bottom: 2px;
&.title {
padding-top: 8px;
font-weight:bold;
text-shadow: white 0 0 1px;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.public_fixedDataTableRow_highlighted,
.public_fixedDataTableRow_highlighted .public_fixedDataTableCell_main {
background-color: $gradebook_even-row-color !important;
}
}
#gradebook-toolbar {
padding-left: 0;
padding-right: 0;
}
#react-gradebook-canvas {
outline: none;
}

View File

@ -1,514 +0,0 @@
/**
* FixedDataTable v0.5.0
*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableCellGroupLayout
*/
.fixedDataTableCellGroupLayout_cellGroup {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
left: 0;
overflow: hidden;
position: absolute;
top: 0;
white-space: nowrap;
}
.fixedDataTableCellGroupLayout_cellGroup > .public_fixedDataTableCell_main {
display: inline-block;
vertical-align: top;
white-space: normal;
}
.fixedDataTableCellGroupLayout_cellGroupWrapper {
position: absolute;
top: 0;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableCellLayout
*/
.fixedDataTableCellLayout_main {
border-right-style: solid;
border-right-width: 1px;
border-width: 0 1px 0 0;
box-sizing: border-box;
display: block;
overflow: hidden;
position: absolute;
white-space: normal;
}
.fixedDataTableCellLayout_lastChild {
border-width: 0 1px 1px 0;
}
.fixedDataTableCellLayout_alignRight {
text-align: right;
}
.fixedDataTableCellLayout_alignCenter {
text-align: center;
}
.fixedDataTableCellLayout_wrap1 {
display: table;
}
.fixedDataTableCellLayout_wrap2 {
display: table-row;
}
.fixedDataTableCellLayout_wrap3 {
display: table-cell;
vertical-align: middle;
}
.fixedDataTableCellLayout_columnResizerContainer {
position: absolute;
right: 0px;
width: 6px;
z-index: 1;
}
.fixedDataTableCellLayout_columnResizerContainer:hover {
cursor: ew-resize;
}
.fixedDataTableCellLayout_columnResizerContainer:hover .fixedDataTableCellLayout_columnResizerKnob {
visibility: visible;
}
.fixedDataTableCellLayout_columnResizerKnob {
position: absolute;
right: 0px;
visibility: hidden;
width: 4px;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableColumnResizerLineLayout
*/
.fixedDataTableColumnResizerLineLayout_mouseArea {
cursor: ew-resize;
position: absolute;
right: -5px;
width: 12px;
}
.fixedDataTableColumnResizerLineLayout_main {
border-right-style: solid;
border-right-width: 1px;
box-sizing: border-box;
position: absolute;
z-index: 10;
}
body[dir="rtl"] .fixedDataTableColumnResizerLineLayout_main {
/* the resizer line is in the wrong position in RTL with no easy fix.
* Disabling is more useful than displaying it.
* #167 (github) should look into this and come up with a permanent fix.
*/
display: none !important;
}
.fixedDataTableColumnResizerLineLayout_hiddenElem {
display: none !important;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableLayout
*/
.fixedDataTableLayout_main {
border-style: solid;
border-width: 1px;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
.fixedDataTableLayout_header,
.fixedDataTableLayout_hasBottomBorder {
border-bottom-style: solid;
border-bottom-width: 1px;
}
.fixedDataTableLayout_footer .public_fixedDataTableCell_main {
border-top-style: solid;
border-top-width: 1px;
}
.fixedDataTableLayout_topShadow,
.fixedDataTableLayout_bottomShadow {
height: 4px;
left: 0;
position: absolute;
right: 0;
z-index: 1;
}
.fixedDataTableLayout_bottomShadow {
margin-top: -4px;
}
.fixedDataTableLayout_rowsContainer {
overflow: hidden;
position: relative;
}
.fixedDataTableLayout_horizontalScrollbar {
bottom: 0;
position: absolute;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableRowLayout
*/
.fixedDataTableRowLayout_main {
box-sizing: border-box;
overflow: hidden;
position: absolute;
top: 0;
}
.fixedDataTableRowLayout_body {
left: 0;
position: absolute;
top: 0;
}
.fixedDataTableRowLayout_fixedColumnsDivider {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
border-left-style: solid;
border-left-width: 1px;
left: 0;
position: absolute;
top: 0;
width: 0;
}
.fixedDataTableRowLayout_columnsShadow {
width: 4px;
}
.fixedDataTableRowLayout_rowWrapper {
position: absolute;
top: 0;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ScrollbarLayout
*/
.ScrollbarLayout_main {
box-sizing: border-box;
outline: none;
overflow: hidden;
position: absolute;
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
-webkit-transition-timing-function: ease;
transition-timing-function: ease;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ScrollbarLayout_mainVertical {
bottom: 0;
right: 0;
top: 0;
-webkit-transition-property: background-color width;
transition-property: background-color width;
width: 15px;
}
.ScrollbarLayout_mainVertical.public_Scrollbar_mainActive,
.ScrollbarLayout_mainVertical:hover {
width: 17px;
}
.ScrollbarLayout_mainHorizontal {
bottom: 0;
height: 15px;
left: 0;
-webkit-transition-property: background-color height;
transition-property: background-color height;
}
/* Touching the scroll-track directly makes the scroll-track bolder */
.ScrollbarLayout_mainHorizontal.public_Scrollbar_mainActive,
.ScrollbarLayout_mainHorizontal:hover {
height: 17px;
}
.ScrollbarLayout_face {
left: 0;
overflow: hidden;
position: absolute;
z-index: 1;
}
/**
* This selector renders the "nub" of the scrollface. The nub must
* be rendered as pseudo-element so that it won't receive any UI events then
* we can get the correct `event.offsetX` and `event.offsetY` from the
* scrollface element while dragging it.
*/
.ScrollbarLayout_face:after {
border-radius: 6px;
content: '';
display: block;
position: absolute;
-webkit-transition: background-color 250ms ease;
transition: background-color 250ms ease;
}
.ScrollbarLayout_faceHorizontal {
bottom: 0;
left: 0;
top: 0;
}
.ScrollbarLayout_faceHorizontal:after {
bottom: 4px;
left: 0;
top: 4px;
width: 100%;
}
.ScrollbarLayout_faceVertical {
left: 0;
right: 0;
top: 0;
}
.ScrollbarLayout_faceVertical:after {
height: 100%;
left: 4px;
right: 4px;
top: 0;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTable
*
*/
/**
* Table.
*/
.public_fixedDataTable_main {
border-color: #d3d3d3;
}
.public_fixedDataTable_header,
.public_fixedDataTable_hasBottomBorder {
border-color: #d3d3d3;
}
.public_fixedDataTable_header .public_fixedDataTableCell_main {
font-weight: bold;
}
.public_fixedDataTable_header,
.public_fixedDataTable_header .public_fixedDataTableCell_main {
background-color: #f6f7f8;
background-image: -webkit-linear-gradient(#fff, #efefef);
background-image: linear-gradient(#fff, #efefef);
}
.public_fixedDataTable_footer .public_fixedDataTableCell_main {
background-color: #f6f7f8;
border-color: #d3d3d3;
}
.public_fixedDataTable_topShadow {
background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAF0lEQVR4AWPUkNeSBhHCjJoK2twgFisAFagCCp3pJlAAAAAASUVORK5CYII=) repeat-x;
}
.public_fixedDataTable_bottomShadow {
background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAHElEQVQI12MwNjZmZdAT1+Nm0JDWEGZQk1GTBgAWkwIeAEp52AAAAABJRU5ErkJggg==) repeat-x;
}
.public_fixedDataTable_horizontalScrollbar .public_Scrollbar_mainHorizontal {
background-color: #fff;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableCell
*/
/**
* Table cell.
*/
.public_fixedDataTableCell_main {
background-color: #fff;
border-color: #d3d3d3;
}
.public_fixedDataTableCell_highlighted {
background-color: #f4f4f4;
}
.public_fixedDataTableCell_cellContent {
padding: 8px;
}
.public_fixedDataTableCell_columnResizerKnob {
background-color: #0284ff;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableColumnResizerLine
*
*/
/**
* Column resizer line.
*/
.public_fixedDataTableColumnResizerLine_main {
border-color: #0284ff;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule fixedDataTableRow
*/
/**
* Table row.
*/
.public_fixedDataTableRow_main {
background-color: #fff;
}
.public_fixedDataTableRow_highlighted,
.public_fixedDataTableRow_highlighted .public_fixedDataTableCell_main {
background-color: #f6f7f8;
}
.public_fixedDataTableRow_fixedColumnsDivider {
border-color: #d3d3d3;
}
.public_fixedDataTableRow_columnsShadow {
background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABCAYAAAD5PA/NAAAAFklEQVQIHWPSkNeSBmJhTQVtbiDNCgASagIIuJX8OgAAAABJRU5ErkJggg==) repeat-y;
}
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Scrollbar
*
*/
/**
* Scrollbars.
*/
/* Touching the scroll-track directly makes the scroll-track bolder */
.public_Scrollbar_main.public_Scrollbar_mainActive,
.public_Scrollbar_main:hover {
background-color: rgba(255, 255, 255, 0.8);
}
.public_Scrollbar_main.public_Scrollbar_mainActive,
.public_Scrollbar_main:hover {
background-color: rgba(255, 255, 255, 0.8);
}
.public_Scrollbar_mainOpaque,
.public_Scrollbar_mainOpaque.public_Scrollbar_mainActive,
.public_Scrollbar_mainOpaque:hover {
background-color: #fff;
}
.public_Scrollbar_face:after {
background-color: #c2c2c2;
}
.public_Scrollbar_main:hover .public_Scrollbar_face:after,
.public_Scrollbar_mainActive .public_Scrollbar_face:after,
.public_Scrollbar_faceActive:after {
background-color: #7d7d7d;
}

View File

@ -66,12 +66,12 @@
<div id="gradebook-toolbar" class="toolbar">
<div class="gradebook_dropdowns">
<% if multiple_grading_periods? %>
<span id="mgp-dropdown" class="multiple-grading-periods-selector-placeholder"></span>
<span class="multiple-grading-periods-selector-placeholder"></span>
<% end %>
</div>
<div class="gradebook_filter" style="display:none">
<% placeholder = t('Filter by student name or secondary ID') %>
<input type="text" class="search-query" id="gradebook-filter-input" placeholder="<%= placeholder %>" aria-label="<%= placeholder %>">
<input type="text" class="search-query" placeholder="<%= placeholder %>" aria-label="<%= placeholder %>">
</div>
<div class="gradebook_menu">
<span class="ui-buttonset">

View File

@ -1,163 +0,0 @@
<%
content_for :page_title, "Gradebook - #{@context.name}"
@body_classes << "gradebook2 full-width"
@show_left_side = false
@show_embedded_chat = false
css_bundle :react_gradebook
js_bundle :gradebook2, :react_gradebook
%>
<div id="keyboard_navigation"></div>
<div id="gradebook_wrapper" class="react-gradebook">
<h1 class="screenreader-only"><%= t('page_header_title', 'Gradebook') %></h1>
<div class="gradebook-header">
<div class="accessibility_warning">
<%= link_to t(:accessibility_warning, 'Warning: For improved accessibility, please click here to use the Individual View Gradebook.'), context_url(@context, :change_gradebook_version_context_gradebook_url, :version => "srgb"), :id => "accessibility_warning", :class => "screenreader-only" %>
</div>
<% if @context.feature_enabled?(:outcome_gradebook) || @context.feature_enabled?(:post_grades) %>
<nav class="gradebook-navigation">
<ul class="nav nav-pills gradebook-navigation-pills">
<li class="active">
<a href="#" data-id="assignment"><%= t(:grades, "Grades") %></a>
</li>
<% if @context.feature_enabled?(:outcome_gradebook) %>
<li>
<a href="#" data-id="outcome"><%= t(:learning_mastery, "Learning Mastery") %></a>
</li>
<% end %>
</ul>
</nav>
<% end %>
<div class="header-buttons">
<a href="<%= context_url(@context, :change_gradebook_version_context_gradebook_url, :version => "srgb") %>" class="Button individual-view-button" id="change_gradebook_version_link_holder" type="button"><%= t(:individual_view, "Individual View") %></a>
</div>
</div>
<div class="assignment-gradebook-container">
<div id="gradebook-toolbar" class="toolbar">
<div class="gradebook_dropdowns">
<% if !@context.feature_enabled?(:outcome_gradebook) && !@context.feature_enabled?(:post_grades)%>
<span class="section-button-placeholder"></span>
<% end %>
<% if multiple_grading_periods? %>
<span class="multiple-grading-periods-selector-placeholder"></span>
<% end %>
</div>
<div class="gradebook_filter">
<% placeholder = t('filter_by_student', 'Filter by student name or secondary ID') %>
<input type="text" id="gradebook-filter-input" class="search-query" placeholder="<%= placeholder %>" aria-label="<%= placeholder %>">
</div>
<div class="gradebook_menu">
<span class="ui-buttonset">
<% if @post_grades_tools.count > 1 %>
<button id="post_grades" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><%= t('Post Grades') %></button>
<ul style="display: none;" class="post-grades-menu ui-kyle-menu">
<% @post_grades_tools.each do |tool| %>
<% case tool[:type] %>
<% when :lti %>
<li class="external-tools-dialog"><a aria-controls="post_grades_frame_dialog" role="button" data-url="<%= tool[:data_url]%>"><%= tool[:name]%></a></li>
<% when :post_grades %>
<li class="post-grades-placeholder in-menu"></li>
<% when :ellip %>
<li class="external-tools-dialog ellip"><a>&hellip;</a></li>
<% end %>
<% end %>
</ul>
<% elsif @post_grades_tools.count == 1 %>
<% case @post_grades_tools[0][:type] %>
<% when :lti %>
<button class="external-tools-dialog ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" data-url="<%= @post_grades_tools[0][:data_url]%>"><%= @post_grades_tools[0][:name]%></button>
<% when :post_grades %>
<span class="post-grades-placeholder"></span>
<% end %>
<% end %>
<% if @gradebook_is_editable && @context.allows_gradebook_uploads? %>
<%= link_to(new_course_gradebook_upload_path(@context), class: "ui-button") do %>
<i class="icon-import"></i>
<%= t 'Import' %>
<% end %>
<% end %>
<button class="ui-button" id="download_csv">
<i class="icon-export"></i>
<%= t('download_scores', 'Export') %>
</button>
<ul style="display: none;" id="csv_export_options" class="export_dropdown ui-kyle-menu">
<li class="generate_new_csv"><a href="#"><%=t('export_current_gradebook', 'Current') %></a></li>
<% if @last_exported_gradebook_csv.present? %>
<li><a href="<%= @last_exported_gradebook_csv.attachment.download_url %>" class="open_in_a_new_tab">
<%= t('Previous (%{time})',time: datetime_string(@last_exported_gradebook_csv.attachment.updated_at)) %>
</a></li>
<% else %>
<li style="display:none;">
<a class="open_in_a_new_tab"></a>
</li>
<% end %>
</ul>
</span>
<iframe id="csv_download" style="display:none"></iframe>
<button id="gradebook_settings"><i class="icon-settings"></i></button>
<ul id="settings_dropdown" style="display: none;" class="gradebook_drop_down ui-kyle-menu">
<li>
<a href="<%= context_url(@context, :context_gradebook_url) %>/history">
<%= t('view_grading_history', 'View Grading History') %>
</a>
</li>
<% if @context.allows_grade_publishing_by(@current_user) && can_do(@context, @current_user, :manage_grades) %>
<li>
<a id="publish_to_sis" href="<%= context_url(@context, :context_details_url, :anchor => 'tab-grade-publishing') %>">
<%= t('publish_to_sis', 'Publish grades to SIS') %>
</a>
</li>
<% end %>
<% if @gradebook_is_editable %>
<li>
<a id="set-group-weights" class="dialog_opener" role="button" aria-controls="assignment_group_weights_dialog" href="#">
<%= t('set_group_weights', 'Set Group Weights') %>
</a>
</li>
<% end %>
<li>
<a id="student_names_toggle" href="#"></a>
</li>
<li>
<a id="arrange_by_toggle" href="#"></a>
</li>
<li class="<% if @course_is_concluded %>ui-state-disabled<% end %>">
<a>
<label>
<%= t('show_concluded_enrollments', "Show Concluded Enrollments") %>
<input type="checkbox" id="show_concluded_enrollments" />
</label>
</a>
</li>
<li>
<a>
<label>
<%= t('show_inactive_enrollments', "Show Inactive Enrollments") %>
<input type="checkbox" id="show_inactive_enrollments" />
</label>
</a>
</li>
<li>
<a id="notes_toggle" href="#"></a>
</li>
</ul>
</div>
</div>
<div id="gradebook-grid-wrapper" class="use-css-transitions-for-show-hide">
<div id="gradebook_grid"></div>
</div>
<div style="display:none;">
<%= render :partial => "shared/message_students" %>
<%= render :partial => 'submissions/submission_download' %>
</div>
</div>
<% if @context.feature_enabled?(:outcome_gradebook) %>
<div class="outcome-gradebook-container hidden"></div>
<% end %>
</div>

View File

@ -204,8 +204,6 @@ with the docker workflow
(was a missing bundle, need to start sniffing for which bundles are actually in the
directory soon)
[X] solve Module build failed: SyntaxError: /app/frontend_build/jsxYankPragma.js!/app/app/jsx/gradebook/grid/stores/tableStore.jsx: Argument name clash in strict mode (13:13)
[X] test in development and
make sure we have source to
debug with

View File

@ -229,10 +229,6 @@ module.exports = {
new webpack.PrefetchPlugin("./app/jsx/files/ShowFolder.jsx"),
new webpack.PrefetchPlugin("./app/jsx/files/UploadButton.jsx"),
new webpack.PrefetchPlugin("./app/jsx/files/utils/openMoveDialog.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/grid/components/column_types/headerRenderer.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/grid/components/dropdown_components/assignmentHeaderDropdownOptions.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/grid/components/gradebook.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/grid/wrappers/columnFactory.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/SISGradePassback/PostGradesApp.jsx"),
new webpack.PrefetchPlugin("./app/jsx/gradebook/SISGradePassback/PostGradesDialogCorrectionsPage.jsx"),
new webpack.PrefetchPlugin("./app/jsx/grading/gradingPeriodCollection.jsx"),

View File

@ -88,7 +88,6 @@ module Canvas
{name: 'redux-actions', location: 'symlink_to_node_modules/redux-actions', main: 'dist/redux-actions.min'},
{name: 'redux-logger', location: 'symlink_to_node_modules/redux-logger', main: 'dist/index'},
{name: 'redux-thunk', location: 'symlink_to_node_modules/redux-thunk', main: 'dist/redux-thunk'},
{name: 'reflux', location: 'symlink_to_node_modules/reflux', main: 'dist/reflux'},
{name: 'tinymce', location: 'symlink_to_node_modules/tinymce', main: 'tinymce'},
{name: 'spin.js', location: 'symlink_to_node_modules/spin.js', main: 'spin' },
].to_json
@ -119,10 +118,6 @@ module Canvas
deps: ['jquery', 'vendor/FileAPI/config'],
exports: 'FileAPI'
},
'fixed-data-table': {
deps: ['object_assign', 'react'],
exports: 'fixed-data-table'
},
'fullcalendar/dist/lang-all': {
deps: ['fullcalendar']
},

View File

@ -373,14 +373,6 @@ END
development: true,
root_opt_in: false
},
'gradebook_performance' => {
display_name: -> { I18n.t('Gradebook Performance') },
description: -> { I18n.t('Performance enhancements for the Gradebook') },
applies_to: 'Course',
state: 'hidden',
development: true,
root_opt_in: true
},
'anonymous_grading' => {
display_name: -> { I18n.t('Anonymous Grading') },
description: -> { I18n.t("Anonymous grading forces student names to be hidden in SpeedGrader™") },

View File

@ -101,7 +101,6 @@
"redux-actions": "0.11.0",
"redux-logger": "2.6.1",
"redux-thunk": "2.1.0",
"reflux": "0.2.7",
"spin.js": "2.3.2"
},
"repository": "instructure/canvas-lms",

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
/**
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
*/
define([], function () {
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target, firstSource) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
});

View File

@ -1,6 +1,6 @@
define [
'compiled/gradebook2/GradebookHelpers'
'jsx/gradebook/grid/constants'
'jsx/gradebook/shared/constants'
], (GradebookHelpers, GradebookConstants) ->
module "GradebookHelpers#noErrorsOnPage",
setup: ->

View File

@ -1,36 +0,0 @@
define [
'compiled/gradebook2/gradeFormatter'
], (GradeFormatter) ->
module 'gradebook2.GradeFormatter',
setup: () ->
teardown: () ->
test 'returns "-" if score is null', () ->
gradeFormatter = new GradeFormatter(null, 100)
result = gradeFormatter.toString()
equal(result, '-')
test 'returns "-" if possibleScore is 0 and score is 0', () -> # Evaluates to NaN
gradeFormatter = new GradeFormatter(0, 0)
result = gradeFormatter.toString()
equal(result, '-')
test 'returns "-" if possibleScore is 0 and score is > 0', () -> # Evaluates to Infinity
gradeFormatter = new GradeFormatter(5, 0)
result = gradeFormatter.toString()
equal(result, '-')
test 'returns "-" if possibleScore is null', () ->
gradeFormatter = new GradeFormatter(5, null)
result = gradeFormatter.toString()
equal(result, '-')
test 'returns "-" if score / possibleScore is NaN', () ->
gradeFormatter = new GradeFormatter(5, 'a')
result = gradeFormatter.toString()
equal(result, '-')
test 'returns score with a percent when valid', () ->
gradeFormatter = new GradeFormatter(5, 5)
result = gradeFormatter.toString()
equal(result, '100%')

View File

@ -1,111 +0,0 @@
define [
'react'
'react-dom'
'react-addons-test-utils'
'jsx/gradebook/grid/components/assignmentGradeCell'
'underscore'
'jsx/gradebook/grid/constants'
], (React, ReactDOM, {Simulate}, AssignmentGradeCell, _, GradebookConstants) ->
wrapper = document.getElementById('fixtures')
renderComponent = (props) ->
element = React.createElement(AssignmentGradeCell, props)
ReactDOM.render(element, wrapper)
buildComponent = (props, additionalProps) ->
cellData = props || {
columnData: {
assignment: {id: '1', points_possible: 10}
},
renderer: mockComponent,
activeCell: false,
rowData: {}
}
$.extend(cellData, additionalProps)
renderComponent(cellData)
mockComponent = React.createClass({
render: () ->
React.createElement('div')
})
buildComponentWithSubmission = (additionalProps, submissionType) ->
cellData =
columnData:
assignment: {id: '1', points_possible: 100}
cellData:
id: '1'
submission_type: submissionType || 'discussion_topic'
assignment_id: '1'
workflow_state: 'submitted'
renderer: mockComponent
activeCell: false
rowData: {}
$.extend(cellData, additionalProps)
buildComponent(cellData)
getIconClassName = (component) ->
component.refs.icon.getDOMNode().className
module 'ReactGradebook.assignmentGradeCellComponent',
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
test 'should mount', ->
ok(buildComponentWithSubmission().isMounted())
module 'ReactGradebook.assignmentGradeCellComponent icons',
setup: ->
@component = buildComponentWithSubmission()
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
test 'should render discussion icon when submission is not graded', ->
expected = 'icon-discussion'
actual = getIconClassName(@component)
equal(actual, expected)
test 'should render online-url icon when submission is not graded', ->
component = buildComponentWithSubmission({}, 'online_url')
equal(getIconClassName(component), 'icon-link')
test 'should render text-url icon when submission is not graded', ->
component = buildComponentWithSubmission({}, 'online_text_entry')
equal(getIconClassName(component), 'icon-text')
test 'should render online-upload icon when submission is not graded', ->
component = buildComponentWithSubmission({}, 'online_upload')
equal(getIconClassName(component), 'icon-document')
test 'should render online-quiz icon when submission is not graded', ->
component = buildComponentWithSubmission({}, 'online_quiz')
equal(getIconClassName(component), 'icon-quiz')
test 'should render media_recording icon when submission is not graded', ->
component = buildComponentWithSubmission({}, 'media_recording')
equal(getIconClassName(component), 'icon-media')
test 'has "active" class when cell isActive', ->
component = buildComponent(null, {activeCell: true})
classList = component.refs.detailsDialog.props.className
ok classList.indexOf('active') > -1
test 'does not have "active" class when cell is not active', ->
component = buildComponent()
classList = component.refs.detailsDialog.props.className
ok (classList.indexOf('active') == -1)
test 'opens dialog when clicking the submissions details link', ->
props =
rowData:
student:
user:
name: "Hello"
id: "1"
component = buildComponent(null, props)
openDialogStub = @stub(component, 'openDialog', (->))
detailsDialogLink = component.refs.detailsDialog
Simulate.click(detailsDialogLink)
ok openDialogStub.calledOnce

View File

@ -1,73 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/assignmentGroupColumn'
'jsx/gradebook/grid/stores/gradingPeriodsStore'
'jsx/gradebook/grid/stores/assignmentGroupsStore'
'jsx/gradebook/grid/constants'
'helpers/fakeENV'
'jquery'
'jquery.ajaxJSON'
], (React, ReactDOM, AssignmentGroupColumn, GradingPeriodsStore, AssignmentGroupsStore, GradebookConstants, fakeENV, $) ->
wrapper = document.getElementById('fixtures')
gradingPeriodsData = ->
GRADEBOOK_OPTIONS:
current_grading_period_id: '0'
assignmentGroups = ->
[{ assignments: [{ id: '3', points_possible: 25 }]}]
submissions = ->
[{ assignment_id: '3', id: '6', score: 25 }]
defaultProps = =>
groups = assignmentGroups()
cellData:
submissions: submissions()
assignmentGroup: groups[0],
rowData:
assignmentGroups: groups
buildComponent = (props) ->
cellData = props || defaultProps()
renderComponent(cellData)
renderComponent = (props) ->
element = React.createElement(AssignmentGroupColumn, props)
ReactDOM.render(element, wrapper)
innerHTML = (component) ->
component.refs.cell.getDOMNode().innerHTML
module 'ReactGradebook.assignmentGroupColumn',
setup: ->
fakeENV.setup(gradingPeriodsData())
GradebookConstants.refresh()
GradingPeriodsStore.getInitialState()
AssignmentGroupsStore.getInitialState()
AssignmentGroupsStore.onLoadCompleted(assignmentGroups())
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
fakeENV.teardown()
test 'mounts on build', ->
ok(buildComponent().isMounted())
test 'shows a "%" sign', ->
component = buildComponent()
ok(innerHTML(component).match(/%/))
test 'displays "-" if points possible is 0', ->
props = defaultProps()
props.cellData.assignmentGroups = [{assignments: []}]
props.cellData.submissions = []
component = buildComponent(props)
ok(innerHTML(component).match(/-/))
test 'has title attribute for assignment group cells', ->
component = buildComponent(defaultProps())
title = component.refs.cell.props.title
equal("25 / 25", title)

View File

@ -1,59 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/assignmentLetterGrade'
'jquery'
], (React, ReactDOM, AssignmentLetterGrade, $) ->
wrapper = document.getElementById('fixtures')
renderComponent = (data) ->
element = React.createElement(AssignmentLetterGrade, data)
ReactDOM.render(element, wrapper)
buildComponent = (props, additionalProps) ->
cellData = props || {cellData: {id: '1'}, rowData: {enrollment: {}, submissions: []}}
$.extend(cellData, additionalProps)
renderComponent(cellData)
buildComponentWithSubmission = (additionalProps, grade, gradingType) ->
cellData =
cellData: {id: '1', grading_type: gradingType || 'letter_grade'}
submission: {id: '1', grade: grade, score: '10',assignment_id: '1'}
rowData: {enrollment: {}}
$.extend(cellData, additionalProps)
buildComponent(cellData)
module 'ReactGradebook.assignmentLetterGradeComponent',
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
test 'should mount', ->
ok(buildComponent().isMounted())
test 'should display "-" if no submission is present', ->
grade = buildComponent().refs.grade.getDOMNode().innerHTML
equal(grade, '-')
test 'should display "-" if submission grade is null', ->
component = buildComponentWithSubmission({}, null)
grade = component.refs.grade.getDOMNode().innerHTML
equal(grade, '-')
test 'should display grade and score when assignment is a letter grade', ->
component = buildComponentWithSubmission({}, 'A')
elements = component.refs.grade.getDOMNode().children
grade = elements[0].innerHTML
score = elements[1].innerHTML
equal(grade, 'A')
equal(score, '10')
test 'should display only grade when assignment is a GPA scale grade', ->
component = buildComponentWithSubmission({}, 'A', 'gpa_scale')
elements = component.refs.grade.getDOMNode().children
grade = elements[0].innerHTML
score = elements[1].innerHTML
equal(grade, 'A')
equal(score, '')

View File

@ -1,84 +0,0 @@
define [
'react',
'react-dom',
'react-addons-test-utils',
'jquery',
'jsx/gradebook/grid/components/column_types/assignmentPassFail'
], (React, ReactDOM, {Simulate}, $, AssignmentPassFail) ->
wrapper = document.getElementById('fixtures')
renderComponent = (submission) =>
cellData = {id: 1}
data =
cellData: cellData
submission: submission
rowData: {enrollment: {}}
isActiveCell: true
element = React.createElement(AssignmentPassFail, data)
ReactDOM.render(element, wrapper)
createSubmission = (score) =>
id: 2
assignment_id: 1
workflow_state: 'graded'
grade: score
createCompleteSubmission = () =>
createSubmission('complete')
createIncompleteSubmission = () =>
createSubmission('incomplete')
module 'ReactGradebook.assignmentPassFail',
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
test 'should mount', =>
ok(renderComponent().isMounted())
test 'should display "-" if no submission is present', =>
grade = renderComponent().refs.grade.getDOMNode()
grade = $(grade.innerHTML).text()
equal(grade, '-')
test 'should display a checkbox when the submission is complete', =>
submission = createCompleteSubmission()
component = renderComponent(submission)
element = component.refs.grade.getDOMNode()
ok($(element).hasClass('gradebook-checkbox-complete'))
test 'should display an "x" for when the submission is incomplete', =>
submission = createIncompleteSubmission()
component = renderComponent(submission)
element = component.refs.grade.getDOMNode()
ok($(element).hasClass('gradebook-checkbox-incomplete'))
test 'should display an empty box after clicking a submission with a null grade', =>
component = renderComponent()
Simulate.click(component.refs.grade.getDOMNode())
$element = $(component.refs.grade.getDOMNode())
ok($element.hasClass('gradebook-checkbox'))
ok($element.hasClass('gradebook-checkbox-null'))
ok($element.hasClass('editable'))
test 'should display an editable checkbox after clicking a complete submission', =>
component = renderComponent(createCompleteSubmission())
Simulate.click(component.refs.grade.getDOMNode())
$element = $(component.refs.grade.getDOMNode())
ok($element.hasClass('gradebook-checkbox-complete'))
ok($element.hasClass('editable'))
test 'should display an editable "x" after clicking an incomplete submission', =>
component = renderComponent(createIncompleteSubmission())
Simulate.click(component.refs.grade.getDOMNode())
$element = $(component.refs.grade.getDOMNode())
ok($element.hasClass('gradebook-checkbox-incomplete'))
ok($element.hasClass('editable'))
test 'should change editable complete to editable incomplete submission', =>
component = renderComponent(createCompleteSubmission())
Simulate.click(component.refs.grade.getDOMNode())
$element = $(component.refs.grade.getDOMNode())
ok($element.hasClass('gradebook-checkbox-complete'))

View File

@ -1,75 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/assignmentPercentage'
'jquery'
'jquery.ajaxJSON'
], (React, ReactDOM, AssignmentPercentage, $) ->
wrapper = document.getElementById('fixtures')
renderComponent = (data) ->
element = React.createElement(AssignmentPercentage, data)
ReactDOM.render(element, wrapper)
buildComponent = (props, additionalProps) ->
cellData = props || {
rowData: {
student: {
enrollment_state: "active"
}
}
}
$.extend(cellData, additionalProps)
renderComponent(cellData)
buildComponentWithSubmission = (additionalProps) ->
cellData =
cellData: {id: '1', grade: '100%', assignment_id: '1'}
rowData: {
student: {
enrollment_state: "active"
}
}
$.extend(cellData, additionalProps)
buildComponent(cellData)
module 'ReactGradebook.assignmentPercentageComponent',
setup: ->
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
test 'should mount', ->
ok(buildComponent().isMounted())
test 'should display "-" if no submission present"', ->
grade = buildComponent().refs.grade.getDOMNode().innerHTML
equal(grade, '-')
test 'should display a grade when a submission is present', ->
component = buildComponentWithSubmission()
grade = component.refs.grade.getDOMNode().innerHTML
equal(grade, '100')
test 'should add "%" to grade when editing', ->
component = buildComponentWithSubmission({isActiveCell: true})
grade = component.refs.gradeInput.getDOMNode().value
equal(grade, '100%')
test 'should diplay a text field after clicking on the cell', ->
component = buildComponent(null, {isActiveCell: true})
equal(component.refs.gradeInput.getDOMNode().tagName, 'INPUT')
test 'should select the text of the grade after click', ->
component = buildComponentWithSubmission({isActiveCell: true})
component.componentDidUpdate({}, {})
grade = component.refs.gradeInput.getDOMNode().value
equal(grade, window.getSelection().toString())
#TODO: use squire
#test 'should return to view state when enter is pressed when editing', ->
# component = buildComponentWithSubmission()
# Simulate.click(component.refs.grade.getDOMNode())
# Simulate.keyUp(component.refs.gradeInput.getDOMNode(), {key: 'Enter'})
# notOk(component.refs.gradeInput)
# ok(component.refs.grade)

View File

@ -1,60 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/assignmentPoints'
'jquery'
], (React, ReactDOM, AssignmentPoints, $) ->
renderComponent = (data) ->
element = React.createElement(AssignmentPoints, data)
ReactDOM.render(element, wrapper)
buildComponent = (props, additionalProps) ->
cellData = props || {
columnData: { assignment: { id: '1', points_possible: 10 } }
rowData: { student: { enrollment_state: 'active' } }
}
$.extend(cellData, additionalProps)
renderComponent(cellData)
buildComponentWithSubmission = (additionalProps, grade) ->
props =
cellData: {id: '1', grade: grade, score: '10', assignment_id: '1'}
columnData: {assignment: {id: '1', points_possible: 100}}
rowData: { student: { enrollment_state: 'active' } }
$.extend(props, additionalProps)
buildComponent(props)
wrapper = document.getElementById('fixtures')
module 'ReactGradebook.assignmentPointsComponent',
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
test 'should mount', ->
ok(buildComponent().isMounted())
test 'should display "-" if no submission is present', ->
grade = buildComponent().refs.grade.getDOMNode().innerHTML
equal(grade, '-')
test 'should display "-" if the submission grade is null', ->
component = buildComponentWithSubmission({}, null)
grade = component.refs.grade.getDOMNode().innerHTML
equal(grade, '-')
test 'should display "-/10" when first creating the submission', ->
component = buildComponent(null, {isActiveCell: true})
grade = component.refs.gradeInput.getDOMNode().value
pointsPossible = component.refs.pointsPossible.getDOMNode().innerHTML
equal(grade, '-')
equal(pointsPossible, '10')
test 'should display grade when submission has a grade', ->
component = buildComponentWithSubmission({isActiveCell: true}, '10')
grade = component.refs.gradeInput.getDOMNode().value
pointsPossible = component.refs.pointsPossible.getDOMNode().innerHTML
equal(grade, '10')
equal(pointsPossible, '100')

View File

@ -1,124 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/headerRenderer'
'jquery'
'jquery.instructure_date_and_time'
'translations/_core_en'
], (React, ReactDOM, HeaderRenderer, $) ->
wrapper = document.getElementById('fixtures')
displayedDueDate = (component) ->
component.refs.dueDate.getDOMNode().textContent
defaultProps = () ->
label: 'Column Label'
columnData:
assignment:
due_at: '2015-07-17T05:59:59Z'
enrollments: []
submissions: {}
renderComponent = (data) ->
element = React.createElement(HeaderRenderer, data)
ReactDOM.render(element, wrapper)
buildComponent = (props) ->
columnData = props
renderComponent(columnData)
module 'HeaderRenderer',
setup: ->
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
test 'displays nothing if there is no assignment', ->
props = defaultProps()
props.columnData = {}
component = buildComponent(props)
deepEqual displayedDueDate(component), ''
test 'displays "No due date" if the assignment has no due date and no overrides', ->
props = defaultProps()
props.columnData.assignment.due_at = null
component = buildComponent(props)
deepEqual displayedDueDate(component), 'No due date'
test 'displays "No due date" if the assignment has no due date and one override with no due date', ->
props = defaultProps()
props.columnData.assignment.due_at = null
props.columnData.assignment.overrides = [
{ title: 'section 1', due_at: null }
]
component = buildComponent(props)
deepEqual displayedDueDate(component), 'No due date'
test 'displays the override due date if the assignment has no due date and one' +
' override with a due date', ->
@stub($, 'sameYear').returns(true)
props = defaultProps()
props.columnData.assignment.due_at = null
props.columnData.assignment.overrides = [
{ title: 'section 1', due_at: '2015-07-18T05:59:59Z' }
]
component = buildComponent(props)
deepEqual displayedDueDate(component), 'Due Jul 18'
test 'displays "Multiple due dates" if the assignment has more than one override', ->
props = defaultProps()
props.columnData.assignment.due_at = null
props.columnData.assignment.overrides = [
{ title: 'section 1', due_at: '2015-07-17T05:59:59Z' },
{ title: 'section 2', due_at: '2015-07-18T05:59:59Z' }
]
component = buildComponent(props)
deepEqual displayedDueDate(component), 'Multiple due dates'
#this logic matches the logic in Assignment.coffee#multipleDueDates, which is
#used to display due dates on the assignment page. I'm matching that logic
#for the sake of consistency.
test 'displays "Multiple due dates" if the assignment has more than one override,' +
'even if those overrides do not have due dates themselves', ->
props = defaultProps()
props.columnData.assignment.due_at = null
props.columnData.assignment.overrides = [
{ title: 'section 1', due_at: null },
{ title: 'section 2', due_at: null }
]
component = buildComponent(props)
deepEqual displayedDueDate(component), 'Multiple due dates'
test 'calculates the correct due date string for an assignment due in the current year', ->
@stub($, 'sameYear').returns(true)
props = defaultProps()
component = buildComponent(props)
deepEqual component.headerDate(props.columnData), 'Due Jul 17'
test 'calculates the correct due date string for an assignment due in a different year', ->
@stub($, 'sameYear').returns(false)
props = defaultProps()
component = buildComponent(props)
deepEqual component.headerDate(props.columnData), 'Due Jul 17, 2015'
test 'displays due date without the year if the assignment has a due date (current year)', ->
@stub($, 'sameYear').returns(true)
props = defaultProps()
component = buildComponent(props)
deepEqual displayedDueDate(component), 'Due Jul 17'
test 'displays due date with the year if the assignment has a due date (different year)', ->
@stub($, 'sameYear').returns(false)
props = defaultProps()
component = buildComponent(props)
deepEqual displayedDueDate(component), 'Due Jul 17, 2015'

View File

@ -1,111 +0,0 @@
define [
'react'
'react-dom'
'react-addons-test-utils'
'underscore'
'jsx/gradebook/grid/components/column_types/teacherNote'
'react-modal'
'helpers/fakeENV'
'jsx/gradebook/grid/constants'
'compiled/gradebook2/GradebookHelpers'
], (React, ReactDOM, {Simulate}, _, TeacherNote, Modal, fakeENV, GradebookConstants, GradebookHelpers) ->
wrapper = document.getElementById('fixtures')
renderComponent = ->
props =
note: 'Great work!'
userId: '1'
studentName: 'Dora Explora'
columnId: '1'
componentFactory = React.createFactory(TeacherNote)
ReactDOM.render(componentFactory(props), wrapper)
module 'ReactGradebook.teacherNoteComponent',
setup: ->
fakeENV.setup()
ENV.GRADEBOOK_OPTIONS =
teacher_notes: { id: '1' }
custom_column_datum_url: 'http://fakeurl.com/api/v1/courses/1/custom_gradebook_columns/:id/data/:user_id'
GradebookConstants.refresh()
Modal.setAppElement(wrapper)
teardown: ->
ReactDOM.unmountComponentAtNode(wrapper)
fakeENV.teardown()
test 'mounts', ->
ok renderComponent().isMounted()
test 'clicking on the cell causes the modal to show up', ->
component = renderComponent()
notOk component.state.showModal
Simulate.click(component.refs.noteCell.getDOMNode())
ok component.state.showModal
test 'hiding the modal sets the content back to the original note', ->
component = renderComponent()
component.setState(showModal: true, content: 'Some fancy new note!')
deepEqual component.state.content, 'Some fancy new note!'
component.hideModal()
deepEqual component.state.content, 'Great work!'
test 'the modal shows the student name if Show Student Names is selected', ->
component = renderComponent()
component.showModal()
notOk component.state.toolbarOptions.hideStudentNames
title = component.refs.studentName.props.children
deepEqual title, 'Notes for Dora Explora'
test 'the modal hides the student name if Hide Student Names is selected', ->
component = renderComponent()
component.setState(showModal: true, toolbarOptions: { hideStudentNames: true })
ok component.state.toolbarOptions.hideStudentNames
title = component.refs.studentName.props.children
deepEqual title, 'Notes for student (name hidden)'
test '#updateContent shows an error message if the new content greater than the max allowed length', ->
component = renderComponent()
flashError = @stub($, 'flashError')
newNote = 'a'.repeat(GradebookConstants.MAX_NOTE_LENGTH + 1)
fakeEvent = { target: { value: newNote } }
component.updateContent(fakeEvent)
ok flashError.called
deepEqual component.state.content, 'Great work!'
test '#updateContent does not show an error message if the new content is exactly the max allowed length', ->
component = renderComponent()
flashError = @stub($, 'flashError')
newNote = 'a'.repeat(GradebookConstants.MAX_NOTE_LENGTH)
fakeEvent = { target: { value: newNote } }
component.updateContent(fakeEvent)
notOk flashError.called
deepEqual component.state.content, newNote
test '#updateContent does not show an error message if the new content is too long, but an error is already showing', ->
component = renderComponent()
flashError = @stub($, 'flashError')
@stub(GradebookHelpers, 'noErrorsOnPage', -> false)
newNote = 'a'.repeat(GradebookConstants.MAX_NOTE_LENGTH + 1)
fakeEvent = { target: { value: newNote } }
component.updateContent(fakeEvent)
ok flashError.notCalled
deepEqual component.state.content, 'Great work!'
test '#handleSubmit makes a PUT request with the new note data', ->
component = renderComponent()
newNote = 'Excellent new note.'
component.setState(content: newNote)
ajaxJSON = @stub($, 'ajaxJSON')
component.handleSubmit()
deepEqual ajaxJSON.args[0][0], 'http://fakeurl.com/api/v1/courses/1/custom_gradebook_columns/1/data/1'
deepEqual ajaxJSON.args[0][1], 'PUT'
deepEqual ajaxJSON.args[0][2]['column_data[content]'], newNote

View File

@ -1,194 +0,0 @@
define [
'underscore'
'react'
'react-dom'
'jsx/gradebook/grid/components/column_types/totalColumn'
'jsx/gradebook/grid/stores/gradingPeriodsStore'
'jsx/gradebook/grid/stores/assignmentGroupsStore'
'jsx/gradebook/grid/constants'
'helpers/fakeENV'
'jquery'
'jquery.ajaxJSON'
], (_, React, ReactDOM, TotalColumn, GradingPeriodsStore, AssignmentGroupsStore, GradebookConstants, fakeENV, $) ->
wrapper = document.getElementById('fixtures')
assignmentGroups = ->
[{ assignments: [{ id: '3', points_possible: 25 }]}]
submissions = ->
[{ assignment_id: '3', id: '6', score: 25 }]
propsWithSubmissions = ->
rowData:
assignmentGroups: [{ assignments: [{ id: '3', points_possible: 25 }]}],
submissions: submissions()
gradingPeriodsData = ->
GRADEBOOK_OPTIONS:
current_grading_period_id: '0'
totalGradeOutput = (component) ->
component.refs.totalGrade.getDOMNode().innerHTML
buildComponent = (props) ->
cellData = props || {cellData: '1', rowData: {submissions: []}}
element = React.createElement(TotalColumn, cellData)
ReactDOM.render(element, wrapper)
firstTestAssignment = (opts) ->
defaultAssignment = { name: 'assignment 1', id: '3', points_possible: 25, submission_types: ['graded'] }
_.defaults(opts || {}, defaultAssignment)
secondTestAssignment = (opts) ->
defaultAssignment = { name: 'assignment 2', id: '7', points_possible: 15, submission_types: ['graded'] }
_.defaults(opts || {}, defaultAssignment)
generateCellData = (assignment1Properties, assignment2Properties, groupProperties) ->
assignmentGroup = _.defaults(groupProperties || {}, { name: 'Group A' })
assignmentGroup.assignments = [
firstTestAssignment(assignment1Properties),
secondTestAssignment(assignment2Properties)
]
rowData:
assignmentGroups: [assignmentGroup]
module 'ReactGradebook.totalColumn',
setup: ->
fakeENV.setup(gradingPeriodsData())
GradebookConstants.refresh()
GradingPeriodsStore.getInitialState()
AssignmentGroupsStore.getInitialState()
AssignmentGroupsStore.onLoadCompleted(assignmentGroups())
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
AssignmentGroupsStore.assignmentGroups = undefined
GradingPeriodsStore.gradingPeriods = undefined
fakeENV.teardown()
test 'mounts on build', ->
ok(buildComponent().isMounted())
test 'displays "-" if there are no submissions yet', ->
component = buildComponent()
deepEqual(totalGradeOutput(component), '-')
test 'Displays a % on a numeric value', ->
props = propsWithSubmissions()
component = buildComponent(props)
deepEqual(totalGradeOutput(component), '100%')
test 'displays warning icon if all assignments combined have 0 points possible', ->
cellData = generateCellData({ points_possible: 0 }, { points_possible: 0 }, { shouldShowNoPointsWarning: false })
component = buildComponent(cellData)
equal component.refs.icon.props.className, 'icon-warning final-warning'
test 'shows grade as points if the user selects "Show As Points"', ->
props = propsWithSubmissions()
component = buildComponent(props)
component.setState({ toolbarOptions: { showTotalGradeAsPoints: true } })
deepEqual(totalGradeOutput(component), '25')
test 'displays "-" if there are no submissions yet with "Show As Points" selected', ->
component = buildComponent()
component.setState({ toolbarOptions: { showTotalGradeAsPoints: true } })
deepEqual(totalGradeOutput(component), '-')
test 'displays mute icon if an assignment is muted', ->
cellData = generateCellData({ muted: true }, { muted: true })
component = buildComponent(cellData)
equal component.refs.icon.props.className, 'icon-muted final-warning'
test 'assignments(): picks all assignments out of assignmentGroups()', ->
cellData = generateCellData()
totalColumn = buildComponent(cellData)
deepEqual(totalColumn.assignments(), [firstTestAssignment(), secondTestAssignment()])
test 'visibleAssignments(): assignments without the not_graded submissions type', ->
cellData = generateCellData(submission_types: ['not_graded'])
totalColumn = buildComponent(cellData)
propEqual(totalColumn.visibleAssignments(), [secondTestAssignment()])
test 'getWarning() for anyMutedAssignments', ->
cellData = generateCellData(muted: true)
totalColumn = buildComponent(cellData)
equal(totalColumn.getWarning(), "This grade differs from the student's view of the grade because some assignments are muted")
test 'getWarning() for group with no points', ->
cellData = generateCellData({ submission_types: ['not_graded'] }, null, { shouldShowNoPointsWarning: true })
groupWeightingSchemeData = ->
GRADEBOOK_OPTIONS:
group_weighting_scheme: 'percent'
fakeENV.setup(groupWeightingSchemeData())
totalColumn = buildComponent(cellData)
equal(totalColumn.getWarning(), 'Score does not include Group A because it has no points possible')
test 'getWarning() for multiple groups with no points', ->
cellData = generateCellData(
{ submission_types: ['not_graded'], points_possible: null },
{ points_possible: null },
{ shouldShowNoPointsWarning: true }
)
groupB =
shouldShowNoPointsWarning: true
name: 'Group B'
assignments: [
{ id: '1', submission_types: ['not_graded']}
{ id: '2', submission_types: ['graded']}
]
cellData.rowData.assignmentGroups.push(groupB)
groupWeightingSchemeData = ->
GRADEBOOK_OPTIONS:
group_weighting_scheme: 'percent'
fakeENV.setup(groupWeightingSchemeData())
totalColumn = buildComponent(cellData)
equal(totalColumn.getWarning(), 'Score does not include Group A and Group B because they have no points possible')
test 'getWarning() for noPointsPossible', ->
cellData = generateCellData({ points_possible: 0 }, { points_possible: 0 })
totalColumn = buildComponent(cellData)
equal(totalColumn.getWarning(), "Can't compute score until an assignment has points possible")
test 'anyMutedAssignments() for one muted assignment', ->
cellData = generateCellData(muted: true)
totalColumn = buildComponent(cellData)
ok(totalColumn.anyMutedAssignments())
test 'anyMutedAssignments() for no muted assignments', ->
cellData = generateCellData()
totalColumn = buildComponent(cellData)
notOk(totalColumn.anyMutedAssignments())
test 'noPointsPossible() for null and 0 points is true', ->
cellData = generateCellData({ points_possible: null }, { points_possible: 0 })
totalColumn = buildComponent(cellData)
ok(totalColumn.noPointsPossible())
test 'noPointsPossible() for greater than 0 points is false', ->
cellData = generateCellData(points_possible: null)
totalColumn = buildComponent(cellData)
notOk(totalColumn.noPointsPossible())
test 'iconClassNames() produces no class names for no muted and has points', ->
cellData = generateCellData()
totalColumn = buildComponent(cellData)
equal(totalColumn.iconClassNames(), '')
test 'iconClassNames() produces class names for muted', ->
cellData = generateCellData(muted: true)
totalColumn = buildComponent(cellData)
equal(totalColumn.iconClassNames(), 'icon-muted final-warning')
test 'iconClassNames() produces class names no points', ->
cellData = generateCellData({ points_possible: 0 }, { points_possible: 0 })
totalColumn = buildComponent(cellData)
equal(totalColumn.iconClassNames(), 'icon-warning final-warning')
test "assignmentGroups() returns rowData's assignmentGroups", ->
cellData = generateCellData()
totalColumn = buildComponent(cellData)
propEqual(totalColumn.assignmentGroups(), [{
name: 'Group A', assignments: [firstTestAssignment(), secondTestAssignment()]
}] )

View File

@ -1,74 +0,0 @@
define [
'react'
'react-dom'
'jsx/gradebook/grid/components/dropdown_components/assignmentHeaderDropdownOptions'
'underscore'
'jsx/gradebook/grid/constants'
'helpers/fakeENV'
], (React, ReactDOM, DropdownOptions, _, GradebookConstants, fakeENV) ->
wrapper = document.getElementById('fixtures')
generateProps = (props) ->
defaultAssignmentAttributes =
id: 1
html_url: 'https://example.instructure.com/courses/1/assignments/1'
submission_types: ['online_upload']
has_submitted_submissions: true
submissions_downloads: 1
speedgrader_url: '/courses/1/gradebook/speed_grader?assignment_id=1'
muted: false
assignmentAttributes = _.defaults(props || {}, defaultAssignmentAttributes)
return {
assignment: assignmentAttributes,
submissions: {},
idAttribute: 'assignmentOptions',
enrollments: []
}
renderComponent = (data) ->
element = React.createElement(DropdownOptions, data)
ReactDOM.render(element, wrapper)
buildComponent = (props) ->
renderComponent(generateProps(props))
module 'AssignmentHeaderDropdownOptions -- speedgrader enabled',
setup: ->
fakeENV.setup({ GRADEBOOK_OPTIONS: { context_id: '1', speed_grader_enabled: true, gradebook_is_editable: true } })
GradebookConstants.refresh()
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
fakeENV.teardown()
test 'includes a "Download Submissions" option if there are downloadable submissions', ->
component = buildComponent()
ok component.refs.downloadSubmissions
test 'does not include a "Download Submissions" option if there no downloadable submisisons', ->
component = buildComponent({ submisison_types: ['none'], has_submitted_submissions: false })
notOk component.refs.downloadSubmissions
test 'includes a "Re-Upload Submissions" option if there is at least one submission download', ->
component = buildComponent()
ok component.refs.reuploadSubmissions
test 'does not include a "Re-Upload Submissions" option if there are no submission downloads', ->
component = buildComponent({ submissions_downloads: 0 })
notOk component.refs.reuploadSubmissions
test 'includes a "Speedgrader" option if speedgrader is enabled', ->
component = buildComponent()
ok component.refs.openSpeedgrader
module 'AssignmentHeaderDropdownOptions -- speedgrader disabled',
setup: ->
fakeENV.setup({ GRADEBOOK_OPTIONS: { context_id: '1', speed_grader_enabled: false } })
GradebookConstants.refresh()
teardown: ->
ReactDOM.unmountComponentAtNode wrapper
fakeENV.teardown()
test 'does not include a "Speedgrader" option if speedgrader is disabled', ->
component = buildComponent()
notOk component.refs.openSpeedgrader

Some files were not shown because too many files have changed in this diff Show More