Skip to content

Commit

Permalink
treesame commit of origin/stable/2017-07-15
Browse files Browse the repository at this point in the history
Treesame-Commit-Id: 0c8b9af
  • Loading branch information
roor0 committed Jul 26, 2017
2 parents d8b4fed + 0c8b9af commit d6b2dc2
Show file tree
Hide file tree
Showing 25 changed files with 796 additions and 625 deletions.
20 changes: 17 additions & 3 deletions app/controllers/planner_overrides_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
# "example": false,
# "type": "boolean"
# },
# "dismissed": {
# "description": "Controls whether or not the associated plannable item shows up in the opportunities list",
# "example": false,
# "type": "boolean"
# },
# "created_at": {
# "description": "The datetime of when the planner override was created",
# "example": "2017-05-09T10:12:00Z",
Expand All @@ -76,6 +81,7 @@
#

class PlannerOverridesController < ApplicationController
include Api::V1::PlannerItem
include Api::V1::PlannerOverride

before_action :require_user
Expand Down Expand Up @@ -123,7 +129,8 @@ class PlannerOverridesController < ApplicationController
# "plannable_id": 1,
# "user_id": 2,
# "workflow_state": "active",
# "visible": true, // A user-defined setting for minimizing/hiding objects on the planner
# "marked_complete": true, // A user-defined setting for marking items complete in the planner
# "dismissed": false, // A user-defined setting for hiding items from the opportunities list
# "deleted_at": null,
# "created_at": "2017-05-18T18:35:55Z",
# "updated_at": "2017-05-18T18:35:55Z"
Expand Down Expand Up @@ -167,7 +174,7 @@ def items_index
items_json = Rails.cache.fetch(['planner_items', @current_user, page, params[:filter], default_opts].cache_key, raw: true, expires_in: 120.minutes) do
items = params[:filter] == 'new_activity' ? unread_items : planner_items
items = Api.paginate(items, self, api_v1_planner_items_url)
planner_items_json(items, @current_user, session, {start_at: start_date})
planner_items_json(items, @current_user, session, {start_at: start_date, due_after: start_date, due_before: end_date})
end

render json: items_json
Expand Down Expand Up @@ -205,10 +212,14 @@ def show
# @argument marked_complete
# determines whether the planner item is marked as completed
#
# @argument dismissed
# determines whether the planner item shows in the opportunities list
#
# @returns PlannerOverride
def update
planner_override = PlannerOverride.find(params[:id])
planner_override.marked_complete = value_to_boolean(params[:marked_complete])
planner_override.dismissed = value_to_boolean(params[:dismissed])

if planner_override.save
render json: planner_override_json(planner_override, @current_user, session), status: :ok
Expand All @@ -230,6 +241,9 @@ def update
# @argument marked_complete [Boolean]
# If this is true, the item will show in the planner as completed
#
# @argument dismissed [Boolean]
# If this is true, the item will not show in the opportunities list
#
#
# @returns PlannerOverride
def create
Expand All @@ -240,7 +254,7 @@ def create
end
planner_override = PlannerOverride.new(plannable_type: plannable_type,
plannable_id: params[:plannable_id], marked_complete: value_to_boolean(params[:marked_complete]),
user: @current_user)
user: @current_user, dismissed: value_to_boolean(params[:dismissed]))

if planner_override.save
render json: planner_override_json(planner_override, @current_user, session), status: :created
Expand Down
76 changes: 0 additions & 76 deletions app/jsx/courses/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ import axios from 'axios'
import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import Checkbox from 'instructure-ui/lib/components/Checkbox'
import Tooltip from 'instructure-ui/lib/components/Tooltip'
import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent'
import PresentationContent from 'instructure-ui/lib/components/PresentationContent'

