From f54e1ec1b94288bbe09442ba49dde73327306b53 Mon Sep 17 00:00:00 2001 From: Jeremy Neander Date: Fri, 11 Jan 2019 08:25:10 -0600 Subject: [PATCH] prettify new gradebook refs GRADE-1934 test plan: * Verify Jenkins passes Change-Id: I5d4d69e45955bb2beae61f5a3764e81c1e9d9def Reviewed-on: https://gerrit.instructure.com/177584 Tested-by: Jenkins Reviewed-by: Derek Bender QA-Review: Jeremy Neander Product-Review: Jeremy Neander --- app/jsx/bundles/gradezilla.js | 36 +- app/jsx/gradezilla/DataLoader.js | 87 +- .../SISGradePassback/PostGradesApp.js | 69 +- .../PostGradesDialogCorrectionsPage.js | 3 +- .../PostGradesDialogNeedsGradingPage.js | 3 +- .../SISGradePassback/PostGradesStore.js | 435 +++---- .../SISGradePassback/assignmentUtils.js | 514 +++++---- app/jsx/gradezilla/SubmissionStateMap.js | 139 ++- .../CurveGradesDialogManager.js | 35 +- .../__tests__/GradebookSpecHelper.js | 4 +- .../default_gradebook/apis/GradebookApi.js | 28 +- .../apis/GradebookSettingsModalApi.js | 51 +- .../apis/SubmissionCommentApi.js | 56 +- .../constants/ViewOptions.js | 6 +- .../default_gradebook/constants/colors.js | 14 +- .../default_gradebook/constants/statuses.js | 13 +- .../constants/studentRowHeaderConstants.js | 24 +- .../propTypes/CommentPropTypes.js | 9 +- .../stores/StudentDatastore.js | 62 +- .../DownloadSubmissionsDialogManager.js | 40 +- .../gradezilla/shared/EnterGradesAsSetting.js | 14 +- .../shared/GradebookExportManager.js | 214 ++-- .../ReuploadSubmissionsDialogManager.js | 102 +- .../shared/SetDefaultGradeDialogManager.js | 75 +- .../gradezilla/shared/helpers/TextMeasure.js | 12 +- .../shared/helpers/assignmentHelper.js | 134 ++- .../helpers/messageStudentsWhoHelper.js | 203 ++-- .../gradezilla/uploads/wait_for_processing.js | 27 +- .../gradezilla/PostGradesFrameDialogSpec.js | 11 +- .../SubmissionStateMapGradeVisibilitySpec.js | 240 ++-- ...SubmissionStateMapGradingPeriodInfoSpec.js | 354 +++--- .../SubmissionStateMapLockingSpec.js | 248 ++-- .../jsx/gradezilla/SubmissionStateMapSpec.js | 370 +++--- .../CurveGradesDialogManagerSpec.js | 187 +-- .../GradebookColumnFilteringSpec.js | 1008 ++++++++++------- .../GradebookColumnOrderingSpec.js | 783 +++++++------ .../GradebookColumnWidthSpec.js | 377 +++--- .../apis/GradebookApiSpec.js | 297 ++--- .../apis/GradebookSettingsModalApiSpec.js | 196 ++-- .../apis/SubmissionCommentApiSpec.js | 70 +- .../EnrollmentFilterChangeDataLoadSpec.js | 5 +- .../data-loading/InitialDataLoadSpec.js | 5 +- .../SectionFilterChangeDataLoadSpec.js | 5 +- .../stores/StudentDatastoreSpec.js | 158 +-- .../AssignmentMuterDialogManagerSpec.js | 6 +- .../shared/EnterGradesAsSettingSpec.js | 80 +- .../shared/helpers/TextMeasureSpec.js | 74 +- 47 files changed, 3808 insertions(+), 3075 deletions(-) diff --git a/app/jsx/bundles/gradezilla.js b/app/jsx/bundles/gradezilla.js index 3fb8031a56e..4f4407c5ad7 100644 --- a/app/jsx/bundles/gradezilla.js +++ b/app/jsx/bundles/gradezilla.js @@ -30,7 +30,7 @@ const getGradebookTab = () => UserSettings.contextGet('gradebook_tab') const setGradebookTab = view => UserSettings.contextSet('gradebook_tab', view) class GradebookRouter extends Backbone.Router { - static initClass () { + static initClass() { this.prototype.routes = { '': 'tab', 'tab-assignment': 'tabAssignment', @@ -39,13 +39,13 @@ class GradebookRouter extends Backbone.Router { } } - initialize () { + initialize() { this.isLoaded = false this.views = {} - ENV.GRADEBOOK_OPTIONS.assignmentOrOutcome = getGradebookTab(); - ENV.GRADEBOOK_OPTIONS.navigate = this.navigate.bind(this); - this.views.assignment = new Gradebook(this.gradebookOptions()); - this.views.assignment.initialize(); + ENV.GRADEBOOK_OPTIONS.assignmentOrOutcome = getGradebookTab() + ENV.GRADEBOOK_OPTIONS.navigate = this.navigate.bind(this) + this.views.assignment = new Gradebook(this.gradebookOptions()) + this.views.assignment.initialize() if (ENV.GRADEBOOK_OPTIONS.outcome_gradebook_enabled) { this.views.outcome = this.initOutcomes() this.renderPagination(0, 0) @@ -54,15 +54,15 @@ class GradebookRouter extends Backbone.Router { return this } - gradebookOptions () { + gradebookOptions() { return { ...ENV.GRADEBOOK_OPTIONS, locale: ENV.LOCALE, currentUserId: ENV.current_user_id - }; + } } - initOutcomes () { + initOutcomes() { const book = new OutcomeGradebookView({ el: $('.outcome-gradebook'), gradebook: this.views.assignment, @@ -74,12 +74,16 @@ class GradebookRouter extends Backbone.Router { renderPagination(page, pageCount) { ReactDOM.render( - this.views.outcome.loadPage(p)} />, - document.getElementById("outcome-gradebook-paginator") + this.views.outcome.loadPage(p)} + />, + document.getElementById('outcome-gradebook-paginator') ) } - tabOutcome () { + tabOutcome() { window.tab = 'outcome' $('.assignment-gradebook-container').addClass('hidden') $('.outcome-gradebook-container > div').removeClass('hidden') @@ -87,7 +91,7 @@ class GradebookRouter extends Backbone.Router { return setGradebookTab('outcome') } - tabAssignment () { + tabAssignment() { window.tab = 'assignment' $('.outcome-gradebook-container > div').addClass('hidden') $('.assignment-gradebook-container').removeClass('hidden') @@ -95,10 +99,10 @@ class GradebookRouter extends Backbone.Router { return setGradebookTab('assignment') } - tab () { + tab() { let view = getGradebookTab() window.tab = view - if ((view !== 'outcome') || !this.views.outcome) { + if (view !== 'outcome' || !this.views.outcome) { view = 'assignment' } $('.assignment-gradebook-container, .outcome-gradebook-container > div').addClass('hidden') @@ -107,7 +111,7 @@ class GradebookRouter extends Backbone.Router { this.views[view].onShow() return setGradebookTab(view) } - } +} GradebookRouter.initClass() new GradebookRouter() diff --git a/app/jsx/gradezilla/DataLoader.js b/app/jsx/gradezilla/DataLoader.js index 9fdab21e2fe..dec308b5ffa 100644 --- a/app/jsx/gradezilla/DataLoader.js +++ b/app/jsx/gradezilla/DataLoader.js @@ -20,71 +20,78 @@ import $ from 'jquery' import cheaterDepaginate from '../shared/CheatDepaginator' import StudentContentDataLoader from './default_gradebook/DataLoader/StudentContentDataLoader' -function getStudentIds (courseId) { - const url = `/courses/${courseId}/gradebook/user_ids`; - return $.ajaxJSON(url, 'GET', {}); +function getStudentIds(courseId) { + const url = `/courses/${courseId}/gradebook/user_ids` + return $.ajaxJSON(url, 'GET', {}) } -function getGradingPeriodAssignments (courseId) { - const url = `/courses/${courseId}/gradebook/grading_period_assignments`; - return $.ajaxJSON(url, 'GET', {}); +function getGradingPeriodAssignments(courseId) { + const url = `/courses/${courseId}/gradebook/grading_period_assignments` + return $.ajaxJSON(url, 'GET', {}) } -function getAssignmentGroups (url, params) { - return cheaterDepaginate(url, params); +function getAssignmentGroups(url, params) { + return cheaterDepaginate(url, params) } -function getContextModules (url) { - return cheaterDepaginate(url); +function getContextModules(url) { + return cheaterDepaginate(url) } -function getCustomColumns (url) { - return cheaterDepaginate(url, { include_hidden: true }); +function getCustomColumns(url) { + return cheaterDepaginate(url, {include_hidden: true}) } -function getDataForColumn (columnId, url, params, cb) { - const columnUrl = url.replace(/:id/, columnId); - const augmentedCallback = data => cb(columnId, data); - return cheaterDepaginate(columnUrl, params, augmentedCallback); +function getDataForColumn(columnId, url, params, cb) { + const columnUrl = url.replace(/:id/, columnId) + const augmentedCallback = data => cb(columnId, data) + return cheaterDepaginate(columnUrl, params, augmentedCallback) } -function getCustomColumnData (options, customColumnsDfd, waitForDfds) { - const url = options.customColumnDataURL; - const params = options.customColumnDataParams; - const cb = options.customColumnDataPageCb; - const customColumnDataLoaded = $.Deferred(); +function getCustomColumnData(options, customColumnsDfd, waitForDfds) { + const url = options.customColumnDataURL + const params = options.customColumnDataParams + const cb = options.customColumnDataPageCb + const customColumnDataLoaded = $.Deferred() if (url) { // waitForDfds ensures that custom column data is loaded *last* $.when(...waitForDfds).then(() => { if (options.customColumnIds) { - const customColumnDataDfds = options.customColumnIds.map(columnId => getDataForColumn(columnId, url, params, cb)); - $.when(...customColumnDataDfds).then(() => customColumnDataLoaded.resolve()); + const customColumnDataDfds = options.customColumnIds.map(columnId => + getDataForColumn(columnId, url, params, cb) + ) + $.when(...customColumnDataDfds).then(() => customColumnDataLoaded.resolve()) } else { - customColumnsDfd.then((customColumns) => { - const customColumnDataDfds = customColumns.map(col => getDataForColumn(col.id, url, params, cb)); - $.when(...customColumnDataDfds).then(() => customColumnDataLoaded.resolve()); - }); + customColumnsDfd.then(customColumns => { + const customColumnDataDfds = customColumns.map(col => + getDataForColumn(col.id, url, params, cb) + ) + $.when(...customColumnDataDfds).then(() => customColumnDataLoaded.resolve()) + }) } - }); + }) } - return customColumnDataLoaded; + return customColumnDataLoaded } -function loadGradebookData (opts) { - const gotAssignmentGroups = getAssignmentGroups(opts.assignmentGroupsURL, opts.assignmentGroupsParams); +function loadGradebookData(opts) { + const gotAssignmentGroups = getAssignmentGroups( + opts.assignmentGroupsURL, + opts.assignmentGroupsParams + ) if (opts.onlyLoadAssignmentGroups) { - return { gotAssignmentGroups }; + return {gotAssignmentGroups} } // Begin loading Students before any other data. - const gotStudentIds = getStudentIds(opts.courseId); - let gotGradingPeriodAssignments; + const gotStudentIds = getStudentIds(opts.courseId) + let gotGradingPeriodAssignments if (opts.getGradingPeriodAssignments) { - gotGradingPeriodAssignments = getGradingPeriodAssignments(opts.courseId); + gotGradingPeriodAssignments = getGradingPeriodAssignments(opts.courseId) } - const gotCustomColumns = getCustomColumns(opts.customColumnsURL); + const gotCustomColumns = getCustomColumns(opts.customColumnsURL) const studentContentDataLoader = new StudentContentDataLoader({ courseId: opts.courseId, @@ -100,7 +107,7 @@ function loadGradebookData (opts) { submissionsUrl: opts.submissionsURL }) - const gotContextModules = getContextModules(opts.contextModulesURL); + const gotContextModules = getContextModules(opts.contextModulesURL) const gotStudents = $.Deferred() const gotSubmissions = $.Deferred() @@ -112,7 +119,7 @@ function loadGradebookData (opts) { }) // Custom Column Data will load only after custom columns and all submissions. - const gotCustomColumnData = getCustomColumnData(opts, gotCustomColumns, [gotSubmissions]); + const gotCustomColumnData = getCustomColumnData(opts, gotCustomColumns, [gotSubmissions]) return { gotAssignmentGroups, @@ -123,10 +130,10 @@ function loadGradebookData (opts) { gotStudents, gotSubmissions, gotCustomColumnData - }; + } } export default { getDataForColumn, loadGradebookData -}; +} diff --git a/app/jsx/gradezilla/SISGradePassback/PostGradesApp.js b/app/jsx/gradezilla/SISGradePassback/PostGradesApp.js index 7b60dcedd58..6ab7627a7b7 100644 --- a/app/jsx/gradezilla/SISGradePassback/PostGradesApp.js +++ b/app/jsx/gradezilla/SISGradePassback/PostGradesApp.js @@ -17,7 +17,7 @@ */ import React from 'react' -import { bool, func, shape, string } from 'prop-types' +import {bool, func, shape, string} from 'prop-types' import ReactDOM from 'react-dom' import $ from 'jquery' import I18n from 'i18n!modules' @@ -31,19 +31,19 @@ class PostGradesApp extends React.Component { labelText: string.isRequired, store: shape({ addChangeListener: func.isRequired, - removeChangeListener: func.isRequired, + removeChangeListener: func.isRequired }).isRequired, renderAsButton: bool, returnFocusTo: shape({ focus: func.isRequired }).isRequired - }; + } static defaultProps = { renderAsButton: false - }; + } - static AppLaunch (store, returnFocusTo) { + static AppLaunch(store, returnFocusTo) { const $dialog = $('
').dialog({ title: I18n.t('Sync Grades to SIS'), maxWidth: 650, @@ -54,58 +54,55 @@ class PostGradesApp extends React.Component { height: 450, resizable: false, buttons: [], - close () { - ReactDOM.unmountComponentAtNode(this); - $(this).remove(); + close() { + ReactDOM.unmountComponentAtNode(this) + $(this).remove() if (returnFocusTo) { - returnFocusTo.focus(); + returnFocusTo.focus() } } - }); + }) - function closeDialog (e) { - e.preventDefault(); - $dialog.dialog('close'); + function closeDialog(e) { + e.preventDefault() + $dialog.dialog('close') } store.reset() - ReactDOM.render(, $dialog[0]); + ReactDOM.render(, $dialog[0]) } - componentDidMount () { + componentDidMount() { this.boundForceUpdate = this.forceUpdate.bind(this) this.props.store.addChangeListener(this.boundForceUpdate) } - componentWillUnmount () { this.props.store.removeChangeListener(this.boundForceUpdate) } - - openDialog (e) { - e.preventDefault(); - - PostGradesApp.AppLaunch(this.props.store, this.props.returnFocusTo); + componentWillUnmount() { + this.props.store.removeChangeListener(this.boundForceUpdate) } - render () { + openDialog(e) { + e.preventDefault() + + PostGradesApp.AppLaunch(this.props.store, this.props.returnFocusTo) + } + + render() { const navClass = classnames({ 'ui-button': this.props.renderAsButton - }); + }) if (this.props.renderAsButton) { return ( - - ); + + ) } else { return ( - {this.props.labelText} - ); + + {this.props.labelText} + + ) } } } diff --git a/app/jsx/gradezilla/SISGradePassback/PostGradesDialogCorrectionsPage.js b/app/jsx/gradezilla/SISGradePassback/PostGradesDialogCorrectionsPage.js index 22a8fa68070..fc4531197f0 100644 --- a/app/jsx/gradezilla/SISGradePassback/PostGradesDialogCorrectionsPage.js +++ b/app/jsx/gradezilla/SISGradePassback/PostGradesDialogCorrectionsPage.js @@ -135,7 +135,8 @@ class PostGradesDialogCorrectionsPage extends React.Component { onClick={this.ignoreErrorsThenProceed} > {errorCount > 0 ? I18n.t('Ignore These') : I18n.t('Continue')} -   +   +
diff --git a/app/jsx/gradezilla/SISGradePassback/PostGradesDialogNeedsGradingPage.js b/app/jsx/gradezilla/SISGradePassback/PostGradesDialogNeedsGradingPage.js index 9b942ca14c1..46256ef58a0 100644 --- a/app/jsx/gradezilla/SISGradePassback/PostGradesDialogNeedsGradingPage.js +++ b/app/jsx/gradezilla/SISGradePassback/PostGradesDialogNeedsGradingPage.js @@ -63,7 +63,8 @@ class PostGradesDialogNeedsGradingPage extends React.Component { className="btn btn-primary" onClick={this.props.leaveNeedsGradingPage} > - {I18n.t('Continue')}  + {I18n.t('Continue')}  + diff --git a/app/jsx/gradezilla/SISGradePassback/PostGradesStore.js b/app/jsx/gradezilla/SISGradePassback/PostGradesStore.js index a943bbed323..b94da863bf4 100644 --- a/app/jsx/gradezilla/SISGradePassback/PostGradesStore.js +++ b/app/jsx/gradezilla/SISGradePassback/PostGradesStore.js @@ -21,222 +21,243 @@ import _ from 'underscore' import createStore from '../../shared/helpers/createStore' import assignmentUtils from '../../gradezilla/SISGradePassback/assignmentUtils' - var PostGradesStore = (state) => { - var store = $.extend(createStore(state), { +var PostGradesStore = state => { + var store = $.extend(createStore(state), { + reset() { + var assignments = this.getAssignments() + _.each(assignments, a => (a.please_ignore = false)) + this.setState({ + assignments: assignments, + pleaseShowNeedsGradingPage: false + }) + }, - reset () { - var assignments = this.getAssignments() - _.each(assignments, (a) => a.please_ignore = false) - this.setState({ - assignments: assignments, - pleaseShowNeedsGradingPage: false - }) - }, + hasAssignments() { + var assignments = this.getAssignments() + if (assignments != undefined && assignments.length > 0) { + return true + } else { + return false + } + }, - hasAssignments () { - var assignments = this.getAssignments() - if (assignments != undefined && assignments.length > 0) { - return true - } else { - return false - } - }, + getSISSectionId(section_id) { + var sections = this.getState().sections + return sections && sections[section_id] ? sections[section_id].sis_section_id : null + }, - getSISSectionId (section_id) { - var sections = this.getState().sections - return (sections && sections[section_id]) ? - sections[section_id].sis_section_id : - null; - }, + allOverrideIds(a) { + var overrides = [] + _.each(a.overrides, o => { + overrides.push(o.course_section_id) + }) + return overrides + }, - allOverrideIds(a) { - var overrides = [] - _.each(a.overrides, (o) => { - overrides.push(o.course_section_id) - }) - return overrides - }, + overrideForEveryone(a) { + var overrides = this.allOverrideIds(a) + var sections = _.keys(this.getState().sections) + var section_ids_with_no_overrides = $(sections) + .not(overrides) + .get() - overrideForEveryone(a) { - var overrides = this.allOverrideIds(a) - var sections = _.keys(this.getState().sections) - var section_ids_with_no_overrides = $(sections).not(overrides).get(); + var section_for_everyone = _.find(section_ids_with_no_overrides, o => { + return state.selected.id == o + }) + return section_for_everyone + }, - var section_for_everyone = _.find(section_ids_with_no_overrides, (o) => { - return state.selected.id == o - }); - return section_for_everyone - }, + selectedSISId() { + return this.getState().selected.sis_id + }, - selectedSISId () { - return this.getState().selected.sis_id - }, - - setGradeBookAssignments (gradebookAssignments) { - var assignments = [] - for (var id in gradebookAssignments) { - var gba = gradebookAssignments[id] - // Only accept assignments suitable to post, e.g. published, post_to_sis - if (assignmentUtils.suitableToPost(gba)) { - // Push a copy, and only of relevant attributes - assignments.push(assignmentUtils.copyFromGradebook(gba)) - } - } - // A second loop is needed to ensure non-unique name errors are included - // in hasError - _.each(assignments, (a) => { - a.original_error = assignmentUtils.hasError(assignments, a) - }) - this.setState({ assignments: assignments }) - }, - - setSections (sections) { - this.setState({ sections: sections }) - this.setSelectedSection( this.getState().sectionToShow ) - }, - - validCheck(a) { - if(a.overrideForThisSection != undefined && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id){ - return a.due_at != null ? true : false - } - else if(a.overrideForThisSection != undefined && a.currentlySelected.type == 'section' && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id){ - return a.overrideForThisSection.due_at != null ? true : false - } - else{ - return true - } - }, - - getAssignments() { - var assignments = this.getState().assignments - var state = this.getState() - if (state.selected.type == "section") { - _.each(assignments, (a) => { - a.recentlyUpdated = false - a.currentlySelected = state.selected - a.sectionCount = _.keys(state.sections).length - a.overrideForThisSection = _.find(a.overrides, (override) => { - return override.course_section_id == state.selected.id; - }); - - //Handle assignment with overrides and the 'Everyone Else' scenario with a section that does not have any overrides - //cleanup overrideForThisSection logic - if(a.overrideForThisSection == undefined){ a.selectedSectionForEveryone = this.overrideForEveryone(a) } - }); - } else { - _.each(assignments, (a) => { - a.recentlyUpdated = false - a.currentlySelected = state.selected - a.sectionCount = _.keys(state.sections).length - - //Course is currentlySlected with sections that have overrides AND are invalid - a.overrideForThisSection = _.find(a.overrides, (override) => { - return override.due_at == null || typeof(override.due_at) == 'object'; - }); - - //Handle assignment with overrides and the 'Everyone Else' scenario with the course currentlySelected - if(a.overrideForThisSection == undefined){ a.selectedSectionForEveryone = this.overrideForEveryone(a) } - }); - } - return assignments; - }, - - getAssignment(assignment_id) { - var assignments = this.getAssignments() - return _.find(assignments, (a) => a.id == assignment_id) - }, - - setSelectedSection (section) { - var state = this.getState() - var section_id = parseInt(section) - var selected; - if (section) { - selected = { - type: "section", - id: section_id, - sis_id: this.getSISSectionId(section_id) - }; - } else { - selected = { - type: "course", - id: state.course.id, - sis_id: state.course.sis_id - }; - } - - this.setState({ selected: selected, sectionToShow: section }) - }, - - updateAssignment (assignment_id, newAttrs) { - var assignments = this.getAssignments() - var assignment = _.find(assignments, (a) => a.id == assignment_id) - $.extend(assignment, newAttrs) - this.setState({assignments: assignments}) - }, - - updateAssignmentDate(assignment_id, date){ - var assignments = this.getState().assignments - var assignment = _.find(assignments, (a) => a.id == assignment_id) - //the assignment has an override and the override being updated is for the section that is currentlySelected update it - if(assignment.overrideForThisSection != undefined && assignment.currentlySelected.id.toString() == assignment.overrideForThisSection.course_section_id) { - assignment.overrideForThisSection.due_at = date - assignment.please_ignore = false - assignment.hadOriginalErrors = true - - this.setState({assignments: assignments}) - } - - //the section override being set from the course level of the sction dropdown - else if(assignment.overrideForThisSection != undefined && assignment.currentlySelected.id.toString() != assignment.overrideForThisSection.course_section_id){ - assignment.overrideForThisSection.due_at = date - assignment.please_ignore = false - assignment.hadOriginalErrors = true - - this.setState({assignments: assignments}) - } - - //update normal assignment and the 'Everyone Else' scenario if the course is currentlySelected - else { - this.updateAssignment(assignment_id, {due_at: date, please_ignore: false, hadOriginalErrors: true}) - } - }, - - assignmentOverrideOrigianlErrorCheck(a) { - a.hadOriginalErrors = a.hadOriginalErrors == true - }, - - saveAssignments () { - var assignments = assignmentUtils.withOriginalErrorsNotIgnored(this.getAssignments()) - var course_id = this.getState().course.id - _.each(assignments, (a) => { - this.assignmentOverrideOrigianlErrorCheck(a) - assignmentUtils.saveAssignmentToCanvas(course_id, a) - }); - }, - - postGrades() { - var assignments = assignmentUtils.notIgnored(this.getAssignments()) - var selected = this.getState().selected - assignmentUtils.postGradesThroughCanvas(selected, assignments) - }, - - getPage () { - var state = this.getState() - if (state.pleaseShowNeedsGradingPage) { - return "needsGrading" - } else { - var originals = assignmentUtils.withOriginalErrors(this.getAssignments()) - var withErrorsCount = _.keys(assignmentUtils.withErrors(this.getAssignments())).length - if (withErrorsCount == 0 && (state.pleaseShowSummaryPage || originals.length == 0)) { - return "summary" - } else { - return "corrections" - } + setGradeBookAssignments(gradebookAssignments) { + var assignments = [] + for (var id in gradebookAssignments) { + var gba = gradebookAssignments[id] + // Only accept assignments suitable to post, e.g. published, post_to_sis + if (assignmentUtils.suitableToPost(gba)) { + // Push a copy, and only of relevant attributes + assignments.push(assignmentUtils.copyFromGradebook(gba)) } } - }) + // A second loop is needed to ensure non-unique name errors are included + // in hasError + _.each(assignments, a => { + a.original_error = assignmentUtils.hasError(assignments, a) + }) + this.setState({assignments: assignments}) + }, - return store - }; + setSections(sections) { + this.setState({sections: sections}) + this.setSelectedSection(this.getState().sectionToShow) + }, + + validCheck(a) { + if ( + a.overrideForThisSection != undefined && + a.currentlySelected.type == 'course' && + a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id + ) { + return a.due_at != null ? true : false + } else if ( + a.overrideForThisSection != undefined && + a.currentlySelected.type == 'section' && + a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id + ) { + return a.overrideForThisSection.due_at != null ? true : false + } else { + return true + } + }, + + getAssignments() { + var assignments = this.getState().assignments + var state = this.getState() + if (state.selected.type == 'section') { + _.each(assignments, a => { + a.recentlyUpdated = false + a.currentlySelected = state.selected + a.sectionCount = _.keys(state.sections).length + a.overrideForThisSection = _.find(a.overrides, override => { + return override.course_section_id == state.selected.id + }) + + //Handle assignment with overrides and the 'Everyone Else' scenario with a section that does not have any overrides + //cleanup overrideForThisSection logic + if (a.overrideForThisSection == undefined) { + a.selectedSectionForEveryone = this.overrideForEveryone(a) + } + }) + } else { + _.each(assignments, a => { + a.recentlyUpdated = false + a.currentlySelected = state.selected + a.sectionCount = _.keys(state.sections).length + + //Course is currentlySlected with sections that have overrides AND are invalid + a.overrideForThisSection = _.find(a.overrides, override => { + return override.due_at == null || typeof override.due_at == 'object' + }) + + //Handle assignment with overrides and the 'Everyone Else' scenario with the course currentlySelected + if (a.overrideForThisSection == undefined) { + a.selectedSectionForEveryone = this.overrideForEveryone(a) + } + }) + } + return assignments + }, + + getAssignment(assignment_id) { + var assignments = this.getAssignments() + return _.find(assignments, a => a.id == assignment_id) + }, + + setSelectedSection(section) { + var state = this.getState() + var section_id = parseInt(section) + var selected + if (section) { + selected = { + type: 'section', + id: section_id, + sis_id: this.getSISSectionId(section_id) + } + } else { + selected = { + type: 'course', + id: state.course.id, + sis_id: state.course.sis_id + } + } + + this.setState({selected: selected, sectionToShow: section}) + }, + + updateAssignment(assignment_id, newAttrs) { + var assignments = this.getAssignments() + var assignment = _.find(assignments, a => a.id == assignment_id) + $.extend(assignment, newAttrs) + this.setState({assignments: assignments}) + }, + + updateAssignmentDate(assignment_id, date) { + var assignments = this.getState().assignments + var assignment = _.find(assignments, a => a.id == assignment_id) + //the assignment has an override and the override being updated is for the section that is currentlySelected update it + if ( + assignment.overrideForThisSection != undefined && + assignment.currentlySelected.id.toString() == + assignment.overrideForThisSection.course_section_id + ) { + assignment.overrideForThisSection.due_at = date + assignment.please_ignore = false + assignment.hadOriginalErrors = true + + this.setState({assignments: assignments}) + } + + //the section override being set from the course level of the sction dropdown + else if ( + assignment.overrideForThisSection != undefined && + assignment.currentlySelected.id.toString() != + assignment.overrideForThisSection.course_section_id + ) { + assignment.overrideForThisSection.due_at = date + assignment.please_ignore = false + assignment.hadOriginalErrors = true + + this.setState({assignments: assignments}) + } + + //update normal assignment and the 'Everyone Else' scenario if the course is currentlySelected + else { + this.updateAssignment(assignment_id, { + due_at: date, + please_ignore: false, + hadOriginalErrors: true + }) + } + }, + + assignmentOverrideOrigianlErrorCheck(a) { + a.hadOriginalErrors = a.hadOriginalErrors == true + }, + + saveAssignments() { + var assignments = assignmentUtils.withOriginalErrorsNotIgnored(this.getAssignments()) + var course_id = this.getState().course.id + _.each(assignments, a => { + this.assignmentOverrideOrigianlErrorCheck(a) + assignmentUtils.saveAssignmentToCanvas(course_id, a) + }) + }, + + postGrades() { + var assignments = assignmentUtils.notIgnored(this.getAssignments()) + var selected = this.getState().selected + assignmentUtils.postGradesThroughCanvas(selected, assignments) + }, + + getPage() { + var state = this.getState() + if (state.pleaseShowNeedsGradingPage) { + return 'needsGrading' + } else { + var originals = assignmentUtils.withOriginalErrors(this.getAssignments()) + var withErrorsCount = _.keys(assignmentUtils.withErrors(this.getAssignments())).length + if (withErrorsCount == 0 && (state.pleaseShowSummaryPage || originals.length == 0)) { + return 'summary' + } else { + return 'corrections' + } + } + } + }) + + return store +} export default PostGradesStore diff --git a/app/jsx/gradezilla/SISGradePassback/assignmentUtils.js b/app/jsx/gradezilla/SISGradePassback/assignmentUtils.js index 8aa4e2bb6c5..3fac731c92f 100644 --- a/app/jsx/gradezilla/SISGradePassback/assignmentUtils.js +++ b/app/jsx/gradezilla/SISGradePassback/assignmentUtils.js @@ -20,230 +20,330 @@ import $ from 'jquery' import _ from 'underscore' import '../../shared/helpers/createStore' - let assignmentUtils = { - copyFromGradebook (assignment) { - var a = _.pick(assignment, [ - "id", - "name", - "due_at", - "needs_grading_count", - "overrides" - ]) - a.please_ignore = false - a.original_error = false - return a - }, +let assignmentUtils = { + copyFromGradebook(assignment) { + var a = _.pick(assignment, ['id', 'name', 'due_at', 'needs_grading_count', 'overrides']) + a.please_ignore = false + a.original_error = false + return a + }, - namesMatch (a, b) { - return a.name === b.name && a !== b - }, + namesMatch(a, b) { + return a.name === b.name && a !== b + }, - nameTooLong (a) { - if (_.unescape(a.name).length > 30){ - return true - } - else{ - return false - } - }, - - nameEmpty (a) { - if (a.name.length == 0){ - return true - } - else{ - return false - } - }, - - notUniqueName (assignments, a) { - return assignments.some(_.partial(assignmentUtils.namesMatch, a)) - }, - - noDueDateForEveryoneElseOverride(a) { - var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false - if(has_overrides && a.overrides.length != a.sectionCount && !a.due_at){ - return true - } else { - return false - } - }, - - withOriginalErrors (assignments) { - // This logic handles an assignment with multiple overrides - // because #setGradeBookAssignments runs on load - // it does not have a reference to what the currently viewed section is. - // Due to this an assignment with 2 overrides (one valid, one invalid) - // it will set original_errors to true. This logic checks the override - // being viewed for that section. If the override is valid make - // original error false so that the override is not shown. Vice versa - // for the invalid override on the assignment. - _.each(assignments, (a) => { - if(a.overrideForThisSection != undefined && a.recentlyUpdated != undefined && a.recentlyUpdated == true && a.overrideForThisSection.due_at != null){a.original_error = false} - else if(a.overrideForThisSection != undefined && a.recentlyUpdated != undefined && a.recentlyUpdated == false && a.overrideForThisSection.due_at == null){a.original_error = true} - //for handling original error detection of a valid override for one section and an invalid override for another section - else if(a.overrideForThisSection != undefined && a.overrideForThisSection.due_at != null && !assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.recentlyUpdated == false && a.hadOriginalErrors == false){a.original_error = false} - //for handling original error detection of a valid override for one section and the EveryoneElse "override" scenario - else if(a.overrideForThisSection != undefined && a.overrideForThisSection.due_at != null && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id && a.recentlyUpdated == false && a.hadOriginalErrors == false){a.original_error = false} - //for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS NOT valid - else if(a.overrideForThisSection == undefined && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.due_at == null && a.currentlySelected.id.toString() == a.selectedSectionForEveryone){a.original_error = true} - //for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS valid - else if(a.overrideForThisSection == undefined && a.due_at != null && a.currentlySelected.id.toString() == a.selectedSectionForEveryone && a.hadOriginalErrors == false){a.original_error = false} - //for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS NOT valid - else if(a.overrideForThisSection == undefined && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.due_at == null && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone){a.original_error = true} - //for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS valid - else if(a.overrideForThisSection == undefined && a.due_at != null && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone && a.hadOriginalErrors == false){a.original_error = false} - }); - return _.filter(assignments, (a) => a.original_error && !a.please_ignore) - }, - - withOriginalErrorsNotIgnored (assignments) { - return _.filter(assignments, function(a){ return (a.original_error || a.hadOriginalErrors) && !a.please_ignore}) - }, - - withErrors (assignments) { - return _.filter(assignments, (a) => assignmentUtils.hasError(assignments, a)) - }, - - notIgnored (assignments) { - return _.filter(assignments, (a) => !a.please_ignore) - }, - - needsGrading (assignments) { - return _.filter(assignments, (a) => a.needs_grading_count > 0) - }, - - hasError (assignments, a) { - ////Decided to ignore - if(a.please_ignore) return false - - ////Not unique - if(assignmentUtils.notUniqueName(assignments, a)) return true - - ////Name too long - if(assignmentUtils.nameTooLong(a)) return true - - ////Name empty - if(assignmentUtils.nameEmpty(a)) return true - - ////Non-override missing due_at - var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false - if(!has_overrides && !a.due_at) return true - - ////Override missing due_at - var has_this_override = a.overrideForThisSection != undefined - if(has_this_override && a.overrideForThisSection.due_at == null && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id) return true - - ////Override missing due_at while currentlySelecteed is at the course level - if(has_this_override && a.overrideForThisSection.due_at == null && a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id) return true - - ////Has one override and another override for 'Everyone Else' - //// - ////The override for 'Everyone Else' isn't really an override and references - ////the assignments actual due_at. So we must check for this behavior - if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.currentlySelected != undefined && a.overrideForThisSection != undefined && a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id) return true - - ////Has only one override but the section that is currently selected does not have an override thus causing the assignment to have due_at that is null making it invalid - if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.overrideForThisSection == undefined && a.currentlySelected != undefined && a.currentlySelected.id.toString() == a.selectedSectionForEveryone) return true - - ////'Everyone Else' scenario and the course is currentlySelected but due_at is null making it invalid - if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.overrideForThisSection == undefined && a.currentlySelected != undefined && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone) return true - - ////Passes all tests, looks good. + nameTooLong(a) { + if (_.unescape(a.name).length > 30) { + return true + } else { return false - }, + } + }, - suitableToPost(assignment) { - return assignment.published && assignment.post_to_sis - }, + nameEmpty(a) { + if (a.name.length == 0) { + return true + } else { + return false + } + }, - saveAssignmentToCanvas (course_id, assignment) { - // if the date on an override is being updated confirm by checking if the due_at is an object - if(assignment.overrideForThisSection != undefined && typeof(assignment.overrideForThisSection.due_at) == "object") { - //allows the validation process to determine when it has been updated and can display the correct page - assignment.hadOriginalErrors = false - var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id + '/overrides/' + assignment.overrideForThisSection.id - //sets up form data to allow a single override to be updated - var fd = new FormData(); - fd.append( 'assignment_override[due_at]', assignment.overrideForThisSection.due_at.toISOString() ) + notUniqueName(assignments, a) { + return assignments.some(_.partial(assignmentUtils.namesMatch, a)) + }, - $.ajax(url, { - type: 'PUT', - data: fd, - processData: false, - contentType: false, - error: (err) => { - var msg = 'An error occurred saving assignment override, (' + assignment.overrideForThisSection.id + '). ' - msg += "HTTP Error " + data.status + " : " + data.statusText - $.flashError(msg) - } - }) - // if there is a naming conflict on the assignment that has an override with a date - // that was just set AND the naming conflict is fixed we must also update the assignment - // to mock natural behavior to the user so that the naming conflict does not appear again - url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id - data = { assignment: { - name: assignment.name, - due_at: assignment.due_at - }} - $.ajax(url, { - type: 'PUT', - data: JSON.stringify(data), - contentType: 'application/json; charset=utf-8', - error: (err) => { - var msg = 'An error occurred saving assignment (' + assignment.id + '). ' - msg += "HTTP Error " + data.status + " : " + data.statusText - $.flashError(msg) - } - }) + noDueDateForEveryoneElseOverride(a) { + var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false + if (has_overrides && a.overrides.length != a.sectionCount && !a.due_at) { + return true + } else { + return false + } + }, + + withOriginalErrors(assignments) { + // This logic handles an assignment with multiple overrides + // because #setGradeBookAssignments runs on load + // it does not have a reference to what the currently viewed section is. + // Due to this an assignment with 2 overrides (one valid, one invalid) + // it will set original_errors to true. This logic checks the override + // being viewed for that section. If the override is valid make + // original error false so that the override is not shown. Vice versa + // for the invalid override on the assignment. + _.each(assignments, a => { + if ( + a.overrideForThisSection != undefined && + a.recentlyUpdated != undefined && + a.recentlyUpdated == true && + a.overrideForThisSection.due_at != null + ) { + a.original_error = false + } else if ( + a.overrideForThisSection != undefined && + a.recentlyUpdated != undefined && + a.recentlyUpdated == false && + a.overrideForThisSection.due_at == null + ) { + a.original_error = true } - else { - //allows the validation process to determine when it has been updated and can display the correct page - assignment.hadOriginalErrors = false - var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id - var data = { assignment: { - name: assignment.name, - due_at: assignment.due_at - }} - $.ajax(url, { - type: 'PUT', - data: JSON.stringify(data), - contentType: 'application/json; charset=utf-8', - error: (err) => { - var msg = 'An error occurred saving assignment (' + assignment.id + '). ' - msg += "HTTP Error " + data.status + " : " + data.statusText - $.flashError(msg) - } - }) + //for handling original error detection of a valid override for one section and an invalid override for another section + else if ( + a.overrideForThisSection != undefined && + a.overrideForThisSection.due_at != null && + !assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.recentlyUpdated == false && + a.hadOriginalErrors == false + ) { + a.original_error = false } + //for handling original error detection of a valid override for one section and the EveryoneElse "override" scenario + else if ( + a.overrideForThisSection != undefined && + a.overrideForThisSection.due_at != null && + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id && + a.recentlyUpdated == false && + a.hadOriginalErrors == false + ) { + a.original_error = false + } + //for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS NOT valid + else if ( + a.overrideForThisSection == undefined && + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.due_at == null && + a.currentlySelected.id.toString() == a.selectedSectionForEveryone + ) { + a.original_error = true + } + //for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS valid + else if ( + a.overrideForThisSection == undefined && + a.due_at != null && + a.currentlySelected.id.toString() == a.selectedSectionForEveryone && + a.hadOriginalErrors == false + ) { + a.original_error = false + } + //for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS NOT valid + else if ( + a.overrideForThisSection == undefined && + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.due_at == null && + a.currentlySelected.type == 'course' && + a.currentlySelected.id.toString() != a.selectedSectionForEveryone + ) { + a.original_error = true + } + //for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS valid + else if ( + a.overrideForThisSection == undefined && + a.due_at != null && + a.currentlySelected.type == 'course' && + a.currentlySelected.id.toString() != a.selectedSectionForEveryone && + a.hadOriginalErrors == false + ) { + a.original_error = false + } + }) + return _.filter(assignments, a => a.original_error && !a.please_ignore) + }, - }, + withOriginalErrorsNotIgnored(assignments) { + return _.filter(assignments, function(a) { + return (a.original_error || a.hadOriginalErrors) && !a.please_ignore + }) + }, + + withErrors(assignments) { + return _.filter(assignments, a => assignmentUtils.hasError(assignments, a)) + }, + + notIgnored(assignments) { + return _.filter(assignments, a => !a.please_ignore) + }, + + needsGrading(assignments) { + return _.filter(assignments, a => a.needs_grading_count > 0) + }, + + hasError(assignments, a) { + ////Decided to ignore + if (a.please_ignore) return false + + ////Not unique + if (assignmentUtils.notUniqueName(assignments, a)) return true + + ////Name too long + if (assignmentUtils.nameTooLong(a)) return true + + ////Name empty + if (assignmentUtils.nameEmpty(a)) return true + + ////Non-override missing due_at + var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false + if (!has_overrides && !a.due_at) return true + + ////Override missing due_at + var has_this_override = a.overrideForThisSection != undefined + if ( + has_this_override && + a.overrideForThisSection.due_at == null && + a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id + ) + return true + + ////Override missing due_at while currentlySelecteed is at the course level + if ( + has_this_override && + a.overrideForThisSection.due_at == null && + a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id + ) + return true + + ////Has one override and another override for 'Everyone Else' + //// + ////The override for 'Everyone Else' isn't really an override and references + ////the assignments actual due_at. So we must check for this behavior + if ( + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.currentlySelected != undefined && + a.overrideForThisSection != undefined && + a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id + ) + return true + + ////Has only one override but the section that is currently selected does not have an override thus causing the assignment to have due_at that is null making it invalid + if ( + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.overrideForThisSection == undefined && + a.currentlySelected != undefined && + a.currentlySelected.id.toString() == a.selectedSectionForEveryone + ) + return true + + ////'Everyone Else' scenario and the course is currentlySelected but due_at is null making it invalid + if ( + assignmentUtils.noDueDateForEveryoneElseOverride(a) && + a.overrideForThisSection == undefined && + a.currentlySelected != undefined && + a.currentlySelected.type == 'course' && + a.currentlySelected.id.toString() != a.selectedSectionForEveryone + ) + return true + + ////Passes all tests, looks good. + return false + }, + + suitableToPost(assignment) { + return assignment.published && assignment.post_to_sis + }, + + saveAssignmentToCanvas(course_id, assignment) { + // if the date on an override is being updated confirm by checking if the due_at is an object + if ( + assignment.overrideForThisSection != undefined && + typeof assignment.overrideForThisSection.due_at == 'object' + ) { + //allows the validation process to determine when it has been updated and can display the correct page + assignment.hadOriginalErrors = false + var url = + '/api/v1/courses/' + + course_id + + '/assignments/' + + assignment.id + + '/overrides/' + + assignment.overrideForThisSection.id + //sets up form data to allow a single override to be updated + var fd = new FormData() + fd.append( + 'assignment_override[due_at]', + assignment.overrideForThisSection.due_at.toISOString() + ) - // Sends a post-grades request to Canvas that is then forwarded to SIS App. - // Expects a list of assignments that will later be queried for grades via - // SIS App's workers - postGradesThroughCanvas (selected, assignments) { - var url = "/api/v1/" + selected.type + "s/" + selected.id + "/post_grades/" - var data = { assignments: _.map(assignments, (assignment) => assignment.id) } $.ajax(url, { - type: 'POST', + type: 'PUT', + data: fd, + processData: false, + contentType: false, + error: err => { + var msg = + 'An error occurred saving assignment override, (' + + assignment.overrideForThisSection.id + + '). ' + msg += 'HTTP Error ' + data.status + ' : ' + data.statusText + $.flashError(msg) + } + }) + // if there is a naming conflict on the assignment that has an override with a date + // that was just set AND the naming conflict is fixed we must also update the assignment + // to mock natural behavior to the user so that the naming conflict does not appear again + url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id + data = { + assignment: { + name: assignment.name, + due_at: assignment.due_at + } + } + $.ajax(url, { + type: 'PUT', data: JSON.stringify(data), contentType: 'application/json; charset=utf-8', - success: (msg) =>{ - if (msg.error){ - $.flashError(msg.error) - }else{ - $.flashMessage(msg.message) - } - }, - error: (err) => { - var msg = 'An error occurred posting grades for (' + selected.type + ' : ' + selected.id +'). ' - msg += "HTTP Error " + data.status + " : " + data.statusText + error: err => { + var msg = 'An error occurred saving assignment (' + assignment.id + '). ' + msg += 'HTTP Error ' + data.status + ' : ' + data.statusText + $.flashError(msg) + } + }) + } else { + //allows the validation process to determine when it has been updated and can display the correct page + assignment.hadOriginalErrors = false + var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id + var data = { + assignment: { + name: assignment.name, + due_at: assignment.due_at + } + } + $.ajax(url, { + type: 'PUT', + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + error: err => { + var msg = 'An error occurred saving assignment (' + assignment.id + '). ' + msg += 'HTTP Error ' + data.status + ' : ' + data.statusText $.flashError(msg) } }) } + }, - }; + // Sends a post-grades request to Canvas that is then forwarded to SIS App. + // Expects a list of assignments that will later be queried for grades via + // SIS App's workers + postGradesThroughCanvas(selected, assignments) { + var url = '/api/v1/' + selected.type + 's/' + selected.id + '/post_grades/' + var data = {assignments: _.map(assignments, assignment => assignment.id)} + $.ajax(url, { + type: 'POST', + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + success: msg => { + if (msg.error) { + $.flashError(msg.error) + } else { + $.flashMessage(msg.message) + } + }, + error: err => { + var msg = + 'An error occurred posting grades for (' + selected.type + ' : ' + selected.id + '). ' + msg += 'HTTP Error ' + data.status + ' : ' + data.statusText + $.flashError(msg) + } + }) + } +} export default assignmentUtils diff --git a/app/jsx/gradezilla/SubmissionStateMap.js b/app/jsx/gradezilla/SubmissionStateMap.js index 97da9d69b76..3d262448853 100644 --- a/app/jsx/gradezilla/SubmissionStateMap.js +++ b/app/jsx/gradezilla/SubmissionStateMap.js @@ -16,70 +16,92 @@ * with this program. If not, see . */ -import _ from 'underscore'; -import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper'; +import _ from 'underscore' +import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper' -function submissionGradingPeriodInformation (assignment, student) { - const submissionInfo = assignment.effectiveDueDates[student.id] || {}; +function submissionGradingPeriodInformation(assignment, student) { + const submissionInfo = assignment.effectiveDueDates[student.id] || {} return { gradingPeriodID: submissionInfo.grading_period_id, inClosedGradingPeriod: submissionInfo.in_closed_grading_period - }; + } } -function hiddenFromStudent (assignment, student) { +function hiddenFromStudent(assignment, student) { if (assignment.only_visible_to_overrides) { - return !_.contains(assignment.assignment_visibility, student.id); + return !_.contains(assignment.assignment_visibility, student.id) } return false } -function gradingPeriodInfoForCell (assignment, student, selectedGradingPeriodID) { - const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID); - const { gradingPeriodID, inClosedGradingPeriod } = submissionGradingPeriodInformation(assignment, student); - const inNoGradingPeriod = !gradingPeriodID; - const inOtherGradingPeriod = !!gradingPeriodID && specificPeriodSelected && - selectedGradingPeriodID !== gradingPeriodID; +function gradingPeriodInfoForCell(assignment, student, selectedGradingPeriodID) { + const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID) + const {gradingPeriodID, inClosedGradingPeriod} = submissionGradingPeriodInformation( + assignment, + student + ) + const inNoGradingPeriod = !gradingPeriodID + const inOtherGradingPeriod = + !!gradingPeriodID && specificPeriodSelected && selectedGradingPeriodID !== gradingPeriodID return { inNoGradingPeriod, inOtherGradingPeriod, inClosedGradingPeriod - }; + } } -function cellMappingsForMultipleGradingPeriods (assignment, student, selectedGradingPeriodID, isAdmin) { - const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID); - const { gradingPeriodID, inClosedGradingPeriod } = submissionGradingPeriodInformation(assignment, student); - const gradingPeriodInfo = gradingPeriodInfoForCell(assignment, student, selectedGradingPeriodID); - let cellMapping; +function cellMappingsForMultipleGradingPeriods( + assignment, + student, + selectedGradingPeriodID, + isAdmin +) { + const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID) + const {gradingPeriodID, inClosedGradingPeriod} = submissionGradingPeriodInformation( + assignment, + student + ) + const gradingPeriodInfo = gradingPeriodInfoForCell(assignment, student, selectedGradingPeriodID) + let cellMapping if (specificPeriodSelected && (!gradingPeriodID || selectedGradingPeriodID !== gradingPeriodID)) { - cellMapping = { locked: true, hideGrade: true }; + cellMapping = {locked: true, hideGrade: true} } else if (!isAdmin && inClosedGradingPeriod) { - cellMapping = { locked: true, hideGrade: false }; + cellMapping = {locked: true, hideGrade: false} } else { - cellMapping = { locked: false, hideGrade: false }; + cellMapping = {locked: false, hideGrade: false} } - return { ...cellMapping, ...gradingPeriodInfo }; + return {...cellMapping, ...gradingPeriodInfo} } -function cellMapForSubmission (assignment, student, hasGradingPeriods, selectedGradingPeriodID, isAdmin) { +function cellMapForSubmission( + assignment, + student, + hasGradingPeriods, + selectedGradingPeriodID, + isAdmin +) { if (!assignment.published || assignment.anonymize_students) { - return { locked: true, hideGrade: true }; + return {locked: true, hideGrade: true} } else if (assignment.moderated_grading && !assignment.grades_published) { - return { locked: true, hideGrade: false }; + return {locked: true, hideGrade: false} } else if (hiddenFromStudent(assignment, student)) { - return { locked: true, hideGrade: true }; + return {locked: true, hideGrade: true} } else if (hasGradingPeriods) { - return cellMappingsForMultipleGradingPeriods(assignment, student, selectedGradingPeriodID, isAdmin); + return cellMappingsForMultipleGradingPeriods( + assignment, + student, + selectedGradingPeriodID, + isAdmin + ) } else { - return { locked: false, hideGrade: false }; + return {locked: false, hideGrade: false} } } -function missingSubmission (student, assignment) { +function missingSubmission(student, assignment) { const submission = { assignment_id: assignment.id, user_id: student.id, @@ -87,53 +109,54 @@ function missingSubmission (student, assignment) { late: false, missing: false, seconds_late: 0 - }; - const dueDates = assignment.effectiveDueDates[student.id] || {}; - if (dueDates.due_at != null && new Date(dueDates.due_at) < new Date()) { - submission.missing = true; } - return submission; + const dueDates = assignment.effectiveDueDates[student.id] || {} + if (dueDates.due_at != null && new Date(dueDates.due_at) < new Date()) { + submission.missing = true + } + return submission } class SubmissionStateMap { - constructor ({ hasGradingPeriods, selectedGradingPeriodID, isAdmin }) { - this.hasGradingPeriods = hasGradingPeriods; - this.selectedGradingPeriodID = selectedGradingPeriodID; - this.isAdmin = isAdmin; - this.submissionCellMap = {}; - this.submissionMap = {}; + constructor({hasGradingPeriods, selectedGradingPeriodID, isAdmin}) { + this.hasGradingPeriods = hasGradingPeriods + this.selectedGradingPeriodID = selectedGradingPeriodID + this.isAdmin = isAdmin + this.submissionCellMap = {} + this.submissionMap = {} } - setup (students, assignments) { - students.forEach((student) => { - this.submissionCellMap[student.id] = {}; - this.submissionMap[student.id] = {}; - _.each(assignments, (assignment) => { - this.setSubmissionCellState(student, assignment, student[`assignment_${assignment.id}`]); - }); - }); + setup(students, assignments) { + students.forEach(student => { + this.submissionCellMap[student.id] = {} + this.submissionMap[student.id] = {} + _.each(assignments, assignment => { + this.setSubmissionCellState(student, assignment, student[`assignment_${assignment.id}`]) + }) + }) } - setSubmissionCellState (student, assignment, submission) { - this.submissionMap[student.id][assignment.id] = submission || missingSubmission(student, assignment); + setSubmissionCellState(student, assignment, submission) { + this.submissionMap[student.id][assignment.id] = + submission || missingSubmission(student, assignment) const params = [ assignment, student, this.hasGradingPeriods, this.selectedGradingPeriodID, this.isAdmin - ]; + ] - this.submissionCellMap[student.id][assignment.id] = cellMapForSubmission(...params); + this.submissionCellMap[student.id][assignment.id] = cellMapForSubmission(...params) } - getSubmission (userId, assignmentId) { - return (this.submissionMap[userId] || {})[assignmentId]; + getSubmission(userId, assignmentId) { + return (this.submissionMap[userId] || {})[assignmentId] } - getSubmissionState ({ user_id: userId, assignment_id: assignmentId }) { - return (this.submissionCellMap[userId] || {})[assignmentId]; + getSubmissionState({user_id: userId, assignment_id: assignmentId}) { + return (this.submissionCellMap[userId] || {})[assignmentId] } } -export default SubmissionStateMap; +export default SubmissionStateMap diff --git a/app/jsx/gradezilla/default_gradebook/CurveGradesDialogManager.js b/app/jsx/gradezilla/default_gradebook/CurveGradesDialogManager.js index 1bafa49ba9a..2fd982e2c31 100644 --- a/app/jsx/gradezilla/default_gradebook/CurveGradesDialogManager.js +++ b/app/jsx/gradezilla/default_gradebook/CurveGradesDialogManager.js @@ -21,21 +21,30 @@ import CurveGradesDialog from 'compiled/shared/CurveGradesDialog' import I18n from 'i18n!gradebook' import 'compiled/jquery.rails_flash_notifications' - const CurveGradesDialogManager = { - createCurveGradesAction (assignment, students, {isAdmin, contextUrl, submissionsLoaded} = {}) { - const { grading_type: gradingType, points_possible: pointsPossible } = assignment; - return { - isDisabled: !submissionsLoaded || gradingType === 'pass_fail' || pointsPossible == null || pointsPossible === 0, +const CurveGradesDialogManager = { + createCurveGradesAction(assignment, students, {isAdmin, contextUrl, submissionsLoaded} = {}) { + const {grading_type: gradingType, points_possible: pointsPossible} = assignment + return { + isDisabled: + !submissionsLoaded || + gradingType === 'pass_fail' || + pointsPossible == null || + pointsPossible === 0, - onSelect (onClose) { // eslint-disable-line consistent-return - if (!isAdmin && assignment.inClosedGradingPeriod) { - return $.flashError(I18n.t('Unable to curve grades because this assignment is due in a closed ' + - 'grading period for at least one student')); - } - const dialog = new CurveGradesDialog({assignment, students, context_url: contextUrl}); - dialog.show(onClose); + onSelect(onClose) { + // eslint-disable-line consistent-return + if (!isAdmin && assignment.inClosedGradingPeriod) { + return $.flashError( + I18n.t( + 'Unable to curve grades because this assignment is due in a closed ' + + 'grading period for at least one student' + ) + ) } - }; + const dialog = new CurveGradesDialog({assignment, students, context_url: contextUrl}) + dialog.show(onClose) + } } } +} export default CurveGradesDialogManager diff --git a/app/jsx/gradezilla/default_gradebook/__tests__/GradebookSpecHelper.js b/app/jsx/gradezilla/default_gradebook/__tests__/GradebookSpecHelper.js index 956090beed1..88733534ccb 100644 --- a/app/jsx/gradezilla/default_gradebook/__tests__/GradebookSpecHelper.js +++ b/app/jsx/gradezilla/default_gradebook/__tests__/GradebookSpecHelper.js @@ -77,7 +77,7 @@ export function createGradebook(options = {}) { } export function setFixtureHtml($fixture) { - return $fixture.innerHTML = ` + return ($fixture.innerHTML = `
@@ -98,7 +98,7 @@ export function setFixtureHtml($fixture) {
- ` + `) } export function stubDataLoader() { diff --git a/app/jsx/gradezilla/default_gradebook/apis/GradebookApi.js b/app/jsx/gradezilla/default_gradebook/apis/GradebookApi.js index ff3ffef9570..cfa96da3c90 100644 --- a/app/jsx/gradezilla/default_gradebook/apis/GradebookApi.js +++ b/app/jsx/gradezilla/default_gradebook/apis/GradebookApi.js @@ -16,34 +16,34 @@ * with this program. If not, see . */ -import axios from 'axios'; -import I18n from 'i18n!gradebook'; -import { underscore } from 'convert_case'; +import axios from 'axios' +import I18n from 'i18n!gradebook' +import {underscore} from 'convert_case' -function createTeacherNotesColumn (courseId) { - const url = `/api/v1/courses/${courseId}/custom_gradebook_columns`; +function createTeacherNotesColumn(courseId) { + const url = `/api/v1/courses/${courseId}/custom_gradebook_columns` const data = { column: { position: 1, teacher_notes: true, title: I18n.t('Notes') } - }; - return axios.post(url, data); + } + return axios.post(url, data) } -function updateTeacherNotesColumn (courseId, columnId, attr) { - const url = `/api/v1/courses/${courseId}/custom_gradebook_columns/${columnId}`; - return axios.put(url, { column: attr }); +function updateTeacherNotesColumn(courseId, columnId, attr) { + const url = `/api/v1/courses/${courseId}/custom_gradebook_columns/${columnId}` + return axios.put(url, {column: attr}) } -function updateSubmission (courseId, assignmentId, userId, submission) { - const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`; - return axios.put(url, { submission: underscore(submission), include: ['visibility'] }); +function updateSubmission(courseId, assignmentId, userId, submission) { + const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}` + return axios.put(url, {submission: underscore(submission), include: ['visibility']}) } export default { createTeacherNotesColumn, updateTeacherNotesColumn, updateSubmission -}; +} diff --git a/app/jsx/gradezilla/default_gradebook/apis/GradebookSettingsModalApi.js b/app/jsx/gradezilla/default_gradebook/apis/GradebookSettingsModalApi.js index b8de95c0ff6..60344b9fc6f 100644 --- a/app/jsx/gradezilla/default_gradebook/apis/GradebookSettingsModalApi.js +++ b/app/jsx/gradezilla/default_gradebook/apis/GradebookSettingsModalApi.js @@ -16,8 +16,8 @@ * with this program. If not, see . */ -import axios from 'axios'; -import { camelize, underscore } from 'convert_case'; +import axios from 'axios' +import {camelize, underscore} from 'convert_case' export const DEFAULT_LATE_POLICY_DATA = Object.freeze({ lateSubmissionDeductionEnabled: false, @@ -28,40 +28,39 @@ export const DEFAULT_LATE_POLICY_DATA = Object.freeze({ missingSubmissionDeductionEnabled: false, missingSubmissionDeduction: 0, newRecord: true -}); +}) -function camelizeLatePolicyResponseData (latePolicyResponseData) { - const camelizedData = camelize(latePolicyResponseData.late_policy); - return { latePolicy: camelizedData }; +function camelizeLatePolicyResponseData(latePolicyResponseData) { + const camelizedData = camelize(latePolicyResponseData.late_policy) + return {latePolicy: camelizedData} } -export function fetchLatePolicy (courseId) { - const url = `/api/v1/courses/${courseId}/late_policy`; - return axios.get(url) - .then(response => ( - { data: camelizeLatePolicyResponseData(response.data) } - )) - .catch((error) => { +export function fetchLatePolicy(courseId) { + const url = `/api/v1/courses/${courseId}/late_policy` + return axios + .get(url) + .then(response => ({data: camelizeLatePolicyResponseData(response.data)})) + .catch(error => { // if we get a 404 then we know the course does not // currently have a late policy set up if (error.response && error.response.status === 404) { - return Promise.resolve({ data: { latePolicy: DEFAULT_LATE_POLICY_DATA } }); + return Promise.resolve({data: {latePolicy: DEFAULT_LATE_POLICY_DATA}}) } else { - return Promise.reject(error); + return Promise.reject(error) } - }); + }) } -export function createLatePolicy (courseId, latePolicyData) { - const url = `/api/v1/courses/${courseId}/late_policy`; - const data = { late_policy: underscore(latePolicyData) }; - return axios.post(url, data).then(response => ( - { data: camelizeLatePolicyResponseData(response.data) } - )); +export function createLatePolicy(courseId, latePolicyData) { + const url = `/api/v1/courses/${courseId}/late_policy` + const data = {late_policy: underscore(latePolicyData)} + return axios + .post(url, data) + .then(response => ({data: camelizeLatePolicyResponseData(response.data)})) } -export function updateLatePolicy (courseId, latePolicyData) { - const url = `/api/v1/courses/${courseId}/late_policy`; - const data = { late_policy: underscore(latePolicyData) }; - return axios.patch(url, data); +export function updateLatePolicy(courseId, latePolicyData) { + const url = `/api/v1/courses/${courseId}/late_policy` + const data = {late_policy: underscore(latePolicyData)} + return axios.patch(url, data) } diff --git a/app/jsx/gradezilla/default_gradebook/apis/SubmissionCommentApi.js b/app/jsx/gradezilla/default_gradebook/apis/SubmissionCommentApi.js index 4baed73acd1..e7a3b9c7ad7 100644 --- a/app/jsx/gradezilla/default_gradebook/apis/SubmissionCommentApi.js +++ b/app/jsx/gradezilla/default_gradebook/apis/SubmissionCommentApi.js @@ -16,19 +16,19 @@ * with this program. If not, see . */ -import axios from 'axios'; -import timezone from 'timezone'; +import axios from 'axios' +import timezone from 'timezone' -function deserializeComment (comment) { +function deserializeComment(comment) { const baseComment = { id: comment.id, createdAt: timezone.parse(comment.created_at), comment: comment.comment, editedAt: comment.edited_at && timezone.parse(comment.edited_at) - }; + } if (!comment.author) { - return baseComment; + return baseComment } return { @@ -37,36 +37,38 @@ function deserializeComment (comment) { author: comment.author.display_name, authorAvatarUrl: comment.author.avatar_image_url, authorUrl: comment.author.html_url - }; + } } -function deserializeComments (comments) { - return comments.map(deserializeComment); +function deserializeComments(comments) { + return comments.map(deserializeComment) } -export function getSubmissionComments (courseId, assignmentId, studentId) { - const commentOptions = { params: { include: 'submission_comments' } }; - const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${studentId}`; - return axios.get(url, commentOptions) - .then(response => deserializeComments(response.data.submission_comments)); +export function getSubmissionComments(courseId, assignmentId, studentId) { + const commentOptions = {params: {include: 'submission_comments'}} + const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${studentId}` + return axios + .get(url, commentOptions) + .then(response => deserializeComments(response.data.submission_comments)) } -export function createSubmissionComment (courseId, assignmentId, studentId, comment) { - const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${studentId}`; - const data = { group_comment: 0, comment: { text_comment: comment } }; - return axios.put(url, data) - .then(response => deserializeComments(response.data.submission_comments)); +export function createSubmissionComment(courseId, assignmentId, studentId, comment) { + const url = `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${studentId}` + const data = {group_comment: 0, comment: {text_comment: comment}} + return axios + .put(url, data) + .then(response => deserializeComments(response.data.submission_comments)) } -export function deleteSubmissionComment (commentId) { - const url = `/submission_comments/${commentId}`; - return axios.delete(url); +export function deleteSubmissionComment(commentId) { + const url = `/submission_comments/${commentId}` + return axios.delete(url) } -export function updateSubmissionComment (commentId, comment) { - const url = `/submission_comments/${commentId}`; - const data = { id: commentId, submission_comment: { comment } }; - return axios.put(url, data).then(response => ( - { data: deserializeComment(response.data.submission_comment) } - )); +export function updateSubmissionComment(commentId, comment) { + const url = `/submission_comments/${commentId}` + const data = {id: commentId, submission_comment: {comment}} + return axios + .put(url, data) + .then(response => ({data: deserializeComment(response.data.submission_comment)})) } diff --git a/app/jsx/gradezilla/default_gradebook/constants/ViewOptions.js b/app/jsx/gradezilla/default_gradebook/constants/ViewOptions.js index eefedbd006a..7dd9d7c9fcd 100644 --- a/app/jsx/gradezilla/default_gradebook/constants/ViewOptions.js +++ b/app/jsx/gradezilla/default_gradebook/constants/ViewOptions.js @@ -16,15 +16,15 @@ * with this program. If not, see . */ -import I18n from 'i18n!gradebook'; +import I18n from 'i18n!gradebook' export const filterLabels = { assignmentGroups: I18n.t('Assignment Groups'), gradingPeriods: I18n.t('Grading Periods'), modules: I18n.t('Modules'), sections: I18n.t('Sections') -}; +} export default { filterLabels -}; +} diff --git a/app/jsx/gradezilla/default_gradebook/constants/colors.js b/app/jsx/gradezilla/default_gradebook/constants/colors.js index 2179d235865..081449865c2 100644 --- a/app/jsx/gradezilla/default_gradebook/constants/colors.js +++ b/app/jsx/gradezilla/default_gradebook/constants/colors.js @@ -16,7 +16,7 @@ * with this program. If not, see . */ -import Color from 'tinycolor2'; +import Color from 'tinycolor2' export const defaultColors = { salmon: '#FFE8E5', @@ -29,7 +29,7 @@ export const defaultColors = { pink: '#F8EAF6', lavender: '#F0E8EF', white: '#FFFFFF' -}; +} const defaultStatusColors = { dropped: defaultColors.orange, @@ -37,15 +37,15 @@ const defaultStatusColors = { late: defaultColors.blue, missing: defaultColors.salmon, resubmitted: defaultColors.green -}; +} -export function statusColors (userColors = {}) { +export function statusColors(userColors = {}) { return { ...defaultStatusColors, ...userColors - }; + } } -export function darken (color, percent) { - return Color(color).darken(percent); +export function darken(color, percent) { + return Color(color).darken(percent) } diff --git a/app/jsx/gradezilla/default_gradebook/constants/statuses.js b/app/jsx/gradezilla/default_gradebook/constants/statuses.js index 37e69a4a3df..08ff9a5a1e6 100644 --- a/app/jsx/gradezilla/default_gradebook/constants/statuses.js +++ b/app/jsx/gradezilla/default_gradebook/constants/statuses.js @@ -16,15 +16,9 @@ * with this program. If not, see . */ -import I18n from 'i18n!gradebook'; +import I18n from 'i18n!gradebook' -export const statuses = [ - 'late', - 'missing', - 'resubmitted', - 'dropped', - 'excused' -]; +export const statuses = ['late', 'missing', 'resubmitted', 'dropped', 'excused'] export const statusesTitleMap = { late: I18n.t('Late'), @@ -32,5 +26,4 @@ export const statusesTitleMap = { resubmitted: I18n.t('Resubmitted'), dropped: I18n.t('Dropped'), excused: I18n.t('Excused') -}; - +} diff --git a/app/jsx/gradezilla/default_gradebook/constants/studentRowHeaderConstants.js b/app/jsx/gradezilla/default_gradebook/constants/studentRowHeaderConstants.js index cbc25dbc26c..23a9bbb333d 100644 --- a/app/jsx/gradezilla/default_gradebook/constants/studentRowHeaderConstants.js +++ b/app/jsx/gradezilla/default_gradebook/constants/studentRowHeaderConstants.js @@ -16,15 +16,15 @@ * with this program. If not, see . */ -import I18n from 'i18n!gradebook'; +import I18n from 'i18n!gradebook' const primaryInfoLabels = { first_last: I18n.t('First, Last Name'), - last_first: I18n.t('Last, First Name'), -}; + last_first: I18n.t('Last, First Name') +} -const primaryInfoKeys = ['first_last', 'last_first']; -const defaultPrimaryInfo = 'first_last'; +const primaryInfoKeys = ['first_last', 'last_first'] +const defaultPrimaryInfo = 'first_last' const secondaryInfoLabels = { section: I18n.t('Section'), @@ -32,18 +32,18 @@ const secondaryInfoLabels = { integration_id: I18n.t('Integration ID'), login_id: I18n.t('Login ID'), none: I18n.t('None') -}; +} -const secondaryInfoKeys = ['section', 'sis_id', 'integration_id', 'login_id', 'none']; -const defaultSecondaryInfo = 'none'; -const sectionSecondaryInfo = 'section'; +const secondaryInfoKeys = ['section', 'sis_id', 'integration_id', 'login_id', 'none'] +const defaultSecondaryInfo = 'none' +const sectionSecondaryInfo = 'section' const enrollmentFilterLabels = { inactive: I18n.t('Inactive enrollments'), concluded: I18n.t('Concluded enrollments') -}; +} -const enrollmentFilterKeys = ['inactive', 'concluded']; +const enrollmentFilterKeys = ['inactive', 'concluded'] export default { primaryInfoKeys, @@ -55,4 +55,4 @@ export default { sectionSecondaryInfo, enrollmentFilterKeys, enrollmentFilterLabels -}; +} diff --git a/app/jsx/gradezilla/default_gradebook/propTypes/CommentPropTypes.js b/app/jsx/gradezilla/default_gradebook/propTypes/CommentPropTypes.js index 236eec957be..95ec78e0fdc 100644 --- a/app/jsx/gradezilla/default_gradebook/propTypes/CommentPropTypes.js +++ b/app/jsx/gradezilla/default_gradebook/propTypes/CommentPropTypes.js @@ -16,10 +16,7 @@ * with this program. If not, see . */ -import { - instanceOf, - string -} from 'prop-types'; +import {instanceOf, string} from 'prop-types' const SubmissionTrayCommentPropTypes = { id: string.isRequired, @@ -29,6 +26,6 @@ const SubmissionTrayCommentPropTypes = { createdAt: instanceOf(Date).isRequired, comment: string.isRequired, editedAt: instanceOf(Date) -}; +} -export default SubmissionTrayCommentPropTypes; +export default SubmissionTrayCommentPropTypes diff --git a/app/jsx/gradezilla/default_gradebook/stores/StudentDatastore.js b/app/jsx/gradezilla/default_gradebook/stores/StudentDatastore.js index d7afc11c441..c60dd7b2ee4 100644 --- a/app/jsx/gradezilla/default_gradebook/stores/StudentDatastore.js +++ b/app/jsx/gradezilla/default_gradebook/stores/StudentDatastore.js @@ -16,9 +16,9 @@ * with this program. If not, see . */ -import _ from 'lodash'; +import _ from 'lodash' -function createStudentPlaceholder (id) { +function createStudentPlaceholder(id) { return { enrollments: [], id, @@ -27,48 +27,48 @@ function createStudentPlaceholder (id) { isPlaceholder: true, loaded: false, sections: [] - }; + } } export default class StudentDatastore { - studentIds = []; + studentIds = [] - constructor (userStudentMap, testStudentMap) { - this.userStudentMap = userStudentMap; - this.testStudentMap = testStudentMap; + constructor(userStudentMap, testStudentMap) { + this.userStudentMap = userStudentMap + this.testStudentMap = testStudentMap } - listStudentIds () { - return this.studentIds; + listStudentIds() { + return this.studentIds } - setStudentIds (studentIds) { - this.studentIds = studentIds; - const idsOfStoredStudents = Object.keys(this.userStudentMap); - _.difference(idsOfStoredStudents, studentIds).forEach((removedStudentId) => { - delete this.userStudentMap[removedStudentId]; - }); - const idsOfStoredTestStudents = Object.keys(this.testStudentMap); - _.difference(idsOfStoredTestStudents, studentIds).forEach((removedStudentId) => { - delete this.testStudentMap[removedStudentId]; - }); + setStudentIds(studentIds) { + this.studentIds = studentIds + const idsOfStoredStudents = Object.keys(this.userStudentMap) + _.difference(idsOfStoredStudents, studentIds).forEach(removedStudentId => { + delete this.userStudentMap[removedStudentId] + }) + const idsOfStoredTestStudents = Object.keys(this.testStudentMap) + _.difference(idsOfStoredTestStudents, studentIds).forEach(removedStudentId => { + delete this.testStudentMap[removedStudentId] + }) } - addUserStudents (students) { - students.forEach((student) => { - this.userStudentMap[student.id] = student; - }); + addUserStudents(students) { + students.forEach(student => { + this.userStudentMap[student.id] = student + }) } - addTestStudents (students) { - students.forEach((student) => { - this.testStudentMap[student.id] = student; - }); + addTestStudents(students) { + students.forEach(student => { + this.testStudentMap[student.id] = student + }) } - listStudents () { - return this.studentIds.map(id => ( - this.userStudentMap[id] || this.testStudentMap[id] || createStudentPlaceholder(id) - )); + listStudents() { + return this.studentIds.map( + id => this.userStudentMap[id] || this.testStudentMap[id] || createStudentPlaceholder(id) + ) } } diff --git a/app/jsx/gradezilla/shared/DownloadSubmissionsDialogManager.js b/app/jsx/gradezilla/shared/DownloadSubmissionsDialogManager.js index bace5d969b0..c6389c5913b 100644 --- a/app/jsx/gradezilla/shared/DownloadSubmissionsDialogManager.js +++ b/app/jsx/gradezilla/shared/DownloadSubmissionsDialogManager.js @@ -20,25 +20,27 @@ import INST from 'INST' import $ from 'jquery' import 'jquery.instructure_misc_helpers' - class DownloadSubmissionsDialogManager { - constructor (assignment, downloadUrlTemplate, submissionsDownloading) { - this.assignment = assignment; - this.downloadUrl = $.replaceTags(downloadUrlTemplate, 'assignment_id', assignment.id); - this.showDialog = this.showDialog.bind(this); - this.validSubmissionTypes = ['online_upload', 'online_text_entry', 'online_url']; - this.submissionsDownloading = submissionsDownloading; - } - - isDialogEnabled () { - return this.assignment.submission_types && this.assignment.submission_types.some( - t => this.validSubmissionTypes.includes(t) - ) && this.assignment.has_submitted_submissions; - } - - showDialog (cb) { - this.submissionsDownloading(this.assignment.id); - INST.downloadSubmissions(this.downloadUrl, cb); - } +class DownloadSubmissionsDialogManager { + constructor(assignment, downloadUrlTemplate, submissionsDownloading) { + this.assignment = assignment + this.downloadUrl = $.replaceTags(downloadUrlTemplate, 'assignment_id', assignment.id) + this.showDialog = this.showDialog.bind(this) + this.validSubmissionTypes = ['online_upload', 'online_text_entry', 'online_url'] + this.submissionsDownloading = submissionsDownloading } + isDialogEnabled() { + return ( + this.assignment.submission_types && + this.assignment.submission_types.some(t => this.validSubmissionTypes.includes(t)) && + this.assignment.has_submitted_submissions + ) + } + + showDialog(cb) { + this.submissionsDownloading(this.assignment.id) + INST.downloadSubmissions(this.downloadUrl, cb) + } +} + export default DownloadSubmissionsDialogManager diff --git a/app/jsx/gradezilla/shared/EnterGradesAsSetting.js b/app/jsx/gradezilla/shared/EnterGradesAsSetting.js index b9898e4a072..d20787cc969 100644 --- a/app/jsx/gradezilla/shared/EnterGradesAsSetting.js +++ b/app/jsx/gradezilla/shared/EnterGradesAsSetting.js @@ -22,7 +22,7 @@ const gradingTypeOptionMap = { pass_fail: ['passFail'], percent: ['points', 'percent'], points: ['points', 'percent'] -}; +} const gradingTypeDefaultOptionMap = { gpa_scale: 'gradingScheme', @@ -30,12 +30,12 @@ const gradingTypeDefaultOptionMap = { pass_fail: 'passFail', percent: 'percent', points: 'points' -}; - -export function defaultOptionForGradingType (gradingType) { - return gradingTypeDefaultOptionMap[gradingType] || null; } -export function optionsForGradingType (gradingType) { - return gradingTypeOptionMap[gradingType] || []; +export function defaultOptionForGradingType(gradingType) { + return gradingTypeDefaultOptionMap[gradingType] || null +} + +export function optionsForGradingType(gradingType) { + return gradingTypeOptionMap[gradingType] || [] } diff --git a/app/jsx/gradezilla/shared/GradebookExportManager.js b/app/jsx/gradezilla/shared/GradebookExportManager.js index 0625155dd4e..61716fd7f5f 100644 --- a/app/jsx/gradezilla/shared/GradebookExportManager.js +++ b/app/jsx/gradezilla/shared/GradebookExportManager.js @@ -19,117 +19,127 @@ import axios from 'axios' import I18n from 'i18n!gradebook' - class GradebookExportManager { - static DEFAULT_POLLING_INTERVAL = 2000; - static DEFAULT_MONITORING_BASE_URL = '/api/v1/progress'; - static DEFAULT_ATTACHMENT_BASE_URL = '/api/v1/users'; +class GradebookExportManager { + static DEFAULT_POLLING_INTERVAL = 2000 + static DEFAULT_MONITORING_BASE_URL = '/api/v1/progress' + static DEFAULT_ATTACHMENT_BASE_URL = '/api/v1/users' - static exportCompleted (workflowState) { - return workflowState === 'completed'; - } + static exportCompleted(workflowState) { + return workflowState === 'completed' + } - // Returns false if the workflowState is 'failed' or an unknown state - static exportFailed (workflowState) { - if (workflowState === 'failed') return true; + // Returns false if the workflowState is 'failed' or an unknown state + static exportFailed(workflowState) { + if (workflowState === 'failed') return true - return !['completed', 'queued', 'running'].includes(workflowState); - } + return !['completed', 'queued', 'running'].includes(workflowState) + } - constructor (exportingUrl, currentUserId, existingExport, pollingInterval = GradebookExportManager.DEFAULT_POLLING_INTERVAL) { - this.pollingInterval = pollingInterval; + constructor( + exportingUrl, + currentUserId, + existingExport, + pollingInterval = GradebookExportManager.DEFAULT_POLLING_INTERVAL + ) { + this.pollingInterval = pollingInterval - this.exportingUrl = exportingUrl; - this.monitoringBaseUrl = GradebookExportManager.DEFAULT_MONITORING_BASE_URL; - this.attachmentBaseUrl = `${GradebookExportManager.DEFAULT_ATTACHMENT_BASE_URL}/${currentUserId}/files`; - this.currentUserId = currentUserId; + this.exportingUrl = exportingUrl + this.monitoringBaseUrl = GradebookExportManager.DEFAULT_MONITORING_BASE_URL + this.attachmentBaseUrl = `${ + GradebookExportManager.DEFAULT_ATTACHMENT_BASE_URL + }/${currentUserId}/files` + this.currentUserId = currentUserId - if (existingExport) { - const workflowState = existingExport.workflowState; + if (existingExport) { + const workflowState = existingExport.workflowState - if (workflowState !== 'completed' && workflowState !== 'failed') { - this.export = existingExport; - } + if (workflowState !== 'completed' && workflowState !== 'failed') { + this.export = existingExport } } - - monitoringUrl () { - if (!(this.export && this.export.progressId)) return undefined; - - return `${this.monitoringBaseUrl}/${this.export.progressId}`; - } - - attachmentUrl () { - if (!(this.attachmentBaseUrl && this.export && this.export.attachmentId)) return undefined; - - return `${this.attachmentBaseUrl}/${this.export.attachmentId}`; - } - - clearMonitor () { - if (this.exportStatusPoll) { - window.clearInterval(this.exportStatusPoll); - this.exportStatusPoll = null; - } - } - - monitorExport (resolve, reject) { - if (!this.monitoringUrl()) { - this.export = undefined; - - reject(I18n.t('No way to monitor gradebook exports provided!')); - } - - this.exportStatusPoll = window.setInterval(() => { - axios.get(this.monitoringUrl()).then((response) => { - const workflowState = response.data.workflow_state; - - if (GradebookExportManager.exportCompleted(workflowState)) { - this.clearMonitor(); - - // Export is complete => let's get the attachment url - axios.get(this.attachmentUrl()).then((attachmentResponse) => { - const resolution = { - attachmentUrl: attachmentResponse.data.url, - updatedAt: attachmentResponse.data.updated_at - }; - - this.export = undefined; - - resolve(resolution); - }).catch((error) => { - reject(error); - }); - } else if (GradebookExportManager.exportFailed(workflowState)) { - this.clearMonitor(); - - reject(I18n.t('Error exporting gradebook: %{msg}', { msg: response.data.message })); - } - }); - }, this.pollingInterval); - } - - startExport (gradingPeriodId) { - if (!this.exportingUrl) { - return Promise.reject(I18n.t('No way to export gradebooks provided!')); - } - - if (this.export) { - // We already have an ongoing export, ignoring this call to start a new one - return Promise.reject(I18n.t('An export is already in progress.')); - } - - const params = { - grading_period_id: gradingPeriodId - }; - - return axios.get(this.exportingUrl, { params }).then((response) => { - this.export = { - progressId: response.data.progress_id, - attachmentId: response.data.attachment_id - }; - - return new Promise(this.monitorExport.bind(this)); - }); - } } + monitoringUrl() { + if (!(this.export && this.export.progressId)) return undefined + + return `${this.monitoringBaseUrl}/${this.export.progressId}` + } + + attachmentUrl() { + if (!(this.attachmentBaseUrl && this.export && this.export.attachmentId)) return undefined + + return `${this.attachmentBaseUrl}/${this.export.attachmentId}` + } + + clearMonitor() { + if (this.exportStatusPoll) { + window.clearInterval(this.exportStatusPoll) + this.exportStatusPoll = null + } + } + + monitorExport(resolve, reject) { + if (!this.monitoringUrl()) { + this.export = undefined + + reject(I18n.t('No way to monitor gradebook exports provided!')) + } + + this.exportStatusPoll = window.setInterval(() => { + axios.get(this.monitoringUrl()).then(response => { + const workflowState = response.data.workflow_state + + if (GradebookExportManager.exportCompleted(workflowState)) { + this.clearMonitor() + + // Export is complete => let's get the attachment url + axios + .get(this.attachmentUrl()) + .then(attachmentResponse => { + const resolution = { + attachmentUrl: attachmentResponse.data.url, + updatedAt: attachmentResponse.data.updated_at + } + + this.export = undefined + + resolve(resolution) + }) + .catch(error => { + reject(error) + }) + } else if (GradebookExportManager.exportFailed(workflowState)) { + this.clearMonitor() + + reject(I18n.t('Error exporting gradebook: %{msg}', {msg: response.data.message})) + } + }) + }, this.pollingInterval) + } + + startExport(gradingPeriodId) { + if (!this.exportingUrl) { + return Promise.reject(I18n.t('No way to export gradebooks provided!')) + } + + if (this.export) { + // We already have an ongoing export, ignoring this call to start a new one + return Promise.reject(I18n.t('An export is already in progress.')) + } + + const params = { + grading_period_id: gradingPeriodId + } + + return axios.get(this.exportingUrl, {params}).then(response => { + this.export = { + progressId: response.data.progress_id, + attachmentId: response.data.attachment_id + } + + return new Promise(this.monitorExport.bind(this)) + }) + } +} + export default GradebookExportManager diff --git a/app/jsx/gradezilla/shared/ReuploadSubmissionsDialogManager.js b/app/jsx/gradezilla/shared/ReuploadSubmissionsDialogManager.js index 3e2b869ca78..2f6d2266085 100644 --- a/app/jsx/gradezilla/shared/ReuploadSubmissionsDialogManager.js +++ b/app/jsx/gradezilla/shared/ReuploadSubmissionsDialogManager.js @@ -22,57 +22,57 @@ import I18n from 'i18n!gradezilla' import $ from 'jquery' import 'jquery.instructure_misc_helpers' - class ReuploadSubmissionsDialogManager { - constructor (assignment, reuploadUrlTemplate) { - this.assignment = assignment; - this.reuploadUrl = $.replaceTags(reuploadUrlTemplate, 'assignment_id', assignment.id); - this.showDialog = this.showDialog.bind(this); - } - - isDialogEnabled () { - return this.assignment.hasDownloadedSubmissions; - } - - getReuploadForm (cb) { - if (ReuploadSubmissionsDialogManager.reuploadForm) { - return ReuploadSubmissionsDialogManager.reuploadForm; - } - - ReuploadSubmissionsDialogManager.reuploadForm = $( - re_upload_submissions_form({ authenticityToken: authenticity_token() }) - ).dialog( - { - width: 400, - modal: true, - resizable: false, - autoOpen: false, - close: () => { - if (typeof cb === 'function') { - cb(); - } - } - } - ).submit(function () { - const data = $(this).getFormData(); - let submitForm = true; - - if (!data.submissions_zip) { - submitForm = false; - } else if (!data.submissions_zip.match(/\.zip$/)) { - $(this).formErrors({ submissions_zip: I18n.t('Please upload files as a .zip') }); - submitForm = false; - } - - return submitForm; - }); - - return ReuploadSubmissionsDialogManager.reuploadForm; - } - - showDialog (cb) { - const form = this.getReuploadForm(cb); - form.attr('action', this.reuploadUrl).dialog('open'); - } +class ReuploadSubmissionsDialogManager { + constructor(assignment, reuploadUrlTemplate) { + this.assignment = assignment + this.reuploadUrl = $.replaceTags(reuploadUrlTemplate, 'assignment_id', assignment.id) + this.showDialog = this.showDialog.bind(this) } + isDialogEnabled() { + return this.assignment.hasDownloadedSubmissions + } + + getReuploadForm(cb) { + if (ReuploadSubmissionsDialogManager.reuploadForm) { + return ReuploadSubmissionsDialogManager.reuploadForm + } + + ReuploadSubmissionsDialogManager.reuploadForm = $( + re_upload_submissions_form({authenticityToken: authenticity_token()}) + ) + .dialog({ + width: 400, + modal: true, + resizable: false, + autoOpen: false, + close: () => { + if (typeof cb === 'function') { + cb() + } + } + }) + .submit(function() { + const data = $(this).getFormData() + let submitForm = true + + if (!data.submissions_zip) { + submitForm = false + } else if (!data.submissions_zip.match(/\.zip$/)) { + $(this).formErrors({submissions_zip: I18n.t('Please upload files as a .zip')}) + submitForm = false + } + + return submitForm + }) + + return ReuploadSubmissionsDialogManager.reuploadForm + } + + showDialog(cb) { + const form = this.getReuploadForm(cb) + form.attr('action', this.reuploadUrl).dialog('open') + } +} + export default ReuploadSubmissionsDialogManager diff --git a/app/jsx/gradezilla/shared/SetDefaultGradeDialogManager.js b/app/jsx/gradezilla/shared/SetDefaultGradeDialogManager.js index 47f4e716178..32a07b35736 100644 --- a/app/jsx/gradezilla/shared/SetDefaultGradeDialogManager.js +++ b/app/jsx/gradezilla/shared/SetDefaultGradeDialogManager.js @@ -21,41 +21,52 @@ import I18n from 'i18n!gradebook' import SetDefaultGradeDialog from 'compiled/gradezilla/SetDefaultGradeDialog' import 'compiled/jquery.rails_flash_notifications' - class SetDefaultGradeDialogManager { - constructor (assignment, students, contextId, selectedSection, isAdmin = false, submissionsLoaded = false) { - this.assignment = assignment; - this.students = students; - this.contextId = contextId; - this.selectedSection = selectedSection; - this.isAdmin = isAdmin; - this.submissionsLoaded = submissionsLoaded; +class SetDefaultGradeDialogManager { + constructor( + assignment, + students, + contextId, + selectedSection, + isAdmin = false, + submissionsLoaded = false + ) { + this.assignment = assignment + this.students = students + this.contextId = contextId + this.selectedSection = selectedSection + this.isAdmin = isAdmin + this.submissionsLoaded = submissionsLoaded - this.showDialog = this.showDialog.bind(this); - } + this.showDialog = this.showDialog.bind(this) + } - getSetDefaultGradeDialogOptions () { - return { - assignment: this.assignment, - students: this.students, - context_id: this.contextId, - selected_section: this.selectedSection, - }; - } - - showDialog (cb) { - if (this.isAdmin || !this.assignment.inClosedGradingPeriod) { - const dialog = new SetDefaultGradeDialog(this.getSetDefaultGradeDialogOptions()); - - dialog.show(cb); - } else { - $.flashError(I18n.t('Unable to set default grade because this ' + - 'assignment is due in a closed grading period for at least one student')); - } - } - - isDialogEnabled () { - return this.submissionsLoaded; + getSetDefaultGradeDialogOptions() { + return { + assignment: this.assignment, + students: this.students, + context_id: this.contextId, + selected_section: this.selectedSection } } + showDialog(cb) { + if (this.isAdmin || !this.assignment.inClosedGradingPeriod) { + const dialog = new SetDefaultGradeDialog(this.getSetDefaultGradeDialogOptions()) + + dialog.show(cb) + } else { + $.flashError( + I18n.t( + 'Unable to set default grade because this ' + + 'assignment is due in a closed grading period for at least one student' + ) + ) + } + } + + isDialogEnabled() { + return this.submissionsLoaded + } +} + export default SetDefaultGradeDialogManager diff --git a/app/jsx/gradezilla/shared/helpers/TextMeasure.js b/app/jsx/gradezilla/shared/helpers/TextMeasure.js index fd32fd3547f..c8eaf16d5fb 100644 --- a/app/jsx/gradezilla/shared/helpers/TextMeasure.js +++ b/app/jsx/gradezilla/shared/helpers/TextMeasure.js @@ -16,14 +16,16 @@ * with this program. If not, see . */ -import $ from 'jquery'; +import $ from 'jquery' -export function getWidth (text) { - let $textMeasure = $('#text-measure'); +export function getWidth(text) { + let $textMeasure = $('#text-measure') if (!$textMeasure.length) { - $textMeasure = $('