From f7f99216f45276ba0546a739f9470f79aedd731d Mon Sep 17 00:00:00 2001 From: Spencer Olson Date: Fri, 8 Jul 2016 14:58:26 -0500 Subject: [PATCH 1/8] undo react 0.14 grading period code we have some grading period code in beta that is only supported by react 0.14. since we are running react 0.13 in beta, it breaks things. this undoes that code so that it works with react 0.13 closes CNVS-30385 test plan 1: 1. navigate to the account grading standards page (/accounts/:account_id/grading_standards) 2. click the "+ Set of Grading Periods" button 3. verify focus switches to the "Set name..." input test plan 2: 1. in Safari with VoiceOver enabled, navigate to the account grading standards page (/accounts/:account_id/grading_standards) 2. move VO focus to the delete icon next to a grading period set and click. make sure you click on a set that is NOT the very top set in the list 3. click "OK" on the confirmation dialogue 4. note that VO focus is now on the title of the grading period set that was above the set that was deleted 5. repeat steps 1-4, but delete the set at the very top of the list. verify focus is shifted to the '+ Grading Period Set' button when the set at the top of the list is deleted Change-Id: Ib764ad5029772ecf0245417cd55252ad27d816fb Reviewed-on: https://gerrit.instructure.com/84713 Reviewed-by: Jeremy Neander Reviewed-by: Neil Gupta Reviewed-by: Derek Bender QA-Review: Amber Taniuchi Product-Review: Keith T. Garner Tested-by: Jenkins --- app/jsx/grading/GradingPeriodSetCollection.jsx | 4 ++-- app/jsx/grading/NewGradingPeriodSetForm.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/jsx/grading/GradingPeriodSetCollection.jsx b/app/jsx/grading/GradingPeriodSetCollection.jsx index ece8bc77c5a42..2564b332bbb61 100644 --- a/app/jsx/grading/GradingPeriodSetCollection.jsx +++ b/app/jsx/grading/GradingPeriodSetCollection.jsx @@ -225,11 +225,11 @@ define([ nodeToFocusOnAfterSetDeletion(setID) { const index = this.state.sets.findIndex(set => set.id === setID); if (index < 1) { - return this.refs.addSetFormButton; + return React.findDOMNode(this.refs.addSetFormButton); } else { const setRef = getShowGradingPeriodSetRef(this.state.sets[index - 1]); const setToFocus = this.refs[setRef]; - return setToFocus.refs.title; + return React.findDOMNode(setToFocus.refs.title); } }, diff --git a/app/jsx/grading/NewGradingPeriodSetForm.jsx b/app/jsx/grading/NewGradingPeriodSetForm.jsx index fd5390f4635e0..88398dae106b1 100644 --- a/app/jsx/grading/NewGradingPeriodSetForm.jsx +++ b/app/jsx/grading/NewGradingPeriodSetForm.jsx @@ -28,7 +28,7 @@ define([ }, componentDidMount() { - this.refs.titleInput.focus(); + React.findDOMNode(this.refs.titleInput).focus(); }, setSelectedEnrollmentTermIDs(termIDs) { From 9f970e388518a4ed35c49436a33f465955a71c68 Mon Sep 17 00:00:00 2001 From: Jeremy Neander Date: Fri, 8 Jul 2016 08:58:33 -0500 Subject: [PATCH 2/8] disable mutable account grading period actions on course page If a grading period belongs to an account, no one should be able to edit in on the course grading periods page. closes CNVS-30213 course grading periods batch_update url: * PATCH `courses/:course_id/grading_periods/batch_update` course grading periods delete url: * DELETE `courses/:course_id/grading_periods/:id` test plan: A. Course Grading Periods 1. Create grading periods for a course 2. Sign in as an authorized user (admin) 3. Visit the course grading periods page 4. Verify grading periods can be updated 5. Verify grading periods can be deleted B. Course Grading Periods via API 1. Create grading periods for a course 2. As an authorized user (admin): a. Verify grading periods can be updated via the API b. Verify grading periods can be deleted via the API C. Account Grading Periods 1. Create an enrollment term for an account 2. Create a course in that enrollment term 3. Create a grading period set for that enrollment term 4. Create grading periods in that set 5. Sign in as an authorized user (admin) 6. Visit the course grading periods page 7. Verify everything is read-only D. Account Grading Periods via API 1. Create an enrollment term for an account 2. Create a course in that enrollment term 3. Create a grading period set for that enrollment term 4. Create grading periods in that set 5. As an authorized user (admin): a. Verify grading periods cannot be updated via the API b. Verify grading periods cannot be deleted via the API Change-Id: I004b147e54c3642c065ea83948d311231d94bd2d Reviewed-on: https://gerrit.instructure.com/84324 Tested-by: Jenkins Reviewed-by: Spencer Olson Reviewed-by: Derek Bender QA-Review: KC Naegle Product-Review: Christi Wruck Reviewed-on: https://gerrit.instructure.com/84649 Tested-by: Keith T. Garner QA-Review: Gentry Beckmann Product-Review: Gentry Beckmann --- app/controllers/grading_periods_controller.rb | 16 ++ app/jsx/grading/gradingPeriod.jsx | 6 +- app/jsx/grading/gradingPeriodCollection.jsx | 22 ++- app/jsx/grading/gradingPeriodTemplate.jsx | 41 +++--- .../GradingPeriodCollectionSpec.coffee | 49 +++++++ .../jsx/gradebook/GradingPeriodSpec.coffee | 19 ++- .../GradingPeriodTemplateSpec.coffee | 107 +++++++++++--- .../grading_periods_controller_spec.rb | 138 ++++++++++++++++-- .../factories/grading_period_group_factory.rb | 8 + 9 files changed, 347 insertions(+), 59 deletions(-) diff --git a/app/controllers/grading_periods_controller.rb b/app/controllers/grading_periods_controller.rb index d67b44e825157..e0d0731469a69 100644 --- a/app/controllers/grading_periods_controller.rb +++ b/app/controllers/grading_periods_controller.rb @@ -83,11 +83,13 @@ def index else grading_periods = GradingPeriod.for(@context).sort_by(&:start_date) end + read_only = grading_periods.present? && grading_periods.first.grading_period_group.account_id.present? paginated_grading_periods, meta = paginate_for(grading_periods) respond_to do |format| format.json do render json: serialize_json_api(paginated_grading_periods, meta) .merge(index_permissions) + .merge(grading_periods_read_only: read_only) end end end @@ -156,6 +158,9 @@ def update # successful. def destroy grading_period = GradingPeriod.active.find(params[:id]) + unless can_destroy_in_context?(grading_period) + return render_unauthorized_action + end if authorized_action(grading_period, @current_user, :delete) grading_period.destroy @@ -208,6 +213,9 @@ def course_batch_update unless batch_update_rights?(periods) return render_unauthorized_action end + unless can_batch_update_in_context?(periods) + return render_unauthorized_action + end @context.grading_periods.transaction do errors = no_overlapping_for_new_periods_validation_errors(periods) @@ -287,6 +295,14 @@ def current_user_can_update?(periods) periods.all? { |p| p.grants_right?(@current_user, :update) } end + def can_batch_update_in_context?(periods) + periods.empty? || periods.first.grading_period_group.account_id.blank? + end + + def can_destroy_in_context?(period) + @context.is_a?(Account) || period.grading_period_group.account_id.blank? + end + def context_grading_period_group @context.grading_period_groups.active.first_or_create end diff --git a/app/jsx/grading/gradingPeriod.jsx b/app/jsx/grading/gradingPeriod.jsx index 5f00850958691..13ef02598fc87 100644 --- a/app/jsx/grading/gradingPeriod.jsx +++ b/app/jsx/grading/gradingPeriod.jsx @@ -7,10 +7,9 @@ define([ 'jsx/grading/gradingPeriodTemplate', 'jsx/shared/helpers/dateHelper' ], function(tz, React, $, I18n, _, GradingPeriodTemplate, DateHelper) { - var types = React.PropTypes; - var GradingPeriod = React.createClass({ + var GradingPeriod = React.createClass({ propTypes: { title: types.string.isRequired, startDate: types.instanceOf(Date).isRequired, @@ -19,6 +18,7 @@ define([ updateGradingPeriodCollection: types.func.isRequired, onDeleteGradingPeriod: types.func.isRequired, disabled: types.bool.isRequired, + readOnly: types.bool.isRequired, permissions: types.shape({ update: types.bool.isRequired, delete: types.bool.isRequired, @@ -77,12 +77,14 @@ define([ render: function () { return ( diff --git a/app/jsx/grading/gradingPeriodCollection.jsx b/app/jsx/grading/gradingPeriodCollection.jsx index f5ffe23ade335..0f19f77d0535d 100644 --- a/app/jsx/grading/gradingPeriodCollection.jsx +++ b/app/jsx/grading/gradingPeriodCollection.jsx @@ -8,8 +8,16 @@ define([ 'jquery.instructure_misc_plugins' ], function(React, GradingPeriod, $, I18n, _, ConvertCase) { - let update = React.addons.update; + + const periodsAreLoaded = (state) => { + return state.periods !== null; + }; + + const canAddPeriods = (state) => { + return !state.readOnly && state.canAddNewPeriods; + }; + let GradingPeriodCollection = React.createClass({ propTypes: { @@ -19,6 +27,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) { getInitialState: function() { return { periods: null, + readOnly: false, disabled: false, saveDisabled: true, canAddNewPeriods: false, @@ -36,6 +45,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) { .success(function(periods) { self.setState({ periods: self.deserializePeriods(periods), + readOnly: periods.grading_periods_read_only, canAddNewPeriods: periods.can_create_grading_periods, canChangeGradingPeriodsSetting: periods.can_toggle_grading_periods, disabled: false, @@ -105,7 +115,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) { }, createNewGradingPeriod: function() { - if (this.state.canAddNewPeriods) { + if (!this.state.readOnly && this.state.canAddNewPeriods) { let newPeriod = { title: '', startDate: new Date(''), @@ -236,7 +246,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) { }, renderSaveButton: function() { - if (_.all(this.state.periods, period => period.permissions.update || period.permissions.create)) { + if (periodsAreLoaded(this.state) && !this.state.readOnly && _.all(this.state.periods, period => period.permissions.update || period.permissions.create)) { let buttonText = this.state.disabled ? I18n.t('Updating') : I18n.t('Save'); return (
@@ -256,11 +266,13 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) { return _.map(this.state.periods, period => { return (