const defaultViewStore = createStore({
selectedDefaultView: ENV.COURSE.default_view,
Expand Down Expand Up @@ -121,79 +117,7 @@ class ChooseHomePageButton extends React.Component {
}
}

// This wraps the instui Checkbox variant=toggle
// to submit the enclosing form when checked or unchecked.
class PubUnpubToggle extends React.Component { // eslint-disable-line react/no-multi-comp
static modes = {
PUBLISHED: {
label: I18n.t('Published'),
tip: I18n.t('Click to unpublish.'),
},
UNPUBLISHED: {
label: I18n.t('Unpublished'),
tip: I18n.t('Click to publish.'),
},
PUBLISHING: {
label: I18n.t('Publishing…'),
tip: I18n.t('Please wait.'),
},
UNPUBLISHING: {
label: I18n.t('Unpublishing…'),
tip: I18n.t('Please wait.'),
}
}
static propTypes = {
op: PropTypes.string.isRequired
}

constructor (props) {
super(props)
this.state = {
mode: PubUnpubToggle.modes[props.op]
}
}

onChange = (event) => {
// if pub/unpub is in-flight, effectively disable clicking the button
if (this.state.mode !== PubUnpubToggle.modes.PUBLISHING && this.state.mode !== PubUnpubToggle.modes.UNPUBLISHING) {
this.setState({
mode: this.props.op === 'PUBLISHED' ? PubUnpubToggle.modes.UNPUBLISHING : PubUnpubToggle.modes.PUBLISHING
})
const form = document.getElementById('course_status_form')
form.submit()
} else {
event.preventDefault()
}
}

render () {
const label = this.state.mode.label
const srlabel = this.state.mode.tip
const checked = this.props.op === 'PUBLISHED' || this.props.op === 'PUBLISHING'
const placement = 'top start'

// ideally, we'd let the tooltip provide the additional tip information, but NVDA doesn't read it unless
// you tab into the Checkbox to give it focus. What I wound up doing was hiding the tooltip from the screenreader
// and including the tip as screenreder only content w/in the checkbox's label.
return (
<Tooltip tip={<PresentationContent>{srlabel}</PresentationContent>} on={['hover', 'focus']} variant="inverse" placement={placement}>
<Checkbox
variant="toggle"
checked={checked}
onChange={this.onChange}
label={<span>{label}<ScreenReaderContent>{srlabel}</ScreenReaderContent></span>}
/>
</Tooltip>
)
}
}

const container = document.getElementById('choose_home_page')
if (container) {
ReactDOM.render(<ChooseHomePageButton store={defaultViewStore} />, container)
}
const pubunpubcontainer = document.getElementById('pubunpub_btn_container')
if (pubunpubcontainer) {
const op = pubunpubcontainer.getAttribute('data-op')
ReactDOM.render(<PubUnpubToggle op={op} />, pubunpubcontainer)
}
4 changes: 2 additions & 2 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2038,8 +2038,8 @@ def self.percent_considered_graded
where('title ILIKE ?', "#{title}%")
}

scope :with_non_placeholder_submissions_for_user, lambda { |user|
with_submissions_for_user(user).merge(Submission.not_placeholder)
scope :having_submissions_for_user, lambda { |user|
with_submissions_for_user(user).merge(Submission.having_submission)
}

scope :for_context_codes, lambda { |codes| where(:context_code => codes) }
Expand Down
2 changes: 1 addition & 1 deletion app/models/enrollment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ def update_from(other, skip_broadcasts=false)
self.end_at = other.end_at
self.course_section_id = other.course_section_id
self.root_account_id = other.root_account_id
self.user.touch if workflow_state_changed?
self.user.touch if workflow_state_changed? && !skip_touch_user
if skip_broadcasts
save_without_broadcasting!
else
Expand Down
2 changes: 0 additions & 2 deletions app/models/planner_override.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class PlannerOverride < ActiveRecord::Base
validates_uniqueness_of :plannable_id, scope: [:user_id, :plannable_type]

scope :active, -> { where workflow_state: 'active' }
scope :visible, -> { where(marked_complete: false) }
scope :hidden, -> { where.not visible }
scope :deleted, -> { where workflow_state: 'deleted' }
scope :not_deleted, -> { where.not deleted }

Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,7 @@ def submitted_assignments(opts={})
as = as.filter_by_visibilities_in_given_courses(id, options[:shard_course_ids]).
published.
due_between_with_overrides(due_after, due_before).
with_non_placeholder_submissions_for_user(id).
having_submissions_for_user(id).
group('submissions.id')
options[:scope_only] ? as : select_available_assignments(as, options)
end
Expand Down
14 changes: 12 additions & 2 deletions app/views/courses/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,20 @@
<div id='course_status_actions' class="ui-buttonset">
<%= form_for @context, html: {id: "course_status_form"} do |f| %>
<% if @context.created? || @context.claimed? %>
<div id="pubunpub_btn_container" data-op="UNPUBLISHED"></div>
<button type="button" class='ui-button btn-unpublish disabled' disabled>
<i class="icon-unpublish"></i><%= t('#buttons.unpublished', %{Unpublished}) %>
</button>
<button type='submit' class="ui-button btn-publish">
<i class="icon-publish"></i><%= t('#buttons.publish', %{Publish}) %>
</button>
<input type='hidden' name='course[event]' value='offer' />
<% else %>
<div id="pubunpub_btn_container" data-op="PUBLISHED"></div>
<button type='submit' class="ui-button">
<i class="icon-unpublish"></i><%= t('#buttons.unpublish', %{Unpublish}) %>
</button>
<button type="button" class="ui-button disabled btn-published" disabled>
<i class="icon-publish"></i><%= t('#buttons.published', %{Published}) %>
</button>
<input type='hidden' name='course[event]' value='claim' />
<% end %>
<% end %>
Expand Down
37 changes: 37 additions & 0 deletions db/migrate/20170705191526_add_dismissed_to_planner_overrides.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#
# Copyright (C) 2017 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class AddDismissedToPlannerOverrides < ActiveRecord::Migration[5.0]
tag :predeploy
disable_ddl_transaction!

def up
add_column :planner_overrides, :dismissed, :boolean
change_column_default :planner_overrides, :dismissed, false

DataFixup::BackfillNulls.run(
PlannerOverride, [:dismissed], default_value: false
)

change_column_null :planner_overrides, :dismissed, false
end

def down
remove_column :planner_overrides, :dismissed
end
end
7 changes: 2 additions & 5 deletions lib/api/v1/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module Api::V1::Assignment
include Api::V1::Locked
include Api::V1::AssignmentOverride
include SubmittablesGradingPeriodProtection
include Api::V1::PlannerOverride

API_ALLOWED_ASSIGNMENT_OUTPUT_FIELDS = {
:only => %w(
Expand Down Expand Up @@ -327,11 +328,7 @@ def assignment_json(assignment, user, session, opts = {})

if opts[:include_planner_override]
override = assignment.planner_override_for(user)
hash['planner_override'] = if override.present?
api_json(override, user, session)
else
nil
end
hash['planner_override'] = planner_override_json(override, user, session)
end

hash
Expand Down
107 changes: 107 additions & 0 deletions lib/api/v1/planner_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#
# Copyright (C) 2017 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

module Api::V1::PlannerItem
include Api::V1::Json
include Api::V1::Assignment
include Api::V1::Quiz
include Api::V1::Context
include Api::V1::DiscussionTopics
include Api::V1::WikiPage
include Api::V1::PlannerOverride

PLANNABLE_TYPES = {
'discussion_topic' => 'DiscussionTopic',
'announcement' => 'DiscussionTopic',
'quiz' => 'Quizzes::Quiz',
'assignment' => 'Assignment',
'wiki_page' => 'WikiPage',
'planner_note' => 'PlannerNote'
}.freeze

def planner_item_json(item, user, session, opts = {})
context_data(item).merge({
:plannable_id => item.id,
:plannable_date => item.planner_date,
:visible_in_planner => item.visible_in_planner_for?(user),
:planner_override => planner_override_json(item.planner_override_for(user), user, session)
}).merge(submission_statuses_for(user, item, opts)).tap do |hash|
if item.is_a?(PlannerNote)
hash[:plannable_type] = 'planner_note'
hash[:plannable] = api_json(item, user, session)
hash[:html_url] = api_v1_planner_notes_show_path(item)
elsif item.is_a?(Quizzes::Quiz) || (item.respond_to?(:quiz?) && item.quiz?)
quiz = item.is_a?(Quizzes::Quiz) ? item : item.quiz
hash[:plannable_type] = 'quiz'
hash[:plannable] = quiz_json(quiz, quiz.context, user, session)
hash[:html_url] = named_context_url(quiz.context, :context_quiz_url, quiz.id)
hash[:planner_override] ||= planner_override_json(quiz.planner_override_for(user), user, session)
elsif item.is_a?(WikiPage) || (item.respond_to?(:wiki_page?) && item.wiki_page?)
item = item.wiki_page if item.respond_to?(:wiki_page?) && item.wiki_page?
hash[:plannable_type] = 'wiki_page'
hash[:plannable] = wiki_page_json(item, user, session)
hash[:html_url] = named_context_url(item.context, :context_wiki_page_url, item.id)
hash[:planner_override] ||= planner_override_json(item.planner_override_for(user), user, session)
elsif item.is_a?(Announcement)
hash[:plannable_type] = 'announcement'
hash[:plannable] = discussion_topic_api_json(item.discussion_topic, item.discussion_topic.context, user, session)
hash[:html_url] = named_context_url(item.discussion_topic.context, :context_discussion_topic_url, item.discussion_topic.id)
elsif item.is_a?(DiscussionTopic) || (item.respond_to?(:discussion_topic?) && item.discussion_topic?)
topic = item.is_a?(DiscussionTopic) ? item : item.discussion_topic
hash[:plannable_type] = 'discussion_topic'
hash[:plannable] = discussion_topic_api_json(topic, topic.context, user, session)
hash[:html_url] = named_context_url(topic.context, :context_discussion_topic_url, topic.id)
hash[:planner_override] ||= planner_override_json(topic.planner_override_for(user), user, session)
else
hash[:plannable_type] = 'assignment'
hash[:plannable] = assignment_json(item, user, session, include_discussion_topic: true)
hash[:html_url] = named_context_url(item.context, :context_assignment_url, item.id)
end
end
end

def planner_items_json(items, user, session, opts = {})
items.map do |item|
planner_item_json(item, user, session, opts)
end
end

def submission_statuses_for(user, item, opts = {})
submission_status = {submissions: false}
return submission_status unless item.is_a?(Assignment)
ss = user.submission_statuses(opts)
submission_status[:submissions] = {
submitted: ss[:submitted].include?(item.id),
excused: ss[:excused].include?(item.id),
graded: ss[:graded].include?(item.id),
late: ss[:late].include?(item.id),
missing: ss[:missing].include?(item.id),
needs_grading: ss[:needs_grading].include?(item.id),
has_feedback: ss[:has_feedback].include?(item.id)
}

submission_status
end

def planner_override_json(override, user, session)
return unless override.present?
json = api_json(override, user, session)
json['plannable_type'] = PLANNABLE_TYPES.key(json['plannable_type'])
json
end
end
Loading

0 comments on commit d6b2dc2

Please sign in to comment.