remove old conversations and conversation submission comments
fixes CNVS-12330, CNVS-9234, CNVS-8099 before you check out - configure a user to use old conversations test plan - ensure that everyone get new conversations, always, even if they explicitly told us that they really like old conversations better - as a student with an existing conversation with a teacher, make an assignment submission and a submission comment - as the teacher, ensure that your unread message count did not increase because of the submission comment Change-Id: If5ae7143abbc5cf5e035f5ed9ea2e5728f70cd45 Reviewed-on: https://gerrit.instructure.com/34343 Reviewed-by: Braden Anderson <banderson@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Steven Shepherd <sshepherd@instructure.com> Product-Review: Joel Hough <joel@instructure.com>
This commit is contained in:
parent
5880ea9f92
commit
38c0bfd032
|
@ -1,23 +0,0 @@
|
|||
require [
|
||||
'jquery'
|
||||
'compiled/conversations/Inbox'
|
||||
'jquery.google-analytics'
|
||||
], ($, Inbox) ->
|
||||
new Inbox(ENV.CONVERSATIONS)
|
||||
|
||||
# Google Analytics
|
||||
$('#create_message_form').on 'click', 'div.token_input', (e) ->
|
||||
$.trackEvent('Compose Message', 'Select Recipient', 'Text Field')
|
||||
|
||||
$('#create_message_form').on 'click', 'a.browser', (e) ->
|
||||
$.trackEvent('Compose Message', 'Select Recipient', 'Picker Button')
|
||||
|
||||
$('body').on 'click', 'div.autocomplete_menu li.selectable', (e) ->
|
||||
label = if $(e.currentTarget).hasClass('context') then 'Course/Group' else 'User'
|
||||
$.trackEvent('Autocomplete', 'Click', label)
|
||||
|
||||
$('#context_tags_filter').on 'click', 'div.token_input', (e) ->
|
||||
$.trackEvent('Filter', 'From/To', 'Text Field')
|
||||
|
||||
$('#context_tags_filter').on 'click', 'a.browser', (e) ->
|
||||
$.trackEvent('Filter', 'From/To', 'Picker Button')
|
|
@ -1,33 +0,0 @@
|
|||
define [
|
||||
'jquery'
|
||||
'Backbone'
|
||||
'jquery.ajaxJSON'
|
||||
'jquery.disableWhileLoading'
|
||||
], ($, Backbone) ->
|
||||
|
||||
class Conversation extends Backbone.Model
|
||||
|
||||
# NOTE: This class should be considered deprecated. Please be careful
|
||||
# when modifying it, especially adding functionality.
|
||||
#
|
||||
# Try adding to app/coffeescripts/models/Conversation.coffee first,
|
||||
# which is a version of this model that uses the API.
|
||||
|
||||
defaults:
|
||||
audience: []
|
||||
|
||||
# we don't currently save the model directly, rather we do inbox actions
|
||||
inboxAction: (options) ->
|
||||
defaults =
|
||||
url: @url()
|
||||
method: 'POST'
|
||||
success: (data) => @list.updateItem(data)
|
||||
options = $.extend(true, {}, defaults, options)
|
||||
options.data = $.extend(@list.baseData(), options.data ? {})
|
||||
ajaxRequest = $.ajaxJSON options.url, options.method, options.data, (data) =>
|
||||
options.success?(data)
|
||||
@list.updateItem(data)
|
||||
# TODO: use $el
|
||||
@list.$item(@id)?.disableWhileLoading(ajaxRequest)
|
||||
|
||||
url: (action='') -> "/conversations/#{@id}/#{action}?#{$.param(@list.baseData())}"
|
|
@ -1,162 +0,0 @@
|
|||
define [
|
||||
'i18n!conversations.conversations_list'
|
||||
'jquery'
|
||||
'compiled/widget/ScrollableList'
|
||||
'compiled/conversations/Conversation'
|
||||
'jst/conversations/conversationItem'
|
||||
'jquery.instructure_date_and_time'
|
||||
'jst/_avatar' # needed by conversationItem template
|
||||
], (I18n, $, ScrollableList, Conversation, conversationItemTemplate) ->
|
||||
|
||||
class extends ScrollableList
|
||||
constructor: (@pane, $scroller) ->
|
||||
@app = @pane.app
|
||||
@$empty = @pane.$pane.find('#no_messages')
|
||||
|
||||
super $scroller,
|
||||
model: Conversation
|
||||
itemTemplate: @conversationItem
|
||||
elementHeight: 76
|
||||
itemIdsKey: 'conversation_ids'
|
||||
itemsKey: 'conversations'
|
||||
sortKey: 'last_message_at'
|
||||
sortDir: 'desc'
|
||||
baseUrl: '/conversations?include_all_conversation_ids=1&include_beta=1'
|
||||
noAutoLoad: true
|
||||
|
||||
$('#menu-wrapper').on('click', 'a.standard_action', @triggerConversationAction)
|
||||
@$list.on('click', 'li[data-id] > a.standard_action', @triggerConversationAction)
|
||||
@$list.on('mousedown keydown', 'button.al-trigger', @pane.filterMenu.bind(@pane))
|
||||
|
||||
$(window).unload(=> clearTimeout(@markAsUnread))
|
||||
|
||||
triggerConversationAction: (e) =>
|
||||
e.preventDefault()
|
||||
@pane.action($(e.currentTarget), method: 'PUT')
|
||||
|
||||
baseData: ->
|
||||
{scope: @scope, filter: @filters}
|
||||
|
||||
load: (params, cb) ->
|
||||
@scope = params.scope
|
||||
@filters = params.filter ? []
|
||||
super
|
||||
sortKey: "#{@lastMessageKey()}_at"
|
||||
params: @baseData()
|
||||
force: params.force
|
||||
loadId: params.id # if set, make sure it's loaded, and scroll into view
|
||||
cb: =>
|
||||
@emptyCheck()
|
||||
cb?()
|
||||
|
||||
# item will either be the loadId's item or null
|
||||
loaded: (id, item, $node) =>
|
||||
if id and not item? # invalid id (deleted or not relevant to scope/filter)
|
||||
@app.updateHashData id: null
|
||||
else
|
||||
@activate item, $node
|
||||
|
||||
added: (conversation, $node) ->
|
||||
@$empty.hide()
|
||||
|
||||
updated: (conversation, $node) ->
|
||||
@emptyCheck()
|
||||
if @isActive(conversation.id) and conversation.get('workflow_state') is 'unread'
|
||||
@markAsUnread = setTimeout =>
|
||||
return unless @isActive(conversation.id) and @$item(conversation.id)
|
||||
conversation.inboxAction
|
||||
method: 'PUT'
|
||||
data: {conversation: {workflow_state: 'read'}}
|
||||
success: (data) -> data.defer_visibility_check = true
|
||||
, 2000
|
||||
|
||||
removed: (data, $node) ->
|
||||
@emptyCheck()
|
||||
@activate(null) if @isActive(data.id)
|
||||
|
||||
clicked: (e) =>
|
||||
# ignore clicks that come from the gear menu
|
||||
unless $(e.target).closest('.admin-links').length
|
||||
@select $(e.currentTarget).attr('data-id')
|
||||
|
||||
lastMessageKey: ->
|
||||
if @scope is 'sent'
|
||||
'last_authored_message'
|
||||
else
|
||||
'last_message'
|
||||
|
||||
emptyCheck: ->
|
||||
map = @app.filterNameMap
|
||||
text = switch @scope
|
||||
when 'unread' then I18n.t 'no_unread_messages', 'You have no unread messages'
|
||||
when 'starred' then I18n.t 'no_starred_messages', 'You have no starred messages'
|
||||
when 'sent' then I18n.t 'no_sent_messages', 'You have no sent messages'
|
||||
when 'archived' then I18n.t 'no_archived_messages', 'You have no archived messages'
|
||||
else I18n.t 'no_messages', 'You have no messages'
|
||||
filterNames = (map[i] for i in @filters when map[i])
|
||||
text += " (#{(filterNames.join(', '))})" if filterNames.length
|
||||
@$empty.text text
|
||||
@$empty.showIf !@$items().length
|
||||
|
||||
select: (id, activate=true) ->
|
||||
@ensureSelected(id, activate)
|
||||
@app.updateHashData id: id if activate
|
||||
|
||||
isActive: (id) ->
|
||||
@active and @active.id is id
|
||||
|
||||
deactivate: ->
|
||||
return unless @active and item = @item(@active.id)
|
||||
delete @active
|
||||
@$item(item.id)?.removeClass('selected')
|
||||
@removeItem(item) unless item.get('visible')
|
||||
clearTimeout @markAsUnread
|
||||
|
||||
ensureSelected: (id, activate=true) ->
|
||||
if activate # deselect any existing selection(s) ... soon we will have bulk conversation actions, so this will make more sense
|
||||
@selected = []
|
||||
@$items().removeClass('selected')
|
||||
@deactivate() unless @isActive(id)
|
||||
else
|
||||
@selected ?= []
|
||||
if id?
|
||||
@$item(id).addClass('selected')
|
||||
@selected.push id
|
||||
|
||||
activate: (conversation, $node) ->
|
||||
if conversation and @isActive(conversation?.id)
|
||||
@app.deselectMessages()
|
||||
return
|
||||
|
||||
@ensureSelected(conversation?.id)
|
||||
@active = conversation
|
||||
|
||||
@app.loadConversation @active, $node, =>
|
||||
if $node?.hasClass 'unread'
|
||||
# we've already done this server-side
|
||||
$node.removeClass('read unread archived').addClass 'read'
|
||||
|
||||
conversationItem: (item) =>
|
||||
data = $.extend({}, item.toJSON())
|
||||
|
||||
if data.participants
|
||||
for user in data.participants when !@app.userCache[user.id]
|
||||
@app.userCache[user.id] = user
|
||||
|
||||
if data.audience
|
||||
data.audienceHtml = @app.htmlAudience(data, highlightFilters: true)
|
||||
@app.formPane.refresh() if @isActive(data.id)
|
||||
|
||||
data.lastMessage = data[@lastMessageKey()]
|
||||
data.lastMessageAt = $.friendlyDatetime($.fudgeDateForProfileTimezone(data[@lastMessageKey() + "_at"]))
|
||||
data.hideCount = data.message_count is 1
|
||||
|
||||
classes = (property for property in data.properties)
|
||||
classes.push data.workflow_state
|
||||
classes.push 'private' if data['private']
|
||||
classes.push 'starred' if data.starred
|
||||
classes.push 'unsubscribed' unless data.subscribed
|
||||
classes.push 'selected' if $.inArray(data.id, @selected) >= 0
|
||||
data.classes = classes.join(' ')
|
||||
|
||||
conversationItemTemplate(data)
|
|
@ -1,81 +0,0 @@
|
|||
define [
|
||||
'i18n!conversations.conversations_pane'
|
||||
'jquery'
|
||||
'compiled/conversations/ConversationsList'
|
||||
'str/htmlEscape'
|
||||
'compiled/util/shortcut'
|
||||
'compiled/jquery/offsetFrom'
|
||||
], (I18n, $, ConversationsList, h, shortcut) ->
|
||||
|
||||
class
|
||||
shortcut this, 'list',
|
||||
'baseData'
|
||||
'updateItems'
|
||||
'isActive'
|
||||
|
||||
constructor: (@app, @$pane) ->
|
||||
@list = new ConversationsList(this, @$pane.find('> div.conversations'))
|
||||
@selected = []
|
||||
@initializeActions()
|
||||
|
||||
initializeActions: ->
|
||||
$('#menu-wrapper').on 'click', 'a.action_delete_all', (e) =>
|
||||
e.preventDefault()
|
||||
if confirm I18n.t('confirm.delete_conversation', "Are you sure you want to delete your copy of this conversation? This action cannot be undone.")
|
||||
@action($(e.currentTarget), method: 'DELETE')
|
||||
|
||||
updateView: (params) ->
|
||||
@list.load params
|
||||
|
||||
action: ($actionNode, options) ->
|
||||
conversationId = options.conversationId or
|
||||
$actionNode.closest('div.conversations li').data('id') or
|
||||
$actionNode.parents('ul[data-id]:first').data('id')
|
||||
conversation = @list.item(conversationId)
|
||||
options = $.extend(true, {}, {url: @actionUrlFor($actionNode, conversationId)}, options)
|
||||
origCb = options.success
|
||||
options.success = (data) =>
|
||||
@app.addMessage(data.messages[0]) if data.messages?.length
|
||||
origCb?(data)
|
||||
conversation.inboxAction options
|
||||
|
||||
actionUrlFor: ($actionNode, conversationId) ->
|
||||
url = $.replaceTags($actionNode.attr('href'), 'id', conversationId)
|
||||
url + (if url.match(/\?/) then '&' else '?') + $.param(@baseData())
|
||||
|
||||
active: ->
|
||||
@list.active
|
||||
|
||||
filterMenu: (e) ->
|
||||
$conversation = $(e.currentTarget).parents('li:first')
|
||||
$list = $(e.currentTarget).siblings('ul:first')
|
||||
|
||||
# reset visibility of all actions
|
||||
$list.find('li').show()
|
||||
|
||||
# get current state of the conversation
|
||||
isRead = $conversation.hasClass('read')
|
||||
isStarred = $conversation.hasClass('starred')
|
||||
isPrivate = $conversation.hasClass('private')
|
||||
isSubscribed = !$conversation.hasClass('unsubscribed')
|
||||
isArchived = $conversation.hasClass('archived')
|
||||
|
||||
# set action visibility based on current state
|
||||
$list.find('.action_mark_as_read').parent().remove() if isRead
|
||||
$list.find('.action_mark_as_unread').parent().remove() unless isRead
|
||||
$list.find('.action_star').parent().remove() if isStarred
|
||||
$list.find('.action_unstar').parent().remove() unless isStarred
|
||||
$list.find('.action_archive').parent().remove() if isArchived
|
||||
$list.find('.action_unarchive').parent().remove() unless isArchived
|
||||
if isArchived
|
||||
$list.find('.action_mark_as_read').parent().remove()
|
||||
$list.find('.action_mark_as_unread').parent().remove()
|
||||
if isPrivate
|
||||
$list.find('.action_subscribe, .action_unsubscribe').parent().remove()
|
||||
else
|
||||
$list.find('.action_subscribe').parent().remove() if isSubscribed
|
||||
$list.find('.action_unsubscribe').parent().remove() unless isSubscribed
|
||||
|
||||
resize: (newHeight) ->
|
||||
@list.$scroller.height(newHeight - $('#actions').outerHeight(true))
|
||||
@list.fetchVisible()
|
|
@ -1,639 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'str/htmlEscape'
|
||||
'compiled/conversations/introSlideshow'
|
||||
'compiled/conversations/ConversationsPane'
|
||||
'compiled/conversations/MessageFormPane'
|
||||
'compiled/conversations/audienceList'
|
||||
'compiled/util/contextList'
|
||||
'compiled/widget/ContextSearch'
|
||||
'compiled/str/TextHelper'
|
||||
'jst/_avatar'
|
||||
'jquery.ajaxJSON'
|
||||
'jquery.instructure_date_and_time'
|
||||
'jquery.instructure_forms'
|
||||
'jqueryui/dialog'
|
||||
'jquery.instructure_misc_helpers'
|
||||
'jquery.disableWhileLoading'
|
||||
'compiled/jquery.rails_flash_notifications'
|
||||
'compiled/jquery/offsetFrom'
|
||||
'media_comments'
|
||||
'vendor/jquery.ba-hashchange'
|
||||
'vendor/jquery.elastic'
|
||||
'jqueryui/position'
|
||||
], (I18n, $, _, h, introSlideshow, ConversationsPane, MessageFormPane, audienceList, contextList, TokenInput, TextHelper, avatarPartial) ->
|
||||
|
||||
class
|
||||
constructor: (@options) ->
|
||||
@currentUser = @options.USER
|
||||
@contexts = @options.CONTEXTS
|
||||
@userCache = {}
|
||||
@userCache[@currentUser.id] = @currentUser
|
||||
$ @render
|
||||
|
||||
render: =>
|
||||
@$inbox = $('#inbox')
|
||||
@minHeight = parseInt @$inbox.css('min-height').replace('px', '')
|
||||
@$conversations = $('#conversations')
|
||||
@$messages = $('#messages')
|
||||
@$messageList = @$messages.find('ul.messages')
|
||||
@$others = $('<div class="others" id="others_popup" />')
|
||||
@initializeHelp()
|
||||
@initializeForms()
|
||||
@initializeMenus()
|
||||
@initializeMessageActions()
|
||||
@initializeTokenInputs()
|
||||
@initializeConversationsPane()
|
||||
@initializeMessageFormPane()
|
||||
@initializeAutoResize()
|
||||
@initializeHashChange()
|
||||
if @options.SHOW_INTRO
|
||||
introSlideshow()
|
||||
|
||||
filters: ->
|
||||
@conversations.baseData().filter ? []
|
||||
|
||||
htmlAudience: (conversation, options = {}) ->
|
||||
conversation ?= @conversations.active()?.attributes
|
||||
unless conversation?
|
||||
return h(I18n.t('headings.new_message', 'New Message'))
|
||||
|
||||
filters = options.filters = if options.highlightFilters then @filters() else []
|
||||
audience = for id in conversation.audience
|
||||
{
|
||||
id: id
|
||||
name: @userCache[id].name
|
||||
activeFilter: _.include(filters, "user_#{id}")
|
||||
}
|
||||
|
||||
ret = audienceList(audience, options)
|
||||
if audience.length
|
||||
ret += " <em>" + @htmlContextList(conversation.audience_contexts, options) + "</em>"
|
||||
ret
|
||||
|
||||
htmlContextList: (contexts, options = {}) ->
|
||||
contexts = {courses: _.keys(contexts.courses), groups: _.keys(contexts.groups)}
|
||||
contextList(contexts, @contexts, options)
|
||||
|
||||
htmlNameForUser: (user, contexts = {courses: user.common_courses, groups: user.common_groups}) ->
|
||||
h(user.name) + if contexts.courses?.length or contexts.groups?.length then " <em>" + @htmlContextList(contexts) + "</em>" else ''
|
||||
|
||||
canAddNotesFor: (userOrId) =>
|
||||
return false unless @options.NOTES_ENABLED
|
||||
user = if typeof userOrId is 'object' then userOrId else @userCache[userOrId]
|
||||
return false unless user?
|
||||
for id, roles of user.common_courses
|
||||
return true if 'StudentEnrollment' in roles and (@options.CAN_ADD_NOTES_FOR_ACCOUNT or @contexts.courses[id]?.permissions?.manage_user_notes)
|
||||
false
|
||||
|
||||
loadConversation: (conversation, $node, cb) ->
|
||||
@$messageList.removeClass('private').hide().html ''
|
||||
@$messageList.addClass('private') if $conversation?.hasClass('private')
|
||||
|
||||
@resetMessageForm(conversation)
|
||||
@toggleMessageActions(off)
|
||||
|
||||
return cb() unless conversation?
|
||||
|
||||
url = "#{conversation.url()}&include_beta=1"
|
||||
@$messageList.show().disableWhileLoading $.ajaxJSON url, 'GET', {}, (data) =>
|
||||
@conversations.updateItems [data]
|
||||
return unless @conversations.isActive(data.id)
|
||||
for user in data.participants when !@userCache[user.id]?.avatar_url
|
||||
@userCache[user.id] = user
|
||||
user.htmlName = @htmlNameForUser(user)
|
||||
if data['private'] and user = (user for user in data.participants when user.id isnt @currentUser.id)[0]
|
||||
@formPane.resetForParticipant(user)
|
||||
@resize()
|
||||
@$messages.show()
|
||||
@currentConversation = data
|
||||
@$messageList.append @buildMessage(message) for message in data.messages
|
||||
@$messageList.show()
|
||||
@formPane.form.setAuthor(data.messages, data.participants)
|
||||
cb()
|
||||
|
||||
resetMessageForm: (conversation) ->
|
||||
$('#action_compose_message').toggleClass 'active', !conversation?
|
||||
baseData = @conversations.baseData()
|
||||
@formPane.reset(_.extend({}, @currentHashData(),
|
||||
conversation: conversation
|
||||
audience: @htmlAudience(null, linkToContexts: true, highlightFilters: true)
|
||||
addRecipientsEnabled: conversation? and !conversation.get('private')
|
||||
mediaCommentsEnabled: @options.MEDIA_COMMENTS_ENABLED
|
||||
filter: baseData.filter
|
||||
scope: baseData.scope
|
||||
))
|
||||
|
||||
updatedConversation: (data) ->
|
||||
@formPane.refresh @htmlAudience(null, linkToContexts: true, highlightFilters: true)
|
||||
return unless data.length
|
||||
|
||||
@conversations.updateItems data
|
||||
if data.length is 1
|
||||
conversation = data[0]
|
||||
if @conversations.isActive(conversation.id)
|
||||
@buildMessage(conversation.messages[0]).prependTo(@$messageList).slideDown 'fast'
|
||||
if conversation.visible
|
||||
@updateHashData id: conversation.id
|
||||
|
||||
deselectMessages: ->
|
||||
@$messageList.find('li.selected').removeClass 'selected'
|
||||
|
||||
addMessage: (message) ->
|
||||
@toggleMessageActions(off)
|
||||
@buildMessage(message).prependTo(@$messageList).slideDown 'fast'
|
||||
|
||||
UNKNOWN_USER_NAMES: [I18n.t('unknown_user', 'Unknown user'), h(I18n.t('unknown_user', 'Unknown user'))]
|
||||
# Returns [userName, htmlName]
|
||||
userNames: (user) ->
|
||||
return @UNKNOWN_USER_NAMES unless user
|
||||
user.htmlName ?= @htmlNameForUser(user)
|
||||
[user.name, user.htmlName]
|
||||
|
||||
buildMessage: (data) ->
|
||||
return @buildSubmission(data) if data.submission
|
||||
$message = $("#message_blank").clone(true).attr('id', 'message_' + data.id)
|
||||
$message.data('id', data.id)
|
||||
$message.addClass(if data.generated
|
||||
'generated'
|
||||
else if data.author_id is @currentUser.id
|
||||
'self'
|
||||
else
|
||||
'other'
|
||||
)
|
||||
$message.addClass('forwardable')
|
||||
user = @userCache[data.author_id]
|
||||
[userName, htmlName] = @userNames user
|
||||
$message.prepend avatarPartial avatar_url: user.avatar_url, display_name: userName if user
|
||||
$message.find('.audience').html htmlName
|
||||
$message.find('span.date').text $.datetimeString(data.created_at)
|
||||
$message.find('p').html TextHelper.formatMessage(data.body)
|
||||
$message.find("a.show_quoted_text_link").click (e) =>
|
||||
$target = $(e.currentTarget)
|
||||
$text = $target.parents(".quoted_text_holder").children(".quoted_text")
|
||||
if $text.length
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
$text.show()
|
||||
$target.hide()
|
||||
$pmAction = $message.find('a.send_private_message')
|
||||
$pmAction.on 'click', (e) =>
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
user = @userCache[data.author_id]
|
||||
# Click the "New Message" button and after a short delay,
|
||||
# add the clicked user's token to the input.
|
||||
$('#action_compose_message').trigger('click')
|
||||
clearTimeout @addUserTokenCb if @addUserTokenCb
|
||||
@addUserTokenCb = setTimeout =>
|
||||
delete @addUserTokenCb
|
||||
@formPane.form.addToken
|
||||
value: user.id
|
||||
text: user.name
|
||||
data: user
|
||||
,
|
||||
100
|
||||
if data.forwarded_messages?.length
|
||||
$ul = $('<ul class="messages"></ul>')
|
||||
for submessage in data.forwarded_messages
|
||||
$ul.append @buildMessage(submessage)
|
||||
$message.append $ul
|
||||
|
||||
$ul = $message.find('ul.message_attachments').first().detach()
|
||||
$mediaObjectBlank = $ul.find('.media_object_blank').detach()
|
||||
$attachmentBlank = $ul.find('.attachment_blank').detach()
|
||||
if data.media_comment? or data.attachments?.length
|
||||
$message.append $ul
|
||||
if data.media_comment?
|
||||
$ul.append @buildMediaObject($mediaObjectBlank, data.media_comment)
|
||||
if data.attachments?
|
||||
for attachment in data.attachments
|
||||
$ul.append @buildAttachment($attachmentBlank, attachment)
|
||||
|
||||
$message
|
||||
|
||||
buildMediaObject: (blank, data) ->
|
||||
$mediaObject = blank.clone(true).attr('id', 'media_comment_' + data.media_id)
|
||||
$mediaObject.find('span.title').html h(data.display_name)
|
||||
$mediaObject.find('span.media_comment_id').html h(data.media_id)
|
||||
$mediaObject.find('.instructure_inline_media_comment').data('media_comment_type', data.media_type)
|
||||
$mediaObject
|
||||
|
||||
buildAttachment: (blank, data) ->
|
||||
$attachment = blank.clone(true).attr('id', 'attachment_' + data.id)
|
||||
$attachment.data('id', data.id)
|
||||
$attachment.find('span.title').html h(data.display_name)
|
||||
$link = $attachment.find('a')
|
||||
$link.attr('href', data.url)
|
||||
$link.click (e) =>
|
||||
e.stopPropagation()
|
||||
$attachment
|
||||
|
||||
buildSubmission: (data) ->
|
||||
$submission = $("#submission_blank").clone(true).attr('id', data.id)
|
||||
$submission.data('id', data.id)
|
||||
data = data.submission
|
||||
$ul = $submission.find('ul')
|
||||
$header = $ul.find('li.header')
|
||||
href = $.replaceTags($header.find('a').attr('href'), course_id: data.assignment.course_id, assignment_id: data.assignment_id, id: data.user_id)
|
||||
$header.find('a').attr('href', href)
|
||||
user = @userCache[data.user_id]
|
||||
[userName, htmlName] = @userNames user
|
||||
$header.find('.title').html h(data.assignment.name)
|
||||
$header.find('span.date').text(if data.submitted_at
|
||||
$.datetimeString(data.submitted_at)
|
||||
else
|
||||
I18n.t('not_applicable', 'N/A')
|
||||
)
|
||||
$header.find('.audience').html htmlName
|
||||
if data.score && data.assignment.points_possible
|
||||
score = "#{data.score} / #{data.assignment.points_possible}"
|
||||
else
|
||||
score = data.score ? I18n.t('not_scored', 'no score')
|
||||
$header.find('.score').html(score)
|
||||
$commentBlank = $ul.find('.comment').detach()
|
||||
index = 0
|
||||
initiallyShown = 4
|
||||
for idx in [data.submission_comments.length - 1 .. 0] by -1
|
||||
comment = data.submission_comments[idx]
|
||||
break if index >= 10
|
||||
index++
|
||||
$comment = @buildSubmissionComment($commentBlank, comment)
|
||||
$comment.hide() if index > initiallyShown
|
||||
$ul.append $comment
|
||||
$moreLink = $ul.find('.more').detach()
|
||||
# the submission response isn't yet paginating/limiting the number of
|
||||
# comments returned, but we don't want to display more than 10 here, so we
|
||||
# artificially limit it.
|
||||
if index > initiallyShown
|
||||
$inlineMore = $moreLink.clone(true)
|
||||
$inlineMore.find('.hidden').text(index - initiallyShown)
|
||||
$inlineMore.attr('title', h(I18n.t('titles.expand_inline', "Show more comments")))
|
||||
$inlineMore.click (e) =>
|
||||
$target = $(e.currentTarget)
|
||||
$submission = $target.closest('.submission')
|
||||
$submission.find('.more:hidden').show()
|
||||
$target.hide()
|
||||
$submission.find('.comment:hidden').slideDown('fast')
|
||||
@resize()
|
||||
return false
|
||||
$ul.append $inlineMore
|
||||
if data.submission_comments.length > index
|
||||
$moreLink.find('a').attr('href', href).attr('target', '_blank')
|
||||
$moreLink.find('.hidden').text(data.submission_comments.length - index)
|
||||
$moreLink.attr('title', h(I18n.t('titles.view_submission', "Open submission in new window.")))
|
||||
$moreLink.hide() if data.submission_comments.length > initiallyShown
|
||||
$ul.append $moreLink
|
||||
$submission
|
||||
|
||||
buildSubmissionComment: (blank, data) ->
|
||||
$comment = blank.clone(true)
|
||||
user = @userCache[data.author_id]
|
||||
[userName, htmlName] = @userNames user
|
||||
$comment.prepend avatarPartial avatar_url: user.avatar_url, display_name: userName if user
|
||||
$comment.find('.audience').html htmlName
|
||||
$comment.find('span.date').text $.datetimeString(data.created_at)
|
||||
$comment.find('p').html h(data.comment).replace(/\n/g, '<br />')
|
||||
$comment
|
||||
|
||||
closeMenus: () ->
|
||||
$('#actions .menus > li, #conversation_actions, #conversations .actions').removeClass('selected')
|
||||
|
||||
openMenu: ($menu) ->
|
||||
@closeMenus()
|
||||
unless $menu.hasClass('disabled')
|
||||
$div = $menu.parent('li, span').addClass('selected').find('div')
|
||||
# TODO: move this out in the DOM so we can center it and not have it get clipped
|
||||
offset = -($div.parent().position().left + $div.parent().outerWidth() / 2) + 6 # for box shadow
|
||||
offset = -($div.outerWidth() / 2) if offset < -($div.outerWidth() / 2)
|
||||
$div.css 'margin-left', offset + 'px'
|
||||
|
||||
resize: (delay=0) ->
|
||||
clearTimeout @resizeCb if @resizeCb
|
||||
@resizeCb = setTimeout =>
|
||||
delete @resizeCb
|
||||
availableHeight = $(window).height() - $('#header').outerHeight(true) - ($('#wrapper-container').outerHeight(true) - $('#wrapper-container').height()) - ($('#main').outerHeight(true) - $('#main').height()) - $('#breadcrumbs').outerHeight(true) - $('#footer').outerHeight(true)
|
||||
availableHeight = @minHeight if availableHeight < @minHeight
|
||||
$(document.body).toggleClass('too_small', availableHeight <= @minHeight)
|
||||
@$inbox.height(availableHeight)
|
||||
@$messageList.height(availableHeight - @formPane.height())
|
||||
@conversations.resize(availableHeight)
|
||||
, delay
|
||||
|
||||
toggleMessageActions: (state) ->
|
||||
if state?
|
||||
@$messageList.find('> li').removeClass('selected')
|
||||
@$messageList.find('> li :checkbox').attr('checked', false)
|
||||
else
|
||||
state = !!@$messageList.find('li.selected').length
|
||||
$('#action_forward').parent().showIf(state and @$messageList.find('li.selected.forwardable').length)
|
||||
if state then $("#message_actions").slideDown(100) else $("#message_actions").slideUp(100)
|
||||
@formPane.toggle(state)
|
||||
|
||||
updateHashData: (changes) ->
|
||||
data = $.extend(@currentHashData(), changes)
|
||||
hash = $.encodeToHex(JSON.stringify(data))
|
||||
if hash isnt location.hash.substring(1)
|
||||
location.hash = hash
|
||||
$(document).triggerHandler('document_fragment_change', hash)
|
||||
|
||||
initializeHelp: ->
|
||||
$('#conversations-intro-menu-item, #conversations-intro-btn').click (e) =>
|
||||
e.preventDefault()
|
||||
introSlideshow()
|
||||
|
||||
prepareTextareas: ($nodes) ->
|
||||
$nodes.elastic()
|
||||
$nodes.keypress (e) =>
|
||||
if e.which is 13 and e.shiftKey
|
||||
$(e.target).closest('form').submit()
|
||||
false
|
||||
|
||||
initializeForms: ->
|
||||
@$addForm = $('#add_recipients_form')
|
||||
@$forwardForm = $('#forward_message_form')
|
||||
@prepareTextareas(@$forwardForm.find('textarea'))
|
||||
|
||||
@$addForm.submit (e) =>
|
||||
valid = !!(@$addForm.find('.token_input li').length)
|
||||
e.stopImmediatePropagation() unless valid
|
||||
valid
|
||||
@$addForm.formSubmit
|
||||
disableWhileLoading: true,
|
||||
success: (data) =>
|
||||
@updatedConversation(data)
|
||||
@$addForm.dialog('close')
|
||||
error: (data) =>
|
||||
@$addForm.dialog('close')
|
||||
|
||||
@$forwardForm.submit (e) =>
|
||||
valid = !!(@$forwardForm.find('#forward_body').val() and @$forwardForm.find('.token_input li').length)
|
||||
e.stopImmediatePropagation() unless valid
|
||||
valid
|
||||
@$forwardForm.formSubmit
|
||||
disableWhileLoading: true,
|
||||
success: (data) =>
|
||||
@updatedConversation(data)
|
||||
@$forwardForm.dialog('close')
|
||||
error: (data) =>
|
||||
@$forwardForm.dialog('close')
|
||||
|
||||
|
||||
@$messageList.click (e) =>
|
||||
if $(e.target).closest('a.instructure_inline_media_comment, .mejs-container').length
|
||||
# a.instructure_inline_media_comment clicks have to propagate to the
|
||||
# top due to "live" handling; if it's one of those, it's not really
|
||||
# intended for us, just let it go
|
||||
# also, dont catch clicks on mediaelementjs videos. that is for play/pause
|
||||
else
|
||||
$message = $(e.target).closest('#messages > ul > li')
|
||||
unless $message.hasClass('generated')
|
||||
$message.toggleClass('selected')
|
||||
$message.find('> :checkbox').attr('checked', $message.hasClass('selected'))
|
||||
@toggleMessageActions()
|
||||
|
||||
initializeMenus: =>
|
||||
$('.menus > li > a').click (e) =>
|
||||
e.preventDefault(e)
|
||||
@openMenu $(e.currentTarget)
|
||||
.focus (e) =>
|
||||
@openMenu $(e.currentTarget)
|
||||
|
||||
$(document).bind 'mousedown', (e) =>
|
||||
unless $(e.target).closest("#others_popup").length
|
||||
@$others.hide()
|
||||
@closeMenus() unless $(e.target).closest(".menus > li, #conversation_actions, #conversations .actions").length
|
||||
|
||||
@$menuViews = $('#menu_views')
|
||||
@$menuViewsList = @$menuViews.parent()
|
||||
@$menuViewsList.find('li a').click (e) =>
|
||||
@closeMenus()
|
||||
if scope = $(e.target).closest('li').data('scope')
|
||||
e.preventDefault()
|
||||
@updateHashData scope: scope
|
||||
|
||||
$('#conversations ul, #create_message_form').on 'click', '.others', (e) =>
|
||||
$this = $(e.currentTarget)
|
||||
$container = $this.closest('li').offsetParent()
|
||||
offset = $this.offsetFrom($container)
|
||||
@$others.empty().append($this.find('> span').clone()).css
|
||||
left: offset.left
|
||||
top: offset.top + $this.height() + $container.scrollTop()
|
||||
fontSize: $this.css('fontSize')
|
||||
$container.append(@$others.show())
|
||||
return false # i.e. don't select conversation
|
||||
|
||||
setScope: (scope) ->
|
||||
$items = @$menuViewsList.find('li')
|
||||
$items.removeClass('checked')
|
||||
$item = $items.filter("[data-scope=#{scope}]")
|
||||
$item = $items.filter("[data-scope=inbox]") unless $item.length
|
||||
$item.addClass('checked')
|
||||
@$menuViews.text $item.text()
|
||||
|
||||
initializeMessageActions: ->
|
||||
$('#message_actions').find('a').click (e) =>
|
||||
e.preventDefault()
|
||||
|
||||
$('#cancel_bulk_message_action').click =>
|
||||
@toggleMessageActions off
|
||||
|
||||
$('#action_delete').click (e) =>
|
||||
active = @conversations.active()
|
||||
return unless active?
|
||||
$selectedMessages = @$messageList.find('.selected')
|
||||
message = if $selectedMessages.length > 1
|
||||
I18n.t('confirm.delete_messages', "Are you sure you want to delete your copy of these messages? This action cannot be undone.")
|
||||
else
|
||||
I18n.t('confirm.delete_message', "Are you sure you want to delete your copy of this message? This action cannot be undone.")
|
||||
if confirm message
|
||||
$selectedMessages.fadeOut 'fast'
|
||||
@conversations.action $(e.currentTarget),
|
||||
conversationId: active.id
|
||||
data: {remove: ($(message).data('id') for message in $selectedMessages)}
|
||||
success: => @toggleMessageActions(off)
|
||||
error: => $selectedMessages.show()
|
||||
|
||||
$('#action_forward').click (e) =>
|
||||
return unless @conversations.active()?
|
||||
@$forwardForm.find("input[name!=authenticity_token], textarea").val('').change()
|
||||
$preview = @$forwardForm.find('ul.messages').first()
|
||||
$preview.html('')
|
||||
$preview.html(@$messageList.find('> li.selected.forwardable').clone(true).removeAttr('id').removeClass('self'))
|
||||
$preview.find('> li')
|
||||
.removeClass('selected odd')
|
||||
.find('> :checkbox')
|
||||
.attr('checked', true)
|
||||
.attr('name', 'forwarded_message_ids[]')
|
||||
.val ->
|
||||
$(this).closest('li').data('id')
|
||||
$preview.find('> li').last().addClass('last')
|
||||
@$forwardForm.css('max-height', ($(window).height() - 300) + 'px')
|
||||
.dialog
|
||||
position: 'center'
|
||||
height: 'auto'
|
||||
width: 510
|
||||
title: I18n.t('title.forward_messages', 'Forward Messages')
|
||||
buttons: [
|
||||
text: I18n.t('#buttons.cancel', 'Cancel')
|
||||
click: -> $(this).dialog('close')
|
||||
,
|
||||
text: I18n.t('buttons.send_message', 'Send')
|
||||
click: -> $(this).submit()
|
||||
class: 'btn-primary'
|
||||
]
|
||||
open: =>
|
||||
@$forwardForm.attr action: '/conversations?' + $.param(@conversations.baseData())
|
||||
close: =>
|
||||
$('#forward_recipients').data('token_input').$input.blur()
|
||||
|
||||
$('#action_compose_message').click (e) =>
|
||||
e.preventDefault()
|
||||
@updateHashData id: null
|
||||
|
||||
addRecipients: ($node) ->
|
||||
return unless @conversations.active()?
|
||||
@$addForm
|
||||
.attr('action', $node.prop('href'))
|
||||
.dialog
|
||||
width: 420
|
||||
title: I18n.t('title.add_recipients', 'Add Recipients')
|
||||
buttons: [
|
||||
{
|
||||
text: I18n.t('buttons.add_people', 'Add People')
|
||||
click: => @$addForm.submit()
|
||||
}
|
||||
{
|
||||
text: I18n.t('#buttons.cancel', 'Cancel')
|
||||
click: => @$addForm.dialog('close')
|
||||
}
|
||||
]
|
||||
open: =>
|
||||
tokenInput = $('#add_recipients').data('token_input')
|
||||
tokenInput.baseExclude = @conversations.active().get('audience')
|
||||
@$addForm.find("input[name!=authenticity_token]").val('').change()
|
||||
close: =>
|
||||
$('#add_recipients').data('token_input').$input.blur()
|
||||
|
||||
initializeTokenInputs: ($scope) ->
|
||||
everyoneText = I18n.t('enrollments_everyone', "Everyone")
|
||||
selectAllText = I18n.t('select_all', "Select All")
|
||||
|
||||
($scope ? $(document)).find('.recipients').contextSearch
|
||||
contexts: @contexts
|
||||
added: (data, $token, newToken) =>
|
||||
data.id = "#{data.id}"
|
||||
if newToken and data.rootId
|
||||
$token.append("<input type='hidden' name='tags[]' value='#{data.rootId}'>")
|
||||
if newToken and data.type
|
||||
$token.addClass(data.type)
|
||||
if data.user_count?
|
||||
$token.addClass('details')
|
||||
$details = $('<span />')
|
||||
$details.text(I18n.t('people_count', 'person', {count: data.user_count}))
|
||||
$token.append($details)
|
||||
$token.data('user_count', data.user_count)
|
||||
unless data.id.match(/^(course|group)_/)
|
||||
data = $.extend({}, data)
|
||||
delete data.avatar_url # since it's the wrong size and possibly a blank image
|
||||
currentData = @userCache[data.id] ? {}
|
||||
@userCache[data.id] = $.extend(currentData, data)
|
||||
canToggle: (data) ->
|
||||
data.type is 'user' or data.permissions?.send_messages_all
|
||||
selector:
|
||||
showToggles: true
|
||||
includeEveryoneOption: (postData, parent) =>
|
||||
# i.e. we are listing synthetic contexts under a course or section
|
||||
if postData.context?.match(/^(course|section)_\d+$/)
|
||||
everyoneText
|
||||
includeSelectAllOption: (postData, parent) =>
|
||||
# i.e. we are listing all users in a group or synthetic context
|
||||
if postData.context?.match(/^((course|section)_\d+_.*|group_\d+)$/) and not postData.context?.match(/^(course|section)_\d+$/) and not postData.context?.match(/^course_\d+_(groups|sections)$/) and parent.data('user_data').permissions.send_messages_all
|
||||
selectAllText
|
||||
baseData:
|
||||
permissions: ["send_messages_all"]
|
||||
messageable_only: true
|
||||
|
||||
return if $scope
|
||||
|
||||
@filterNameMap = {}
|
||||
$('#context_tags').contextSearch
|
||||
contexts: @contexts
|
||||
prefixUserIds: true
|
||||
added: (data, $token, newToken) =>
|
||||
$token.prevAll().remove() # only one token at a time
|
||||
tokenWrapBuffer: 80
|
||||
selector:
|
||||
includeEveryoneOption: (postData, parent) =>
|
||||
if postData.context?.match(/^course_\d+$/)
|
||||
everyoneText
|
||||
includeFilterOption: (postData) =>
|
||||
if postData.context?.match(/^course_\d+$/)
|
||||
I18n.t('filter_by_course', 'Filter by this course')
|
||||
else if postData.context?.match(/^group_\d+$/)
|
||||
I18n.t('filter_by_group', 'Filter by this group')
|
||||
baseData:
|
||||
synthetic_contexts: 1
|
||||
types: ['course', 'user', 'group']
|
||||
include_inactive: true
|
||||
blank_avatar_fallback: false
|
||||
filterInput = $('#context_tags').data('token_input')
|
||||
filterInput.change = (tokenValues) =>
|
||||
filters = for pair in filterInput.tokenPairs()
|
||||
@filterNameMap[pair[0]] = pair[1]
|
||||
pair[0]
|
||||
@updateHashData filter: filters
|
||||
|
||||
initializeConversationsPane: () ->
|
||||
@conversations = new ConversationsPane this, @$conversations
|
||||
|
||||
initializeMessageFormPane: () ->
|
||||
@formPane = new MessageFormPane(this, folderId: @options.FOLDER_ID)
|
||||
|
||||
addedMessageForm: ($form) ->
|
||||
@prepareTextareas($form.find('textarea'))
|
||||
@initializeTokenInputs($form)
|
||||
|
||||
initializeAutoResize: ->
|
||||
$(window).resize => @resize(50)
|
||||
@resize()
|
||||
|
||||
currentHashData: ->
|
||||
try
|
||||
data = $.parseJSON($.decodeFromHex(location.hash.substring(1))) || {}
|
||||
catch e
|
||||
data = {}
|
||||
data
|
||||
|
||||
updateView: (force = false) =>
|
||||
hash = location.hash
|
||||
data = @currentHashData()
|
||||
data.force = force
|
||||
if data.filter
|
||||
data.filter = (id for id in data.filter when @filterNameMap[id])
|
||||
return @updateHashData(filter: null) if not data.filter.length
|
||||
@setScope(data.scope)
|
||||
@conversations.updateView(data)
|
||||
|
||||
initializeHashChange: ->
|
||||
$(window).bind('hashchange', => @updateView()).triggerHandler('hashchange')
|
|
@ -1,167 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'compiled/util/shortcut'
|
||||
'jst/conversations/MessageForm'
|
||||
'jst/conversations/addAttachment'
|
||||
], (I18n, $, _, shortcut, messageFormTemplate, addAttachmentTemplate) ->
|
||||
|
||||
class MessageForm
|
||||
shortcut this, 'pane',
|
||||
'resize'
|
||||
|
||||
constructor: (@pane, @canAddNotesFor, @options) ->
|
||||
templateOptions = _.extend({}, @options, conversation: @options.conversation?.toJSON())
|
||||
@$form = $(messageFormTemplate(templateOptions))
|
||||
@$mediaComment = @$form.find('.media_comment')
|
||||
@$mediaCommentId = @$form.find("input[name=media_comment_id]")
|
||||
@$mediaCommentType = @$form.find("input[name=media_comment_type]")
|
||||
@$addMediaComment = @$form.find(".action_media_comment")
|
||||
@$attachments = @$form.find('.attachment_list')
|
||||
|
||||
initialize: ->
|
||||
if @tokenInput = @$form.find('.recipients').data('token_input')
|
||||
# since it doesn't infer percentage widths, just whatever the current pixels are
|
||||
@tokenInput.$fakeInput.css('width', '100%')
|
||||
if @options.user_id
|
||||
query = { user_id: @options.user_id, from_conversation_id: @options.from_conversation_id }
|
||||
$.ajaxJSON @tokenInput.selector.url, 'GET', query, (data) =>
|
||||
if data.length
|
||||
@tokenInput.addToken
|
||||
value: data[0].id
|
||||
text: data[0].name
|
||||
data: data[0]
|
||||
|
||||
@initializeActions()
|
||||
if !$(document.activeElement).filter(':input').length and window.location.hash isnt ''
|
||||
@$form.find(':input:visible:first').focus()
|
||||
|
||||
setAuthor: (messages, participants) ->
|
||||
@messageList or= messages.reverse()
|
||||
message = _.find(@messageList, (m) -> m.author_id isnt ENV.current_user_id)
|
||||
return unless message
|
||||
@messageAuthor = _.find(participants, (p) -> p.id == message.author_id)
|
||||
|
||||
initializeActions: ->
|
||||
if @tokenInput
|
||||
@tokenInput.change = @recipientIdsChanged
|
||||
|
||||
$('[type=submit]').on('click', ((e) => @replyToAuthor = true))
|
||||
$('[type=button]').on('click', ((e) => @$form.submit()))
|
||||
|
||||
@$form.formSubmit
|
||||
fileUpload: => (@$form.find(".file_input:visible").length > 0)
|
||||
preparedFileUpload: true
|
||||
context_code: "user_" + ENV.current_user_id
|
||||
folder_id: @options.folderId
|
||||
intent: 'message'
|
||||
formDataTarget: 'url'
|
||||
disableWhileLoading: true
|
||||
required: ['body']
|
||||
property_validations:
|
||||
token_capture: => I18n.t('errors.field_is_required', "This field is required") if @tokenInput and !@tokenInput.tokenValues().length
|
||||
handle_files: (attachments, data) ->
|
||||
data.attachment_ids = (a.attachment.id for a in attachments)
|
||||
data
|
||||
onSubmit: (@request, data) =>
|
||||
if !@messageAuthor and @pane.app.currentConversation
|
||||
conversation = @pane.app.currentConversation
|
||||
@setAuthor(conversation.messages, conversation.participants)
|
||||
if @options.conversation?.get('beta') and @replyToAuthor
|
||||
data['recipients[]'] = @messageAuthor.id
|
||||
@pane.addingMessage(@messageData(data), @request)
|
||||
@replyToAuthor = false
|
||||
true
|
||||
|
||||
recipientIdsChanged: (recipientIds) =>
|
||||
if recipientIds.length > 1 or recipientIds[0]?.match(/^(course|group)_/)
|
||||
@toggleOptions(user_note: off, group_conversation: on)
|
||||
else
|
||||
@toggleOptions(user_note: @canAddNotesFor(recipientIds[0]), group_conversation: off)
|
||||
@resize()
|
||||
|
||||
addAttachment: ->
|
||||
$attachment = $(addAttachmentTemplate())
|
||||
@$attachments.append($attachment)
|
||||
$attachment.slideDown "fast", => @resize() # shortcuts aren't bound to instance, so this don't "optimize" this :P
|
||||
|
||||
removeAttachment: ($node) ->
|
||||
$attachment = $node.closest(".attachment")
|
||||
$attachment.slideUp "fast", =>
|
||||
@resize()
|
||||
$attachment.remove()
|
||||
|
||||
addToken: (userData) ->
|
||||
input = @$form.find('.recipients').data('token_input')
|
||||
input.addToken(userData) if input
|
||||
|
||||
addMediaComment: ->
|
||||
@$mediaComment.mediaComment 'create', 'any', (id, type) =>
|
||||
@$mediaCommentId.val(id)
|
||||
@$mediaCommentType.val(type)
|
||||
@$mediaComment.show()
|
||||
@$addMediaComment.hide()
|
||||
|
||||
removeMediaComment: ->
|
||||
@$mediaCommentId.val('')
|
||||
@$mediaCommentType.val('')
|
||||
@$mediaComment.hide()
|
||||
@$addMediaComment.show()
|
||||
|
||||
messageData: (data) ->
|
||||
numRecipients = if @options.conversation
|
||||
if data['recipients[]']
|
||||
1
|
||||
else
|
||||
Math.max(@options.conversation.get('audience').length, 1)
|
||||
else
|
||||
# note: this number may be high, if users appear in multiple of the
|
||||
# specified recipient contexts. there's no way of knowing without going
|
||||
# to the server first, which is what we're trying to avoid.
|
||||
_.reduce @tokenInput.$tokens.find('input[name="recipients[]"]'),
|
||||
(memo, node) -> memo + ($(node).closest('li').data('user_count') ? 1),
|
||||
0
|
||||
{recipient_count: numRecipients, message: {body: data.body}}
|
||||
|
||||
resetForParticipant: (user) ->
|
||||
@toggleOptions(user_note: on) if @canAddNotesFor(user)
|
||||
|
||||
toggleOptions: (options) ->
|
||||
for key, enabled of options
|
||||
$node = @$form.find(".#{key}_info")
|
||||
$node.showIf(enabled)
|
||||
$node.find("input[type=checkbox][name=#{key}]").prop('checked', false) unless enabled
|
||||
|
||||
toggle: (state) ->
|
||||
@$form[if state then 'addClass' else 'removeClass']('disabled')
|
||||
|
||||
height: ->
|
||||
@$form.outerHeight(true)
|
||||
|
||||
refresh: (audienceHtml) ->
|
||||
@$form.find('.audience').html audienceHtml
|
||||
@resize()
|
||||
|
||||
destroy: ->
|
||||
@$form.hideErrors()
|
||||
@$form.css(position: 'absolute', zIndex: -1)
|
||||
$.when(@request).then => @$form.remove()
|
|
@ -1,76 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'compiled/util/shortcut'
|
||||
'compiled/conversations/MessageForm'
|
||||
'compiled/conversations/MessageProgressTracker'
|
||||
'compiled/fn/preventDefault'
|
||||
], (I18n, $, _, shortcut, MessageForm, MessageProgressTracker, preventDefault) ->
|
||||
|
||||
class MessageFormPane
|
||||
shortcut this, 'form',
|
||||
'refresh'
|
||||
'toggle'
|
||||
'resetForParticipant'
|
||||
shortcut this, 'app',
|
||||
'resize'
|
||||
|
||||
constructor: (@app, @formOptions) ->
|
||||
@$node = $('#create_message_form')
|
||||
@initializeActions()
|
||||
@tracker = new MessageProgressTracker(@app)
|
||||
@tracker.batchPoller()
|
||||
|
||||
height: ->
|
||||
(@form?.height() ? 0) + @tracker.height()
|
||||
|
||||
reset: (@options) ->
|
||||
@form?.destroy()
|
||||
@form = new MessageForm(this, @app.canAddNotesFor, _.defaults(@options, @formOptions))
|
||||
@$node.append(@form.$form)
|
||||
@app.addedMessageForm(@form.$form)
|
||||
@form.initialize()
|
||||
|
||||
initializeActions: ->
|
||||
@$node.click => @app.toggleMessageActions off
|
||||
|
||||
@$node.on 'click', '.action_add_attachment', preventDefault =>
|
||||
@form.addAttachment()
|
||||
@$node.on 'click', '.attachment a.remove_link', preventDefault (e) =>
|
||||
@form.removeAttachment($(e.currentTarget))
|
||||
|
||||
@$node.on 'click', '.action_media_comment', preventDefault =>
|
||||
@form.addMediaComment()
|
||||
@$node.on 'click', '.media_comment a.remove_link', preventDefault =>
|
||||
@form.removeMediaComment()
|
||||
|
||||
@$node.on 'click', '.action_add_recipients', preventDefault (e) =>
|
||||
@app.addRecipients($(e.currentTarget))
|
||||
|
||||
addingMessage: (data, deferred) ->
|
||||
@reset(@options)
|
||||
@tracker.track(data, deferred)
|
||||
|
||||
$.when(deferred).then (data) =>
|
||||
data = [data] unless data.length?
|
||||
@app.updatedConversation(data)
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'jst/conversations/MessageProgressBarText'
|
||||
'compiled/str/TextHelper'
|
||||
'jquery.ajaxJSON'
|
||||
], (I18n, $, _, messageProgressBarTextTemplate, {truncateText}) ->
|
||||
|
||||
class MessageProgressBar
|
||||
constructor: (@tracker, @data) ->
|
||||
@$node = $('<li class="progress-bar" />')
|
||||
messageId = _.uniqueId('progress_')
|
||||
@$message = $('<span />', id: messageId)
|
||||
@$bar = $('<div />',
|
||||
tabIndex: -1
|
||||
role: 'progressbar'
|
||||
'aria-valuemin': 0
|
||||
'aria-valuemax': 1
|
||||
'aria-valuenow': @data.completion
|
||||
'aria-describedby': messageId
|
||||
)
|
||||
@$completion = $('<b />').appendTo(@$bar)
|
||||
@$node.append(@$message, @$bar)
|
||||
@update(@data)
|
||||
|
||||
update: (@data) ->
|
||||
@data.status = if @data.error
|
||||
'error'
|
||||
else if @data.completion?
|
||||
if @data.completion is 1 then 'complete' else 'determinate'
|
||||
else
|
||||
'indeterminate'
|
||||
@data.num_people = I18n.t('people_count', 'person', {count: @data.recipient_count})
|
||||
@data.message_preview = truncateText(@data.message.body, max: 20)
|
||||
|
||||
@$node.attr('class', "progress-bar progress-bar-#{@data.status}")
|
||||
@$message.html messageProgressBarTextTemplate(@data)
|
||||
@$message.attr title: @data.message_preview
|
||||
@$bar.showIf(@data.status isnt 'error')
|
||||
percent = parseInt(100 * (@data.completion ? 0)) + "%"
|
||||
@$bar.attr('aria-valuenow', @data.completion)
|
||||
@$completion.css width: percent
|
||||
|
||||
error: (error) ->
|
||||
@update _.extend(@data, error: error, completion: 1)
|
||||
@destroy()
|
||||
|
||||
complete: () ->
|
||||
@update _.extend(@data, completion: 1)
|
||||
@destroy()
|
||||
|
||||
destroy: () ->
|
||||
setTimeout =>
|
||||
@$node.fadeTo('fast', 0).animate(width: 0, 'fast', => @$node.remove())
|
||||
, 5000
|
|
@ -1,87 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'compiled/conversations/MessageProgressBar'
|
||||
'jquery.ajaxJSON'
|
||||
], (I18n, $, _, MessageProgressBar) ->
|
||||
|
||||
class MessageProgressTracker
|
||||
constructor: (@app) ->
|
||||
@batchItems = {}
|
||||
@$list = $('#message_status')
|
||||
|
||||
track: (data, deferred) ->
|
||||
item = new MessageProgressBar(this, data)
|
||||
@$list.append(item.$node)
|
||||
item.$bar.focus()
|
||||
|
||||
# when the formSubmit deferred is done, we're done, unless this is a bulk
|
||||
# private message, in which case we kick of the poller/updater fu to
|
||||
# track its progress
|
||||
if deferred
|
||||
$.when(deferred).then (data, submitParam, xhr) =>
|
||||
if xhr.status is 202
|
||||
if batchId = xhr.getResponseHeader('X-Conversation-Batch-Id')
|
||||
@batchItems[batchId] = item
|
||||
@batchPoller() unless @polling
|
||||
else
|
||||
item.complete()
|
||||
, (data) =>
|
||||
if data.isRejected?() # e.g. refreshed the page, thus aborting the request
|
||||
item.complete()
|
||||
else
|
||||
error = if data[0]?.attribute is 'recipients' and data[0].message is 'invalid'
|
||||
I18n.t('recipient_error', 'The course or group you have selected has no valid recipients')
|
||||
else if data[0]?.attribute is 'attachment' and data[0].message is 'upload failed'
|
||||
I18n.t('attachment_error', 'Attachment failed to upload, please try again.')
|
||||
else
|
||||
I18n.t('unspecified_error', 'An unexpected error occurred, please try again')
|
||||
item.error(error)
|
||||
|
||||
item
|
||||
|
||||
batchPoller: =>
|
||||
@polling = true
|
||||
$.ajaxJSON '/conversations/batches', 'GET', {}, (data) =>
|
||||
@updateItems(data)
|
||||
if data.length > 0
|
||||
setTimeout(@batchPoller, 3000)
|
||||
else
|
||||
@polling = false
|
||||
|
||||
updateItems: (data) ->
|
||||
dataHash = _.reduce(data, (h, i) ->
|
||||
h[i.id] = i
|
||||
h
|
||||
, {})
|
||||
for id, data of dataHash
|
||||
@batchItems[id]?.update(data) ? @batchItems[id] = @track(data)
|
||||
|
||||
# remove stuff that has finished
|
||||
completed = for id, item of @batchItems when not dataHash[id]
|
||||
item.complete()
|
||||
delete @batchItems[id]
|
||||
if completed.length
|
||||
@app.updateView(true)
|
||||
|
||||
height: ->
|
||||
@$list.outerHeight(true)
|
|
@ -1,24 +0,0 @@
|
|||
define [
|
||||
'i18n!conversations'
|
||||
'jquery'
|
||||
'underscore'
|
||||
'str/htmlEscape'
|
||||
'compiled/util/listWithOthers'
|
||||
'jquery.instructure_misc_helpers'
|
||||
], (I18n, $, _, h, listWithOthers) ->
|
||||
|
||||
format = (person) ->
|
||||
str = h(person.name)
|
||||
str = "<span class='active-filter'>#{str}</span>" if person.activeFilter
|
||||
$.raw str
|
||||
|
||||
(audience, options={}) ->
|
||||
if options.highlightFilters
|
||||
audience = _.groupBy(audience, (user) -> user.activeFilter)
|
||||
audience = (audience[true] ? []).concat(audience[false] ? [])
|
||||
audience = (format(person) for person in audience)
|
||||
|
||||
if audience.length == 0
|
||||
"<span>#{h(I18n.t('notes_to_self', 'Monologue'))}</span>"
|
||||
else
|
||||
listWithOthers(audience)
|
|
@ -1,60 +0,0 @@
|
|||
define [
|
||||
'i18n!conversations_intro'
|
||||
'jquery'
|
||||
'compiled/widget/slideshow'
|
||||
'jquery.ajaxJSON'
|
||||
], (I18n, $, Slideshow) ->
|
||||
|
||||
->
|
||||
introSlideshow = new Slideshow('conversations_intro')
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide1', 'Slide 1'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/icon.png', 'icon')
|
||||
slide.addParagraph(I18n.t('slide1.paragraph1', 'Take a look at your Inbox!'), 'large')
|
||||
slide.addParagraph(I18n.t('slide1.paragraph2', 'Conversations—the new Canvas messaging system—has arrived!'), 'large')
|
||||
slide.addParagraph(I18n.t('slide1.paragraph3', 'Use Conversations to send a private message to a classmate or use Conversations to talk to an entire group of people.'), 'large_and_blue')
|
||||
slide.addParagraph(I18n.t('slide1.paragraph4', 'Ready for a short intro? Click the right arrow to get started.'), 'large')
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide2', 'Slide 2'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image2.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide2.paragraph1', 'All your conversations are shown on the left side.'))
|
||||
slide.addParagraph(I18n.t('slide2.paragraph2', 'You can see who the conversation is with, how many messages there are, and a few lines from the newest message in each conversation.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide3', 'Slide 3'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image3.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide3.paragraph1', 'Conversations can be marked as read/unread, starred, or archived using the "actions" button on the message.'))
|
||||
slide.addParagraph(I18n.t('slide3.paragraph2', 'Archived messages aren\'t deleted, they\'re just moved out of your inbox, so you can access them again if needed.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide4', 'Slide 4'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image4.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide4.paragraph1', 'When you select a conversation, all the messages for that conversation are shown in the panel on the right. When conversations get long, you can always scroll down to see earlier messages.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide5', 'Slide 5'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image5.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide5.paragraph1', 'To begin a message, start by clicking the Compose icon.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide6', 'Slide 6'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image6.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide6.paragraph1', 'In the New Message box, start typing the person, or group\'s name, and they\'ll show up in the dropdown. Alternatively, you can click the address book icon to find someone if you don\'t remember a name.'))
|
||||
slide.addParagraph(I18n.t('slide6.paragraph2', 'Type your message, click Send, and you\'re golden.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide7', 'Slide 7'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image7.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide7.paragraph1', 'You can send messages to one person or to a group of people. By default all group messages are available to everyone in the group.'))
|
||||
slide.addParagraph(I18n.t('slide7.paragraph2', 'If you wanted to send the message privately to all the recipients, uncheck the "This is a group conversation" checkbox.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide8', 'Slide 8'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image8.png', 'screenshot')
|
||||
slide.addParagraph(I18n.t('slide8.paragraph1', 'You can select one or more messages by clicking the checkbox on the right hand side.'))
|
||||
slide.addParagraph(I18n.t('slide8.paragraph2', 'Once selected, you can forward them to someone else, or delete them.'))
|
||||
|
||||
introSlideshow.addSlide I18n.t('titles.slide9', 'Slide 9'), (slide) ->
|
||||
slide.addImage('/images/conversations/intro/image9.png', 'screenshot', 'http://www.youtube.com/watch?v=NWqIaEyVWZM')
|
||||
slide.addParagraph(I18n.t('slide9.paragraph1', 'We think you\'ll find Conversations simple and easy to use.'))
|
||||
slide.addParagraph(I18n.t('slide9.paragraph2', 'Check out this short introduction video to see Conversations in action.'))
|
||||
slide.addParagraph(I18n.t('slide9.paragraph3', 'Your old messages have been organized into Conversations for you. So what are you waiting for? Get started!'))
|
||||
|
||||
introSlideshow.dom.bind 'dialogclose', ->
|
||||
$.ajaxJSON '/conversations/watched_intro', 'POST', {}
|
||||
|
||||
introSlideshow.start()
|
|
@ -1220,7 +1220,7 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :calendar_url_for, :files_url_for
|
||||
|
||||
def conversations_path(params={})
|
||||
if @current_user and @current_user.preferences[:use_new_conversations]
|
||||
if @current_user and @current_user.use_new_conversations?
|
||||
query_string = params.slice(:context_id, :user_id, :user_name).inject([]) do |res, (k, v)|
|
||||
res << "#{k}=#{v}"
|
||||
res
|
||||
|
|
|
@ -144,9 +144,8 @@ class ConversationsController < ApplicationController
|
|||
# filtering conversations that at have at least all of the contexts ("and")
|
||||
# or at least one of the contexts ("or")
|
||||
#
|
||||
# @argument interleave_submissions [Boolean] Default is false. If true, the
|
||||
# message_count will also include these submission-based messages in the
|
||||
# total. See the show action for more information.
|
||||
# @argument interleave_submissions [Boolean] (Obsolete) Submissions are no
|
||||
# longer linked to conversations. This parameter is ignored.
|
||||
#
|
||||
# @argument include_all_conversation_ids [Boolean] Default is false. If true,
|
||||
# the top-level element of the response will be an object rather than
|
||||
|
@ -232,37 +231,18 @@ class ConversationsController < ApplicationController
|
|||
load_all_contexts :permissions => [:manage_user_notes]
|
||||
notes_enabled = @current_user.associated_accounts.any?{|a| a.enable_user_notes }
|
||||
can_add_notes_for_account = notes_enabled && @current_user.associated_accounts.any?{|a| a.grants_right?(@current_user, nil, :manage_students) }
|
||||
if @current_user.use_new_conversations?
|
||||
js_env(:CONVERSATIONS => {
|
||||
:ATTACHMENTS_FOLDER_ID => @current_user.conversation_attachments_folder.id,
|
||||
:ACCOUNT_CONTEXT_CODE => "account_#{@domain_root_account.id}",
|
||||
:CONTEXTS => @contexts,
|
||||
:NOTES_ENABLED => notes_enabled,
|
||||
:CAN_ADD_NOTES_FOR_ACCOUNT => can_add_notes_for_account,
|
||||
})
|
||||
return render :template => 'conversations/index_new'
|
||||
else
|
||||
@current_user.reset_unread_conversations_counter
|
||||
current_user_json = conversation_user_json(@current_user, @current_user, session, :include_participant_avatars => true)
|
||||
current_user_json[:id] = current_user_json[:id].to_s
|
||||
hash = {:CONVERSATIONS => {
|
||||
:USER => current_user_json,
|
||||
:CONTEXTS => @contexts,
|
||||
:NOTES_ENABLED => notes_enabled,
|
||||
:CAN_ADD_NOTES_FOR_ACCOUNT => can_add_notes_for_account,
|
||||
:SHOW_INTRO => !@current_user.watched_conversations_intro?,
|
||||
:FOLDER_ID => @current_user.conversation_attachments_folder.id,
|
||||
:MEDIA_COMMENTS_ENABLED => feature_enabled?(:kaltura),
|
||||
}, :CONTEXT_ACTION_SOURCE => :conversation}
|
||||
append_sis_data(hash)
|
||||
js_env(hash)
|
||||
end
|
||||
js_env(:CONVERSATIONS => {
|
||||
:ATTACHMENTS_FOLDER_ID => @current_user.conversation_attachments_folder.id,
|
||||
:ACCOUNT_CONTEXT_CODE => "account_#{@domain_root_account.id}",
|
||||
:CONTEXTS => @contexts,
|
||||
:NOTES_ENABLED => notes_enabled,
|
||||
:CAN_ADD_NOTES_FOR_ACCOUNT => can_add_notes_for_account,
|
||||
})
|
||||
return render :template => 'conversations/index_new'
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_new_conversations
|
||||
@current_user.preferences[:use_new_conversations] = value_to_boolean(params[:use_new_conversations])
|
||||
@current_user.save!
|
||||
redirect_to action: 'index'
|
||||
end
|
||||
|
||||
|
@ -399,14 +379,11 @@ class ConversationsController < ApplicationController
|
|||
|
||||
# @API Get a single conversation
|
||||
# Returns information for a single conversation. Response includes all
|
||||
# fields that are present in the list/index action, as well as messages,
|
||||
# submissions, and extended participant information.
|
||||
# fields that are present in the list/index action as well as messages
|
||||
# and extended participant information.
|
||||
#
|
||||
# @argument interleave_submissions [Boolean] Default false. If true,
|
||||
# submission data will be returned as first class messages interleaved
|
||||
# with other messages. The submission details (comments, assignment, etc.)
|
||||
# will be stored as the submission property on the message. Note that if
|
||||
# set, the message_count will also include these messages in the total.
|
||||
# @argument interleave_submissions [Boolean] (Obsolete) Submissions are no
|
||||
# longer linked to conversations. This parameter is ignored.
|
||||
#
|
||||
# @argument scope [Optional, String, "unread"|"starred"|"archived"]
|
||||
# Used when generating "visible" in the API response. See the explanation
|
||||
|
@ -436,11 +413,9 @@ class ConversationsController < ApplicationController
|
|||
# media_comment:: Audio/video comment data for this message (if applicable). Fields include: display_name, content-type, media_id, media_type, url
|
||||
# forwarded_messages:: If this message contains forwarded messages, they will be included here (same format as this list). Note that those messages may have forwarded messages of their own, etc.
|
||||
# attachments:: Array of attachments for this message. Fields include: display_name, content-type, filename, url
|
||||
# @response_field submissions Array of assignment submissions having
|
||||
# comments relevant to this conversation. These should be interleaved with
|
||||
# the messages when displaying to the user. See the {api:SubmissionsApiController#index Submissions API documentation}
|
||||
# for details on the fields included. This response includes
|
||||
# the submission_comments and assignment associations.
|
||||
# @response_field submissions (Obsolete) Array of assignment submissions having
|
||||
# comments relevant to this conversation. Submissions are no longer linked to conversations.
|
||||
# This field will always be nil or empty.
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
|
@ -508,24 +483,17 @@ class ConversationsController < ApplicationController
|
|||
end
|
||||
|
||||
@conversation.update_attribute(:workflow_state, "read") if @conversation.unread? && auto_mark_as_read?
|
||||
messages = submissions = nil
|
||||
messages = nil
|
||||
Shackles.activate(:slave) do
|
||||
messages = @conversation.messages
|
||||
ConversationMessage.send(:preload_associations, messages, :asset)
|
||||
submissions = messages.map(&:submission).compact
|
||||
Submission.send(:preload_associations, submissions, [:assignment, :submission_comments])
|
||||
if interleave_submissions
|
||||
submissions = nil
|
||||
else
|
||||
messages = messages.select{ |message| message.submission.nil? }
|
||||
end
|
||||
end
|
||||
render :json => conversation_json(@conversation,
|
||||
@current_user,
|
||||
session,
|
||||
include_indirect_participants: true,
|
||||
messages: messages,
|
||||
submissions: submissions,
|
||||
submissions: [],
|
||||
include_beta: params[:include_beta],
|
||||
include_context_name: true)
|
||||
end
|
||||
|
@ -629,7 +597,7 @@ class ConversationsController < ApplicationController
|
|||
|
||||
# @API Add recipients
|
||||
# Add recipients to an existing group conversation. Response is similar to
|
||||
# the GET/show action, except that omits submissions and only includes the
|
||||
# the GET/show action, except that only includes the
|
||||
# latest message (e.g. "joe was added to the conversation by bob")
|
||||
#
|
||||
# @argument recipients[] [String]
|
||||
|
@ -679,7 +647,7 @@ class ConversationsController < ApplicationController
|
|||
|
||||
# @API Add a message
|
||||
# Add a message to an existing conversation. Response is similar to the
|
||||
# GET/show action, except that omits submissions and only includes the
|
||||
# GET/show action, except that only includes the
|
||||
# latest message (i.e. what we just sent)
|
||||
#
|
||||
# @argument body [String]
|
||||
|
@ -1028,9 +996,9 @@ class ConversationsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# TODO API v2: default to true, like we do in the UI
|
||||
# Obsolete. Forced to false until we go through and clean it up thoroughly
|
||||
def interleave_submissions
|
||||
value_to_boolean(params[:interleave_submissions]) || !api_request?
|
||||
false
|
||||
end
|
||||
|
||||
def include_private_conversation_enrollments
|
||||
|
|
|
@ -119,84 +119,6 @@ class Conversation < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# ==== Arguments
|
||||
# * <tt>asset</tt> - The asset with conversation_messages to update.
|
||||
# * <tt>options</tt> - Options for special behavior.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:delete_all</tt> - Boolean option. If +true+, all of the asset's conversation messages are destroyed.
|
||||
# * <tt>:only_existing</tt> - Boolean option. If +true+, only existing ones are updated. No new ones are created.
|
||||
# Additional options are passed on further but not directly used here.
|
||||
# * <tt>:update_participants</tt> - Boolean option.
|
||||
# * <tt>:skip_users</tt> - Array of users to skip.
|
||||
# * <tt>:recalculate_count</tt> - Boolean
|
||||
# * <tt>:recalculate_last_authored_at</tt> - Boolean
|
||||
def self.update_all_for_asset(asset, options)
|
||||
transaction do
|
||||
asset.lock!
|
||||
if options[:delete_all]
|
||||
asset.conversation_messages.destroy_all
|
||||
return
|
||||
end
|
||||
|
||||
groups = asset.conversation_groups
|
||||
|
||||
conversations = if groups.empty?
|
||||
[]
|
||||
elsif options[:only_existing]
|
||||
groups.first.first.shard.activate do
|
||||
conversation_ids = ConversationParticipant.select(:conversation_id).uniq.
|
||||
where(:private_hash => groups.map { |g|
|
||||
private_hash_for(g)}).map(&:conversation_id)
|
||||
if conversation_ids.empty?
|
||||
[]
|
||||
else
|
||||
find_all_by_id(conversation_ids, :lock => true)
|
||||
end
|
||||
end
|
||||
else
|
||||
groups.map{ |g| initiate(g, true) }.each(&:lock!)
|
||||
end
|
||||
|
||||
current_messages = conversations.map{ |c| c.update_for_asset(asset, options) }
|
||||
|
||||
# delete asset messages from obsolete conversations (e.g. once the first
|
||||
# instructor comments on a submission, remove it from conversations
|
||||
# between the submitter and other instructors)
|
||||
(asset.conversation_messages - current_messages).each(&:destroy)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# ==== Arguments
|
||||
# * <tt>asset</tt> - The asset with conversation_messages to update.
|
||||
# * <tt>options</tt> - Options for special behavior.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:update_participants</tt> - Boolean option.
|
||||
def update_for_asset(asset, options)
|
||||
message = asset.conversation_messages.detect { |m| m.conversation_id == id }
|
||||
if message
|
||||
add_message_to_participants(message, options) # make sure it gets re-added
|
||||
else
|
||||
message = add_message(asset.user, '', options.merge(:asset => asset, :update_participants => false, :root_account_id => asset.context.try(:root_account_id)))
|
||||
end
|
||||
if (data = asset.conversation_message_data).present?
|
||||
message.created_at = data[:created_at]
|
||||
message.author = data[:author]
|
||||
message.body = data[:body]
|
||||
message.save!
|
||||
end
|
||||
|
||||
if options[:update_participants]
|
||||
update_participants message, options
|
||||
else
|
||||
conversation_participants.each{ |cp| cp.update_cached_data!(options.merge(:set_last_message_at => false)) }
|
||||
end
|
||||
message
|
||||
end
|
||||
|
||||
def add_participants(current_user, users, options={})
|
||||
self.shard.activate do
|
||||
user_ids = users.map(&:id).uniq
|
||||
|
|
|
@ -33,7 +33,9 @@ class ConversationMessage < ActiveRecord::Base
|
|||
has_many :conversation_message_participants
|
||||
has_many :attachment_associations, :as => :context
|
||||
has_many :attachments, :through => :attachment_associations, :order => 'attachments.created_at, attachments.id'
|
||||
belongs_to :asset, :polymorphic => true, :types => :submission # TODO: move media comments into this
|
||||
# we used to attach submission comments to conversations via this asset
|
||||
# TODO: remove this column when we're sure we don't want this relation anymore
|
||||
belongs_to :asset, :polymorphic => true, :types => :submission
|
||||
delegate :participants, :to => :conversation
|
||||
delegate :subscribed_participants, :to => :conversation
|
||||
attr_accessible
|
||||
|
|
|
@ -248,11 +248,7 @@ class ConversationParticipant < ActiveRecord::Base
|
|||
Rails.cache.fetch([conversation, user, 'participants', options].cache_key) do
|
||||
participants = conversation.participants
|
||||
if options[:include_indirect_participants]
|
||||
user_ids =
|
||||
messages.map(&:all_forwarded_messages).flatten.map(&:author_id) |
|
||||
messages.map{
|
||||
|m| m.submission.submission_comments.map(&:author_id) if m.submission
|
||||
}.compact.flatten
|
||||
user_ids = messages.map(&:all_forwarded_messages).flatten.map(&:author_id)
|
||||
user_ids -= participants.map(&:id)
|
||||
participants += Shackles.activate(:slave) { MessageableUser.available.where(:id => user_ids).all }
|
||||
end
|
||||
|
|
|
@ -37,7 +37,12 @@ class Submission < ActiveRecord::Base
|
|||
has_many :rubric_assessments, :as => :artifact
|
||||
has_many :attachment_associations, :as => :context
|
||||
has_many :attachments, :through => :attachment_associations
|
||||
|
||||
# we no longer link submission comments and conversations, but we haven't fixed up existing
|
||||
# linked conversations so this relation might be useful
|
||||
# TODO: remove this when removing the conversationmessage asset columns
|
||||
has_many :conversation_messages, :as => :asset # one message per private conversation
|
||||
|
||||
has_many :content_participations, :as => :content
|
||||
|
||||
EXPORTABLE_ATTRIBUTES = [
|
||||
|
@ -49,7 +54,7 @@ class Submission < ActiveRecord::Base
|
|||
|
||||
EXPORTABLE_ASSOCIATIONS = [
|
||||
:attachment, :assignment, :user, :grader, :group, :media_object, :student, :submission_comments, :assessment_requests, :assigned_assessments, :quiz_submission,
|
||||
:rubric_assessment, :rubric_assessments, :attachments, :conversation_messages, :content_participations
|
||||
:rubric_assessment, :rubric_assessments, :attachments, :content_participations
|
||||
]
|
||||
|
||||
serialize :turnitin_data, Hash
|
||||
|
@ -854,19 +859,6 @@ class Submission < ActiveRecord::Base
|
|||
comment
|
||||
end
|
||||
|
||||
def conversation_groups
|
||||
participating_instructors.map{ |i| [user, i] }
|
||||
end
|
||||
|
||||
def conversation_message_data
|
||||
latest = visible_submission_comments.where(:author_id => possible_participants_ids).last or return
|
||||
{
|
||||
:created_at => latest.created_at,
|
||||
:author => latest.author,
|
||||
:body => latest.comment
|
||||
}
|
||||
end
|
||||
|
||||
def comment_authors
|
||||
visible_submission_comments(:include => :author).map(&:author)
|
||||
end
|
||||
|
@ -883,55 +875,6 @@ class Submission < ActiveRecord::Base
|
|||
[user_id] + context.participating_instructors.uniq.map(&:id)
|
||||
end
|
||||
|
||||
# ensure that conversations/messages are created/updated for all relevant
|
||||
# participants as submission comments are added/removed. there should be a
|
||||
# conversation between the submitter and each participating admin, and it
|
||||
# should have a single conversation_message that represents the submission
|
||||
# (there may of course be other regular messages in the conversation)
|
||||
#
|
||||
# ==== Arguments
|
||||
# * <tt>trigger</tt> - Values of :create, :destroy, :migrate are supported.
|
||||
# * <tt>overrides</tt> - Hash of overrides that can be passed through when
|
||||
# updating the conversation.
|
||||
#
|
||||
# ==== Overrides
|
||||
# * <tt>:skip_users</tt> - Gets passed through to <tt>Conversation</tt>.<tt>update_all_for_asset</tt>.
|
||||
# nil by default, which means mark-as-unread for
|
||||
# everyone but the author.
|
||||
def create_or_update_conversations!(trigger, overrides={})
|
||||
options = {}
|
||||
case trigger
|
||||
when :create
|
||||
options[:update_participants] = true
|
||||
options[:update_for_skips] = false
|
||||
options[:skip_users] = overrides[:skip_users] || [conversation_message_data[:author]] # don't mark-as-unread for the author
|
||||
options[:skip_users] << user if user.preferences[:use_new_conversations]
|
||||
participating_instructors.each do |t|
|
||||
# Check their settings and add to :skip_users if set to suppress.
|
||||
if t.preferences[:no_submission_comments_inbox] == true ||
|
||||
t.preferences[:use_new_conversations]
|
||||
options[:skip_users] << t
|
||||
end
|
||||
end
|
||||
when :destroy
|
||||
options[:delete_all] = visible_submission_comments.empty?
|
||||
options[:only_existing] = true
|
||||
when :migrate # don't mark-as-unread for anybody or add to empty conversations
|
||||
return unless conversation_message_data
|
||||
options[:recalculate_count] = true
|
||||
options[:recalculate_last_authored_at] = true
|
||||
options[:only_existing] = true
|
||||
end
|
||||
|
||||
Conversation.update_all_for_asset(self, options)
|
||||
end
|
||||
|
||||
def self.batch_migrate_conversations!(ids)
|
||||
find_all_by_id(ids).each do |sub|
|
||||
sub.create_or_update_conversations!(:migrate)
|
||||
end
|
||||
end
|
||||
|
||||
def limit_comments(user, session=nil)
|
||||
@comment_limiting_user = user
|
||||
@comment_limiting_session = session
|
||||
|
|
|
@ -46,8 +46,6 @@ class SubmissionComment < ActiveRecord::Base
|
|||
after_save :check_for_media_object
|
||||
after_destroy :delete_other_comments_in_this_group
|
||||
after_create :update_participants
|
||||
after_create { |c| c.submission.create_or_update_conversations!(:create) if c.send_to_conversations? }
|
||||
after_destroy { |c| c.submission.create_or_update_conversations!(:destroy) if c.send_to_conversations? }
|
||||
|
||||
serialize :cached_attachments
|
||||
|
||||
|
@ -220,10 +218,6 @@ class SubmissionComment < ActiveRecord::Base
|
|||
"/images/users/#{User.avatar_key(self.author_id)}"
|
||||
end
|
||||
|
||||
def send_to_conversations?
|
||||
!hidden? && submission.possible_participants_ids.include?(author_id)
|
||||
end
|
||||
|
||||
def serialization_methods
|
||||
context.root_account.service_enabled?(:avatars) ? [:avatar_path] : []
|
||||
end
|
||||
|
|
|
@ -1420,7 +1420,7 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def use_new_conversations?
|
||||
preferences[:use_new_conversations] == true
|
||||
true
|
||||
end
|
||||
|
||||
def ignore_item!(asset, purpose, permanent = false)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,218 +0,0 @@
|
|||
<% @body_classes << "full-width" %>
|
||||
|
||||
<% content_for :page_title, t(:page_title, "Conversations") %>
|
||||
|
||||
<% content_for :auto_discovery do %>
|
||||
<% if @current_user %>
|
||||
<%= auto_discovery_link_tag(:atom, feeds_conversation_format_path(@current_user.feed_code, :atom), {:title => t('titles.rss.conversations', "Conversations Atom Feed")}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% js_bundle :conversations %>
|
||||
<% jammit_css :conversations %>
|
||||
|
||||
<div id="inbox">
|
||||
<div id="conversations">
|
||||
<div id="actions">
|
||||
<ul class="buttons">
|
||||
<% unless @disallow_messages %>
|
||||
<li>
|
||||
<a href="#"
|
||||
id="action_compose_message"
|
||||
title="<%= t('titles.compose_new_message', 'Write a new message') %>"
|
||||
data-track-category="Compose Message"
|
||||
data-track-action="Click"
|
||||
data-track-label="New Message Symbol"
|
||||
style="background-image: url(/images/messages/compose-button-sprite.png)"><%= t('links.compose_new_message', 'New Message') %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<ul class="menus">
|
||||
<li><a href="#" id="menu_views"></a>
|
||||
<span></span>
|
||||
<div>
|
||||
<ul class="first">
|
||||
<li><b><%= t(:inbox_view, "VIEW") %></b></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li data-scope="inbox"
|
||||
data-track-category="Combo_Box"
|
||||
data-track-action="Select_Folder"
|
||||
data-track-label="Inbox"><%= link_to t('inbox_views.inbox', "Inbox"), '#' %></li>
|
||||
<li data-scope="unread"
|
||||
data-track-category="Combo_Box"
|
||||
data-track-action="Select_Folder"
|
||||
data-track-label="Unread"><%= link_to t('inbox_views.unread_messages', "Unread"), '#' %></li>
|
||||
<li data-scope="starred"
|
||||
data-track-category="Combo_Box"
|
||||
data-track-action="Select_Folder"
|
||||
data-track-label="Starred"><%= link_to t('inbox_views.starred_messages', "Starred"), '#' %></li>
|
||||
<li data-scope="sent"
|
||||
data-track-category="Combo_Box"
|
||||
data-track-action="Select_Folder"
|
||||
data-track-label="Sent"><%= link_to t('inbox_views.sent_messages', "Sent"), '#' %></li>
|
||||
</ul>
|
||||
<ul class="last">
|
||||
<li data-scope="archived"
|
||||
data-track-category="Combo_Box"
|
||||
data-track-action="Select_Folder"
|
||||
data-track-label="Archived"><%= link_to t('inbox_views.archived_messages', "Archived"), '#' %></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="context_tags_filter">
|
||||
<label for="context_tags">
|
||||
<%= before_label :filter, "Filter" %>
|
||||
</label>
|
||||
<%= text_field_tag :context_tags, nil, 'data-finder_url' => search_recipients_url %>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="conversations"><ul></ul></div>
|
||||
<p id="no_messages" style="display:none"></p>
|
||||
</div>
|
||||
<div id="messages">
|
||||
<div aria-live="polite">
|
||||
<div id="message_actions">
|
||||
<ul>
|
||||
<li><%= link_to t('inbox_actions.forward', "Forward"), {}, :id => :action_forward %></li>
|
||||
<li><%= link_to t('inbox_actions.delete', "Delete"), conversation_remove_messages_url('{{ id }}'), :id => :action_delete %></li>
|
||||
</ul>
|
||||
<a href="#" id="cancel_bulk_message_action"><%= t '#buttons.cancel', 'Cancel' %></a>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="message_status"></ul>
|
||||
<div role="application">
|
||||
<a id="help-btn"
|
||||
class="al-trigger"
|
||||
data-track-category="Help"
|
||||
data-track-action="Click"
|
||||
data-track-label="Question Mark"
|
||||
href="#"
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-owns="help-menu-items">
|
||||
<i class="icon-question"></i>
|
||||
<i class="icon-mini-arrow-down"></i>
|
||||
<span class="screenreader-only"><%= t 'help', "Help" %></span>
|
||||
</a>
|
||||
|
||||
<ul id="help-menu-items"
|
||||
class="al-options"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
aria-hidden="true"
|
||||
aria-expanded="false"
|
||||
aria-activedescendant="new-conversations-opt-in-menu-item">
|
||||
<li role="presentation">
|
||||
<a id="try-new-conversations-menu-item" href="/conversations/toggle_new_conversations?use_new_conversations=1" data-method="post" rel="nofollow" tabindex="-1" role="menuitem" title="<%= t 'try_new_conversations', "Try New Conversations" %>"><%= t 'try_new_conversations', "Try New Conversations" %></a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#" id="conversations-intro-menu-item" tabindex="-1" role="menuitem" title="<%= t 'conversations_intro', "Conversations Intro" %>"><%= t 'conversations_intro', "Conversations Intro" %></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="create_message_form"></div>
|
||||
<ul class="messages">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spin_holder"></div>
|
||||
</div>
|
||||
|
||||
<div id="menu-wrapper"></div>
|
||||
|
||||
<ul style="display:none">
|
||||
<li id="message_blank"
|
||||
data-track-category="Individual Message"
|
||||
data-track-action="Click"
|
||||
data-track-label="Message Block"
|
||||
class="message">
|
||||
<span class="date"></span>
|
||||
<b class="audience"></b>
|
||||
<span class="actions">
|
||||
<a href="#"
|
||||
data-track-category="Individual Message"
|
||||
data-track-action="Click"
|
||||
data-track-label="New Message..."
|
||||
class="send_private_message"><%= t :send_private_message, "New message..." %></a>
|
||||
</span>
|
||||
<p></p>
|
||||
<input type="checkbox"
|
||||
data-track-category="Individual Message"
|
||||
data-track-action="Click"
|
||||
data-track-label="Check Box"
|
||||
/>
|
||||
<ul class='message_attachments'>
|
||||
<li class='media_object_blank'>
|
||||
<a href="#" class="instructure_inline_media_comment media_comment no-underline" title="<%= t('titles.play_media_comment', "Play media comment") %>">
|
||||
<%= image_tag "messages/media-gray.png" %>
|
||||
<span class="media_comment_id" style="display: none;"></span>
|
||||
<span class='title'></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class='attachment_blank'>
|
||||
<a href="#" title="<%= t('titles.download_attachment', "Download attachment") %>">
|
||||
<%= image_tag "messages/attach-gray.png" %>
|
||||
<span class='title'></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="submission_blank" class="submission">
|
||||
<ul>
|
||||
<li class="header">
|
||||
<span class="submission_date"><%= before_label('submitted', "Submitted") %> <span class="date"> </span></span>
|
||||
<a target="_blank"
|
||||
data-track-category="Individual Message"
|
||||
data-track-action="Click"
|
||||
data-track-label="Open message in new window"
|
||||
href="<%= course_assignment_submission_path(:course_id => '{{ course_id }}',
|
||||
:assignment_id => '{{ assignment_id }}',
|
||||
:id => '{{ id }}') %>"
|
||||
style="text-decoration: none;"
|
||||
title="<%= t('titles.view_submission', "Open submission in new window.") %>">
|
||||
<b class="title"></b>
|
||||
</a>
|
||||
<span class="audience" title="<%= t('titles.submission_author', "Submission Author") %>"></span>
|
||||
<span class="score" title="<%= t('titles.submission_score', "Submission Score") %>"></span>
|
||||
<div class="clear"></div>
|
||||
</li>
|
||||
<li class="comment">
|
||||
<span class="date"></span>
|
||||
<b class="audience"></b>
|
||||
<p></p>
|
||||
<div class="clear"></div>
|
||||
</li>
|
||||
<li class="more">
|
||||
<a href="#">
|
||||
<%= t('more_messages', "%{count} more...", :count => raw('<span class="hidden"></span>')) %>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<input type="checkbox" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form id="add_recipients_form" method="post">
|
||||
<p>
|
||||
<%= t :add_recipients_instructions, "People you add to the conversation will see all previous messages." %>
|
||||
</p>
|
||||
<%= text_field_tag :recipients, nil, :id => 'add_recipients', 'data-finder_url' => search_recipients_url, :class => "recipients", :style => "width: 370px" %>
|
||||
</form>
|
||||
|
||||
<%= form_tag conversations_url, :id => 'forward_message_form' do %>
|
||||
<table>
|
||||
<tr id="recipient_info">
|
||||
<th><%= label_tag :forward_recipients, :to, :en => "To", :before => true %></th>
|
||||
<td>
|
||||
<%= text_field_tag :recipients, nil, :id => :forward_recipients,
|
||||
'data-finder_url' => search_recipients_url,
|
||||
:class => "recipients" %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th><%= label_tag :body, :message, :en => "Message", :before => true %></th><td><%= text_area_tag :body, nil, :id => :forward_body %></td></tr>
|
||||
</table>
|
||||
<ul class="messages"></ul>
|
||||
<input type="hidden" name="group_conversation" value="1" />
|
||||
<% end %>
|
|
@ -86,37 +86,6 @@
|
|||
<div id="sending-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="admin-link pull-right" role="application">
|
||||
<a id="help-btn"
|
||||
class="al-trigger help-btn"
|
||||
href="#"
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-owns="help-menu-items">
|
||||
<i class="icon-question"></i>
|
||||
<i class="icon-mini-arrow-down"></i>
|
||||
<span class="screenreader-only"><%= t(:help, "Help") %></span>
|
||||
</a>
|
||||
|
||||
<ul id="help-menu-items"
|
||||
class="al-options"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
aria-hidden="true"
|
||||
aria-expanded="false"
|
||||
aria-activedescendant="switch-to-old-conversations-menu-item">
|
||||
<li role="presentation">
|
||||
<a id="switch-to-old-conversations-menu-item"
|
||||
href="/conversations/toggle_new_conversations"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
tabindex="-1"
|
||||
role="menuitem"
|
||||
title="<%= t(:switch_to_old_conversations, "Switch Back to Old Conversations") %>"><%= t(:switch_to_old_conversations, "Switch Back to Old Conversations")%></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div role="search" class="search pull-right form-search">
|
||||
<div class="ac" id="search-autocomplete">
|
||||
<div class="ac-input-box">
|
||||
|
|
|
@ -75,10 +75,6 @@ module Mutable
|
|||
[submission, comments.map(&:author_id).uniq.size == 1 ? [comments.last.author] : []]
|
||||
}.compact
|
||||
SubmissionComment.where(:hidden => true, :submission_id => submissions).update_all(:hidden => false)
|
||||
Submission.send(:preload_associations, outstanding.map(&:first), :visible_submission_comments)
|
||||
outstanding.each do |submission, skip_users|
|
||||
submission.create_or_update_conversations!(:create, :skip_users => skip_users)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -224,7 +224,7 @@ describe ConversationsController, type: :request do
|
|||
{ :controller => 'conversations', :action => 'show', :id => @c3.conversation.id.to_s, :format => 'json' })
|
||||
json["context_name"].should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "filtering by tags" do
|
||||
|
@ -1059,64 +1059,19 @@ describe ConversationsController, type: :request do
|
|||
json["starred"].should be_true
|
||||
end
|
||||
|
||||
context "submission comments" do
|
||||
before do
|
||||
submission1 = submission_model(:course => @course, :user => @bob)
|
||||
submission2 = submission_model(:course => @course, :user => @bob)
|
||||
conversation(@bob)
|
||||
submission1.add_comment(:comment => "hey bob", :author => @me)
|
||||
submission1.add_comment(:comment => "wut up teacher", :author => @bob)
|
||||
submission2.add_comment(:comment => "my name is bob", :author => @bob)
|
||||
end
|
||||
it "should not link submission comments and conversations anymore" do
|
||||
submission1 = submission_model(:course => @course, :user => @bob)
|
||||
submission2 = submission_model(:course => @course, :user => @bob)
|
||||
conversation(@bob)
|
||||
submission1.add_comment(:comment => "hey bob", :author => @me)
|
||||
submission1.add_comment(:comment => "wut up teacher", :author => @bob)
|
||||
submission2.add_comment(:comment => "my name is bob", :author => @bob)
|
||||
|
||||
it "should return submission and comments with the conversation in api format" do
|
||||
json = api_call(:get, "/api/v1/conversations/#{@conversation.conversation_id}",
|
||||
{ :controller => 'conversations', :action => 'show', :id => @conversation.conversation_id.to_s, :format => 'json' })
|
||||
|
||||
json['messages'].size.should == 1
|
||||
json['submissions'].size.should == 2
|
||||
jsub = json['submissions'][1]
|
||||
jsub['assignment'].should be_present # includes & ['assignment']
|
||||
jcom = jsub['submission_comments']
|
||||
jcom.should be_present # includes & ['submission_comments']
|
||||
jcom.size.should == 2
|
||||
jcom[0]['author_id'].should == @me.id
|
||||
jcom[1]['author_id'].should == @bob.id
|
||||
|
||||
jsub = json['submissions'][0]
|
||||
jcom = jsub['submission_comments']
|
||||
jcom.size.should == 1
|
||||
jcom[0]['author_id'].should == @bob.id
|
||||
end
|
||||
|
||||
it "should interleave submission and comments in the conversation" do
|
||||
@conversation.add_message("another message!")
|
||||
|
||||
json = api_call(:get, "/api/v1/conversations/#{@conversation.conversation_id}?interleave_submissions=1",
|
||||
{ :controller => 'conversations', :action => 'show', :id => @conversation.conversation_id.to_s, :format => 'json', :interleave_submissions => '1' })
|
||||
|
||||
json['submissions'].should be_nil
|
||||
json['messages'].size.should eql 4
|
||||
json['messages'][0]['body'].should eql 'another message!'
|
||||
|
||||
json['messages'][1]['body'].should eql 'my name is bob'
|
||||
jsub = json['messages'][1]['submission']
|
||||
jsub['assignment'].should be_present
|
||||
jcom = jsub['submission_comments']
|
||||
jcom.should be_present
|
||||
jcom.size.should == 1
|
||||
jcom[0]['author_id'].should == @bob.id
|
||||
|
||||
json['messages'][2]['body'].should eql 'wut up teacher' # most recent comment
|
||||
jsub = json['messages'][2]['submission']
|
||||
jcom = jsub['submission_comments']
|
||||
jcom.size.should == 2
|
||||
jcom[0]['author_id'].should == @me.id
|
||||
jcom[1]['author_id'].should == @bob.id
|
||||
|
||||
json['messages'][3]['body'].should eql 'test'
|
||||
end
|
||||
json = api_call(:get, "/api/v1/conversations/#{@conversation.conversation_id}",
|
||||
{ :controller => 'conversations', :action => 'show', :id => @conversation.conversation_id.to_s, :format => 'json' })
|
||||
|
||||
json['messages'].size.should == 1
|
||||
json['submissions'].size.should == 0
|
||||
end
|
||||
|
||||
it "should add a message to the conversation" do
|
||||
|
|
|
@ -157,19 +157,6 @@ describe ConversationsController do
|
|||
assert_unauthorized
|
||||
end
|
||||
|
||||
it "should recompute inbox count" do
|
||||
# In an effort to make the data fix easy to do and self-healing,
|
||||
# recompute the unread inbox count when the page is loaded.
|
||||
course_with_student_logged_in(:active_all => true)
|
||||
@user.update_attribute(:unread_conversations_count, -20) # create invalid starting value
|
||||
@c1 = conversation
|
||||
|
||||
get 'index'
|
||||
response.should be_success
|
||||
@user.reload
|
||||
@user.unread_conversations_count.should == 0
|
||||
end
|
||||
|
||||
context "masquerading" do
|
||||
before do
|
||||
a = Account.default
|
||||
|
@ -573,33 +560,9 @@ describe ConversationsController do
|
|||
course_with_student_logged_in(:active_all => true)
|
||||
end
|
||||
|
||||
it "should enable new conversations for a user" do
|
||||
@user.preferences[:use_new_conversations] = false
|
||||
@user.save!
|
||||
@user.use_new_conversations?.should be_false
|
||||
post 'toggle_new_conversations', :use_new_conversations => true
|
||||
@user.reload
|
||||
@user.use_new_conversations?.should be_true
|
||||
end
|
||||
|
||||
it "should disable new conversations for a user" do
|
||||
@user.preferences[:use_new_conversations] = true
|
||||
@user.save!
|
||||
it "should not disable new conversations for a user anymore" do
|
||||
post 'toggle_new_conversations'
|
||||
@user.reload
|
||||
@user.use_new_conversations?.should be_false
|
||||
end
|
||||
|
||||
it "should be idempotent" do
|
||||
@user.use_new_conversations?.should be_false
|
||||
post 'toggle_new_conversations'
|
||||
@user.reload
|
||||
@user.use_new_conversations?.should be_false
|
||||
post 'toggle_new_conversations', :use_new_conversations => 1
|
||||
@user.reload
|
||||
@user.use_new_conversations?.should be_true
|
||||
post 'toggle_new_conversations', :use_new_conversations => 1
|
||||
@user.reload
|
||||
@user.use_new_conversations?.should be_true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -494,101 +494,6 @@ describe Conversation do
|
|||
end
|
||||
end
|
||||
|
||||
context "update_all_for_asset" do
|
||||
it "should delete all messages if requested" do
|
||||
asset = mock
|
||||
asset_messages = mock
|
||||
asset_messages.expects(:destroy_all).returns([])
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_messages).at_least_once.returns(asset_messages)
|
||||
Conversation.update_all_for_asset asset, :delete_all => true
|
||||
end
|
||||
|
||||
it "should not create conversations if only_existing is set" do
|
||||
u1 = user
|
||||
u2 = user
|
||||
conversation = Conversation.initiate([u1, u2], true)
|
||||
asset = Submission.new(:user => u1)
|
||||
asset.expects(:conversation_groups).returns([[u1, u2]])
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_messages).at_least_once.returns([])
|
||||
asset.expects(:conversation_message_data).returns({:created_at => Time.now.utc, :author => u1, :body => "asdf"})
|
||||
Conversation.update_all_for_asset asset, :update_message => true, :only_existing => true
|
||||
conversation.conversation_messages.size.should eql 1
|
||||
end
|
||||
|
||||
it "should undelete visible soft-deleted message participants" do
|
||||
u1 = user
|
||||
u2 = user
|
||||
conversation = Conversation.initiate([u1, u2], true)
|
||||
# a message to keep the conversation visible to u2 after remove_messages on the asset's message
|
||||
previous_message = conversation.add_message(u1, 'hello')
|
||||
message = conversation.add_message(u1, 'test message')
|
||||
|
||||
# make u1's conversation invisible so they won't be updated.
|
||||
u1.conversations.first.remove_messages(previous_message, message)
|
||||
u1.conversations.should be_empty
|
||||
|
||||
# u2 only deletes the asset's message, but conversations is still visible
|
||||
u2.conversations.first.remove_messages(message)
|
||||
u2.conversations.first.messages.size.should eql 1
|
||||
|
||||
asset = Submission.new(:user => u1)
|
||||
asset.expects(:conversation_groups).returns([[u1, u2]])
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_messages).at_least_once.returns([message])
|
||||
asset.expects(:conversation_message_data).returns({:created_at => Time.now.utc, :author => u1, :body => "asdf"})
|
||||
|
||||
Conversation.update_all_for_asset asset, :update_message => true, :only_existing => true
|
||||
conversation.conversation_messages.size.should eql 2
|
||||
u1.conversations.should be_empty
|
||||
# but u1 should still have the soft-deleted participant
|
||||
u1.all_conversations.first.all_messages.size.should eql 2
|
||||
u2.conversations.first.messages.size.should eql 2
|
||||
end
|
||||
|
||||
context "sharding" do
|
||||
specs_require_sharding
|
||||
|
||||
it "should re-use conversations from another shard" do
|
||||
u1 = @shard1.activate { user }
|
||||
u2 = user
|
||||
conversation = @shard2.activate { Conversation.initiate([u1, u2], true) }
|
||||
asset = Submission.new(:user => u1)
|
||||
asset.expects(:conversation_groups).returns([[u1, u2]])
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_messages).at_least_once.returns([])
|
||||
asset.expects(:conversation_message_data).returns({:created_at => Time.now.utc, :author => u1, :body => "asdf"})
|
||||
Conversation.update_all_for_asset asset, :update_message => true, :only_existing => true
|
||||
conversation.conversation_messages.size.should eql 1
|
||||
end
|
||||
end
|
||||
|
||||
it "should create conversations by default" do
|
||||
u1 = user
|
||||
u2 = user
|
||||
conversation = Conversation.initiate([u1, u2], true)
|
||||
asset = Submission.new(:user => u1)
|
||||
asset.expects(:conversation_groups).returns([[u1, u2]])
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_messages).at_least_once.returns([])
|
||||
asset.expects(:conversation_message_data).returns({:created_at => Time.now.utc, :author => u1, :body => "asdf"})
|
||||
Conversation.expects(:initiate).returns(conversation)
|
||||
Conversation.update_all_for_asset asset, :update_message => true
|
||||
conversation.conversation_messages.size.should eql 1
|
||||
end
|
||||
|
||||
it "should delete obsolete messages" do
|
||||
old_message = mock
|
||||
old_message.expects(:destroy).returns(true)
|
||||
asset = mock
|
||||
asset.expects(:lock!).returns(true)
|
||||
asset.expects(:conversation_groups).returns([])
|
||||
asset.expects(:conversation_messages).at_least_once.returns([old_message])
|
||||
Conversation.update_all_for_asset(asset, {})
|
||||
end
|
||||
end
|
||||
|
||||
context "context tags" do
|
||||
context "current_context_strings" do
|
||||
it "should not double-count duplicate enrollments" do
|
||||
|
|
|
@ -144,493 +144,6 @@ This text has a http://www.google.com link in it...
|
|||
@comment = @submission.add_comment(:author => se.user, :media_comment_type => 'audio', :media_comment_id => 'fake')
|
||||
end
|
||||
|
||||
context "conversations" do
|
||||
before do
|
||||
assignment_model
|
||||
@assignment.workflow_state = 'published'
|
||||
@assignment.save
|
||||
@course.offer
|
||||
@course.enroll_teacher(user).accept
|
||||
@teacher1 = @user
|
||||
@course.enroll_teacher(user).accept
|
||||
@teacher2 = @user
|
||||
@assignment.reload
|
||||
@course.enroll_student(user)
|
||||
@student1 = @user
|
||||
@assignment.context.reload
|
||||
@submission1 = @assignment.submit_homework(@student1, :body => 'some message')
|
||||
end
|
||||
|
||||
context "creation" do
|
||||
it "should send submitter comments to all instructors if no instructors have commented" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.messages.size.should eql 1
|
||||
tc1.messages.first.asset.should eql @submission1
|
||||
@teacher2.conversations.size.should eql 1
|
||||
tc2 = @teacher2.conversations.first
|
||||
tc2.messages.size.should eql 1
|
||||
tc2.messages.first.asset.should eql @submission1
|
||||
end
|
||||
|
||||
it "should not send non-participant comments to anyone" do
|
||||
@submission1.add_comment(:author => user, :comment => "ohai im in ur group")
|
||||
@teacher1.conversations.size.should eql 0 # if we actually set up a group assignment and had this comment on all submissions, the teacher would have one conversation with that commenter
|
||||
@student1.conversations.size.should eql 0
|
||||
end
|
||||
|
||||
it "should just create a single message for all comments" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello again!")
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello hello hello!")
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.messages.size.should eql 1
|
||||
tc1.messages.first.asset.should eql @submission1
|
||||
end
|
||||
|
||||
it "should set the most recent comment as the message data" do
|
||||
SubmissionComment.any_instance.stubs(:current_time_from_proper_timezone).returns(Time.now.utc, Time.now.utc + 1.hour)
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
c2 = @submission1.add_comment(:author => @teacher1, :comment => "hello again!").reload
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.last_message_at.to_i.should eql c1.created_at.to_i
|
||||
tc1.messages.last.body.should eql c2.comment
|
||||
tc1.messages.last.author.should eql @teacher1
|
||||
end
|
||||
|
||||
it "should set the root_account_ids" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@teacher.conversations.where(:root_account_ids => nil).any?.should be_false
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "sup")
|
||||
@teacher.conversations.where(:root_account_ids => nil).any?.should be_false
|
||||
@student.conversations.where(:root_account_ids => nil).any?.should be_false
|
||||
end
|
||||
|
||||
it "should not be visible to the student until an instructor comments" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@student1.conversations.size.should eql 0
|
||||
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "sup")
|
||||
@student1.conversations.reload.size.should eql 1
|
||||
end
|
||||
|
||||
it "should not be visible to other instructors once the first instructor comments" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@teacher1.conversations.size.should eql 1
|
||||
@teacher2.conversations.size.should eql 1
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "hello")
|
||||
@teacher2.reload.conversations.size.should eql 0
|
||||
@teacher2.all_conversations.size.should eql 1 # still there, the message was just deleted
|
||||
end
|
||||
|
||||
it "should set the unread count/status for everyone but the author" do
|
||||
@submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
tconvo = @teacher1.conversations.first
|
||||
tconvo.should be_unread
|
||||
tconvo.update_attribute :workflow_state, 'read'
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "hi")
|
||||
sconvo = @student1.conversations.first
|
||||
sconvo.should be_unread
|
||||
tconvo.reload.should be_read
|
||||
end
|
||||
|
||||
it "should not create conversations for teachers in new conversations" do
|
||||
@teacher1.preferences[:use_new_conversations] = true
|
||||
@teacher1.save!
|
||||
@submission1.add_comment(author: @student1, comment: 'test')
|
||||
@teacher1.reload.unread_conversations_count.should == 0
|
||||
end
|
||||
|
||||
it "should not create conversations for students in new conversations" do
|
||||
@student1.preferences[:use_new_conversations] = true
|
||||
@student1.save!
|
||||
@submission1.add_comment(author: @teacher1, comment: 'test')
|
||||
@student1.reload.unread_conversations_count.should == 0
|
||||
end
|
||||
|
||||
context "teacher makes first submission comment" do
|
||||
it "should only show as sent for the teacher if private converstation does not already exist" do
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "test comment")
|
||||
@teacher1.conversations.should be_empty
|
||||
@teacher1.all_conversations.size.should eql 1
|
||||
@teacher1.all_conversations.sent.size.should eql 1
|
||||
end
|
||||
|
||||
it "should reuse an existing private conversation, but not change its state for teacher" do
|
||||
convo = Conversation.initiate([@teacher1, @student1], true)
|
||||
convo.add_message(@teacher1, 'direct message')
|
||||
@teacher1.conversations.count.should == 1
|
||||
convo = @teacher1.conversations.first
|
||||
convo.workflow_state = 'archived'
|
||||
convo.save!
|
||||
@teacher1.reload.conversations.default.should be_empty
|
||||
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "test comment")
|
||||
@teacher1.reload
|
||||
@teacher1.all_conversations.size.should eql 1
|
||||
@teacher1.conversations.default.should be_empty
|
||||
@teacher1.all_conversations.archived.size.should eql 1
|
||||
end
|
||||
end
|
||||
|
||||
context "with no_submission_comments_inbox" do
|
||||
context "when teacher sets after conversation started" do
|
||||
before :each do
|
||||
@submission1.add_comment(:author => @student1, :comment => 'Test comment')
|
||||
@submission1.add_comment(:author => @teacher1, :comment => 'Test response')
|
||||
@student1.mark_all_conversations_as_read!
|
||||
@teacher1.mark_all_conversations_as_read!
|
||||
end
|
||||
|
||||
it "should keep unread 0 when comments added" do
|
||||
@teacher1.conversations.unread.count.should == 0
|
||||
# Disable notification with existing conversation
|
||||
@teacher1.preferences[:no_submission_comments_inbox] = true
|
||||
@teacher1.save!
|
||||
# Student adds another comment
|
||||
@submission1.add_comment(:author => @student1, :comment => 'New comment')
|
||||
@teacher1.conversations.unread.count.should == 0
|
||||
end
|
||||
end
|
||||
context "when not set" do
|
||||
before :each do
|
||||
@submission1.add_comment(:author => @student1, :comment => 'Test comment')
|
||||
end
|
||||
|
||||
it "should add an unread comment" do
|
||||
@teacher1.conversations.unread.count.should == 1
|
||||
end
|
||||
end
|
||||
context "when preference set for teacher" do
|
||||
before :each do
|
||||
# setup user setting
|
||||
@teacher1.preferences[:no_submission_comments_inbox] = true
|
||||
@teacher1.save!
|
||||
end
|
||||
it "should not create new conversations" do
|
||||
@teacher1.conversations.count.should == 0
|
||||
@submission1.add_comment(:author => @student1, :comment => 'New comment')
|
||||
@teacher1.conversations.count.should == 0
|
||||
end
|
||||
it "should create conversations after re-enabling the notification" do
|
||||
@submission1.add_comment(:author => @student1, :comment => 'New comment')
|
||||
@teacher1.conversations.count.should == 0
|
||||
@teacher1.preferences[:no_submission_comments_inbox] = false
|
||||
@teacher1.save!
|
||||
# Student adds another comment
|
||||
@submission1.add_comment(:author => @student1, :comment => 'Another comment')
|
||||
@teacher1.conversations.count.should == 1
|
||||
end
|
||||
it "should show teacher comment as new to student" do
|
||||
@submission1.add_comment(:author => @student1, :comment => 'Test comment')
|
||||
@submission1.add_comment(:author => @teacher1, :comment => 'Test response')
|
||||
@student1.conversations.unread.count.should == 1
|
||||
end
|
||||
it "should not block direct message from student" do
|
||||
convo = Conversation.initiate([@student1, @teacher], false)
|
||||
convo.add_message(@student1, 'My direct message')
|
||||
@teacher.conversations.unread.count.should == 1
|
||||
end
|
||||
it "should add submission comments to existing conversations" do
|
||||
convo = Conversation.initiate([@student1, @teacher1], true)
|
||||
convo.add_message(@student1, 'My direct message')
|
||||
c = @teacher1.conversations.unread.first
|
||||
c.should_not be_nil
|
||||
c.update_attribute(:workflow_state, 'read')
|
||||
@submission1.add_comment(:author => @student1, :comment => 'A comment')
|
||||
c.reload
|
||||
c.should be_read # still read, since we don't care to be notified
|
||||
c.messages.size.should eql 2 # but the submission is visible
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "unmuting" do
|
||||
before do
|
||||
@assignment.mute!
|
||||
end
|
||||
|
||||
it "should update conversations when assignments are unmuted" do
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "!", :hidden => true)
|
||||
@teacher1.conversations.size.should eql 0
|
||||
@teacher1.all_conversations.sent.size.should eql 0
|
||||
@student1.conversations.size.should eql 0
|
||||
@assignment.unmute!
|
||||
@teacher1.reload.conversations.size.should eql 0
|
||||
@teacher1.all_conversations.sent.size.should eql 1
|
||||
@student1.reload.conversations.size.should eql 1
|
||||
@student1.conversations.first.should be_unread
|
||||
end
|
||||
|
||||
it "should not set an older created_at/message" do
|
||||
SubmissionComment.any_instance.stubs(:current_time_from_proper_timezone).returns(Time.now.utc, Time.now.utc + 1.hour)
|
||||
c1 = @submission1.add_comment(:author => @teacher1, :comment => "!", :hidden => true)
|
||||
c2 = @submission1.add_comment(:author => @student1, :comment => "a new comment").reload
|
||||
@teacher1.conversations.size.should eql 1
|
||||
@teacher1.conversations.first.messages.last.created_at.to_i.should eql c2.created_at.to_i
|
||||
@teacher1.conversations.first.messages.last.body.should eql c2.comment
|
||||
@teacher2.conversations.size.should eql 1
|
||||
@student1.conversations.size.should eql 0
|
||||
@assignment.unmute!
|
||||
@teacher1.reload.conversations.size.should eql 1
|
||||
@teacher1.conversations.first.messages.last.created_at.to_i.should eql c2.created_at.to_i
|
||||
@teacher1.conversations.first.messages.last.body.should eql c2.comment
|
||||
@teacher2.reload.conversations.size.should eql 0
|
||||
@student1.reload.conversations.size.should eql 1
|
||||
@student1.conversations.first.should be_unread
|
||||
end
|
||||
|
||||
it "should mark-as-unread for everyone if there are multiple authors of hidden comments" do
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "help!")
|
||||
c2 = @submission1.add_comment(:author => @teacher1, :comment => "ok", :hidden => true)
|
||||
c3 = @submission1.add_comment(:author => @teacher2, :comment => "no", :hidden => true)
|
||||
@student1.conversations.size.should eql 0
|
||||
t1convo = @teacher1.conversations.first
|
||||
t1convo.workflow_state = :read
|
||||
t1convo.save!
|
||||
t2convo = @teacher2.conversations.first
|
||||
t2convo.workflow_state = :read
|
||||
t2convo.save!
|
||||
|
||||
@assignment.unmute!
|
||||
|
||||
t1convo.reload.should be_unread
|
||||
t2convo.reload.should be_unread
|
||||
@student1.reload.conversations.size.should eql 2
|
||||
@student1.conversations.first.should be_unread
|
||||
@student1.conversations.last.should be_unread
|
||||
end
|
||||
|
||||
it "should respect the no_submission_comments_inbox setting" do
|
||||
@teacher1.preferences[:no_submission_comments_inbox] = true
|
||||
@teacher1.save!
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "help!")
|
||||
c2 = @submission1.add_comment(:author => @teacher1, :comment => "ok", :hidden => true)
|
||||
c3 = @submission1.add_comment(:author => @teacher2, :comment => "no", :hidden => true)
|
||||
@student1.conversations.size.should eql 0
|
||||
@teacher1.conversations.size.should eql 0
|
||||
@teacher1.all_conversations.sent.size.should eql 0
|
||||
t2convo = @teacher2.conversations.first
|
||||
t2convo.workflow_state = :read
|
||||
t2convo.save!
|
||||
|
||||
@assignment.unmute!
|
||||
|
||||
# If there is more than one author in the set of submission comments,
|
||||
# then it is treated as a new message for everyone.
|
||||
@teacher1.reload.conversations.should be_empty
|
||||
@teacher1.all_conversations.size.should eql 1
|
||||
@teacher1.all_conversations.sent.size.should eql 0
|
||||
t2convo.reload.should be_unread
|
||||
@student1.reload.conversations.size.should eql 2
|
||||
@student1.conversations.first.should be_unread
|
||||
@student1.conversations.last.should be_unread
|
||||
end
|
||||
|
||||
it "should reuse an existing private conversation, but not change its state for teacher on unmute" do
|
||||
convo = Conversation.initiate([@teacher1, @student1], true)
|
||||
convo.add_message(@teacher1, 'direct message')
|
||||
@teacher1.conversations.count.should == 1
|
||||
convo = @teacher1.conversations.first
|
||||
convo.workflow_state = 'archived'
|
||||
convo.save!
|
||||
@submission1.add_comment(:author => @teacher1, :comment => "test comment")
|
||||
|
||||
@assignment.unmute!
|
||||
|
||||
@teacher1.reload.conversations.default.should be_empty
|
||||
@teacher1.all_conversations.size.should eql 1
|
||||
@teacher1.all_conversations.archived.size.should eql 1
|
||||
@teacher1.all_conversations.sent.size.should eql 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "deletion" do
|
||||
it "should update the message correctly if the most recent comment is deleted" do
|
||||
SubmissionComment.any_instance.stubs(:current_time_from_proper_timezone).returns(Time.now.utc, Time.now.utc + 1.hour)
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "hello").reload
|
||||
c2 = @submission1.add_comment(:author => @teacher1, :comment => "hello again!")
|
||||
c2.destroy
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.last_message_at.to_i.should eql c1.created_at.to_i
|
||||
tc1.messages.last.body.should eql c1.comment
|
||||
tc1.messages.last.author.should eql @student1
|
||||
|
||||
# it won't reappear in the other teacher's conversation until another
|
||||
# non-instructor comment is added, since we don't know if it was
|
||||
# deleted automatically or explicitly by the teacher
|
||||
@teacher2.conversations.size.should eql 0
|
||||
end
|
||||
|
||||
it "should not change the message preview/timestamp if the deleted message was by a non-participant" do
|
||||
SubmissionComment.any_instance.stubs(:current_time_from_proper_timezone).returns(Time.now.utc, Time.now.utc + 1.hour)
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
c2 = @submission1.add_comment(:author => @teacher1, :comment => "hello again!").reload
|
||||
c3 = @submission1.add_comment(:author => user, :comment => "ohai im in ur group")
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.last_message_at.to_i.should eql c1.created_at.to_i
|
||||
tc1.messages.last.body.should eql c2.comment
|
||||
tc1.messages.last.author.should eql @teacher1
|
||||
|
||||
c3.destroy
|
||||
|
||||
tc1.reload
|
||||
tc1.last_message_at.to_i.should eql c1.created_at.to_i
|
||||
tc1.messages.last.body.should eql c2.comment
|
||||
tc1.messages.last.author.should eql @teacher1
|
||||
end
|
||||
|
||||
it "should not re-add the message to users who have deleted it" do
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
c2 = @submission1.add_comment(:author => @student1, :comment => "hello again!")
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tc1 = @teacher1.conversations.first
|
||||
tc1.remove_messages(:all)
|
||||
@teacher1.conversations.should be_empty
|
||||
c2.destroy
|
||||
|
||||
@teacher1.conversations.reload.should be_empty
|
||||
end
|
||||
|
||||
it "should remove the message from conversations when the last comment is deleted" do
|
||||
c1 = @submission1.add_comment(:author => @student1, :comment => "hello")
|
||||
@teacher1.conversations.size.should eql 1
|
||||
c1.destroy
|
||||
@teacher1.reload.conversations.size.should eql 0
|
||||
end
|
||||
|
||||
it "should delete other comments for the same assignment with the same group_comment_id" do
|
||||
c1 = @submission1.add_comment(:comment => "hai")
|
||||
c1.update_attribute(:group_comment_id, "testid1")
|
||||
@submission2 = @assignment.submit_homework(@course.enroll_student(user).user, :body => 'sub2')
|
||||
c2 = @submission2.add_comment(:comment => "hai2")
|
||||
c2.update_attribute(:group_comment_id, "testid1")
|
||||
@assignment2 = assignment_model(:course => @course)
|
||||
@assignment2.update_attribute(:workflow_state, 'published')
|
||||
@submission3 = @assignment2.submit_homework(@student1, :body => 'sub3')
|
||||
c3 = @submission3.add_comment(:comment => "hai3")
|
||||
c3.update_attribute(:group_comment_id, "testid1")
|
||||
c1.destroy
|
||||
SubmissionComment.find_by_id(c1.id).should be_nil
|
||||
SubmissionComment.find_by_id(c2.id).should be_nil
|
||||
SubmissionComment.find_by_id(c3.id).should == c3
|
||||
end
|
||||
end
|
||||
|
||||
context "migration" do
|
||||
def raw_comment(submission, author, comment, time=Time.now.utc)
|
||||
c = Submission.connection
|
||||
c.execute <<-SQL
|
||||
INSERT INTO submission_comments(submission_id, author_id, created_at, comment)
|
||||
VALUES(#{c.quote(submission.id)}, #{c.quote(author.id)}, #{c.quote(time)}, #{c.quote(comment)})
|
||||
SQL
|
||||
end
|
||||
|
||||
before do
|
||||
@course.enroll_student(user)
|
||||
@student2 = @user
|
||||
@submission2 = @assignment.submit_homework(@student2, :body => 'some message')
|
||||
end
|
||||
|
||||
it "should only create messages where conversations already exist" do
|
||||
convo1 = @student1.initiate_conversation([@teacher1])
|
||||
convo1.add_message('ohai')
|
||||
convo2 = @student1.initiate_conversation([@teacher2])
|
||||
convo2.add_message('hey', :update_for_sender => false) # like if the student did a bulk private message
|
||||
@student1.conversations.size.should eql 1 # second one is not visible to student
|
||||
@student1.conversations.first.messages.size.should eql 1
|
||||
@student2.conversations.size.should eql 0
|
||||
@teacher1.conversations.size.should eql 1
|
||||
@teacher1.conversations.first.messages.size.should eql 1
|
||||
@teacher2.conversations.size.should eql 1
|
||||
@teacher2.conversations.first.messages.size.should eql 1
|
||||
|
||||
raw_comment(@submission1, @student1, "hello")
|
||||
@submission1.create_or_update_conversations!(:migrate)
|
||||
raw_comment(@submission2, @student2, "yo")
|
||||
@submission2.create_or_update_conversations!(:migrate)
|
||||
|
||||
# same number of conversations, but existing ones got the new message
|
||||
@student1.conversations.size.should eql 1 # second one is still not visible to student
|
||||
@student1.conversations.first.messages.size.should eql 2
|
||||
@student2.conversations.size.should eql 0
|
||||
@teacher1.conversations.size.should eql 1
|
||||
@teacher1.conversations.first.messages.size.should eql 2
|
||||
@teacher2.conversations.size.should eql 1
|
||||
@teacher2.conversations.first.messages.size.should eql 2
|
||||
end
|
||||
|
||||
it "should not change any unread count/status" do
|
||||
convo = @student1.initiate_conversation([@teacher1])
|
||||
convo.add_message('ohai')
|
||||
@student1.conversations.size.should eql 1
|
||||
convo.messages.size.should eql 1
|
||||
@teacher1.conversations.size.should eql 1
|
||||
tconvo = @teacher1.conversations.first
|
||||
tconvo.messages.size.should eql 1
|
||||
tconvo.workflow_state = :read
|
||||
tconvo.save!
|
||||
@teacher1.reload.unread_conversations_count.should eql 0
|
||||
|
||||
raw_comment(@submission1, @student1, "hello")
|
||||
@submission1.create_or_update_conversations!(:migrate)
|
||||
|
||||
convo.reload.messages.size.should eql 2
|
||||
convo.should be_read
|
||||
@student1.reload.unread_conversations_count.should eql 0
|
||||
tconvo.reload.messages.size.should eql 2
|
||||
tconvo.should be_read
|
||||
@teacher1.reload.unread_conversations_count.should eql 0
|
||||
end
|
||||
|
||||
it "should update last_message_at, message_count and last_authored_at" do
|
||||
convo = @student1.initiate_conversation([@teacher1])
|
||||
convo.add_message('ohai')
|
||||
tconvo = @teacher1.conversations.first
|
||||
raw_comment(@submission1, @student1, "hello", Time.now.utc + 1.day)
|
||||
raw_comment(@submission1, @student1, "hello!", Time.now.utc + 2.day)
|
||||
@submission1.create_or_update_conversations!(:migrate)
|
||||
comments = @submission1.submission_comments
|
||||
|
||||
convo.reload.messages.size.should eql 2
|
||||
convo.last_message_at.to_i.should eql comments.last.created_at.to_i
|
||||
convo.last_authored_at.to_i.should eql comments.last.created_at.to_i
|
||||
convo.messages.first.created_at.to_i.should eql comments.last.created_at.to_i
|
||||
|
||||
tconvo.reload.messages.size.should eql 2
|
||||
tconvo.last_message_at.to_i.should eql comments.last.created_at.to_i
|
||||
tconvo.last_authored_at.should be_nil
|
||||
tconvo.messages.first.created_at.to_i.should eql comments.last.created_at.to_i
|
||||
end
|
||||
|
||||
it "should skip submissions with no participant comments" do
|
||||
convo = @student1.initiate_conversation([@teacher1])
|
||||
message = convo.add_message('ohai').reload
|
||||
tconvo = @teacher1.conversations.first
|
||||
raw_comment(@submission1, user, "ohai im in ur group", Time.now.utc + 1.day)
|
||||
|
||||
# should not add a submission message
|
||||
@submission1.create_or_update_conversations!(:migrate)
|
||||
|
||||
convo.reload.messages.size.should eql 1
|
||||
convo.last_message_at.to_i.should eql message.created_at.to_i
|
||||
convo.last_authored_at.to_i.should eql message.created_at.to_i
|
||||
convo.messages.first.created_at.to_i.should eql message.created_at.to_i
|
||||
|
||||
tconvo.reload.messages.size.should eql 1
|
||||
tconvo.last_message_at.to_i.should eql message.created_at.to_i
|
||||
tconvo.last_authored_at.should be_nil
|
||||
tconvo.messages.first.created_at.to_i.should eql message.created_at.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should prevent peer reviewer from seeing other comments" do
|
||||
@student1 = @student
|
||||
@student2 = student_in_course(:active_all => true).user
|
||||
|
|
|
@ -102,28 +102,6 @@ describe Submission do
|
|||
end
|
||||
end
|
||||
|
||||
it "should not return duplicate conversation groups" do
|
||||
assignment_model
|
||||
@assignment.workflow_state = 'published'
|
||||
@assignment.save!
|
||||
section1 = @course.course_sections.create(name: '1')
|
||||
section2 = @course.course_sections.create(name: '2')
|
||||
section3 = @course.course_sections.create(name: '3')
|
||||
section4 = @course.course_sections.create(name: '4')
|
||||
section5 = @course.course_sections.create(name: '5')
|
||||
section1.enroll_user(@teacher, 'TeacherEnrollment', 'accepted')
|
||||
section2.enroll_user(@teacher, 'TeacherEnrollment', 'accepted')
|
||||
section3.enroll_user(@teacher, 'TeacherEnrollment', 'accepted')
|
||||
section4.enroll_user(@teacher, 'TeacherEnrollment', 'invited')
|
||||
section5.enroll_user(@teacher, 'TeacherEnrollment', 'completed')
|
||||
@course.offer!
|
||||
@course.enroll_student(@student = user)
|
||||
@assignment.context.reload
|
||||
|
||||
@submission = @assignment.submit_homework(@student, :body => 'some message')
|
||||
@submission.conversation_groups.should eql @submission.conversation_groups.uniq
|
||||
end
|
||||
|
||||
it "should ensure the media object exists" do
|
||||
assignment_model
|
||||
se = @course.enroll_student(user)
|
||||
|
|
|
@ -1,261 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations attachments local tests" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before do
|
||||
conversation_setup
|
||||
local_storage!
|
||||
end
|
||||
|
||||
it "should be able to add an attachment" do
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
new_conversation
|
||||
|
||||
new_conversation
|
||||
submit_message_form(:attachments => [fullpath])
|
||||
@user.all_conversations.order("conversation_id DESC").last.has_attachments.should be_true
|
||||
@user.conversation_attachments_folder.attachments.count.should == 1
|
||||
end
|
||||
|
||||
it "should be able to remove attachments from the message form" do
|
||||
new_conversation
|
||||
|
||||
add_attachment_link = f(".action_add_attachment")
|
||||
add_attachment_link.click
|
||||
wait_for_ajaximations
|
||||
add_attachment_link.click
|
||||
wait_for_ajaximations
|
||||
ffj(".attachment_list > .attachment:visible .remove_link")[1].click
|
||||
wait_for_ajaximations
|
||||
ffj(".attachment_list > .attachment:visible").size.should == 1
|
||||
ffj(".attachment_list > .attachment:visible .remove_link")[0].click
|
||||
submit_message_form
|
||||
@user.all_conversations.order("conversation_id DESC").last.has_attachments.should be_false
|
||||
end
|
||||
|
||||
it "should save just one attachment when sending a bulk private message" do
|
||||
student_in_course
|
||||
@course.enroll_user(User.create(:name => "student1"))
|
||||
@course.enroll_user(User.create(:name => "student2"))
|
||||
@course.enroll_user(User.create(:name => "student3"))
|
||||
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
new_conversation
|
||||
add_recipient("student1")
|
||||
add_recipient("student2")
|
||||
add_recipient("student3")
|
||||
ConversationBatch.any_instance.stubs(:mode).returns(:sync)
|
||||
expect {
|
||||
submit_message_form(:attachments => [fullpath], :add_recipient => false, :group_conversation => false)
|
||||
}.to change(Attachment, :count).by(1)
|
||||
end
|
||||
|
||||
it "should save attachments on new messages on existing conversations" do
|
||||
student_in_course
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
|
||||
new_conversation
|
||||
submit_message_form
|
||||
|
||||
message = submit_message_form(:attachments => [fullpath])
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
message = "#message_#{message.id}"
|
||||
ffj("#{message} .message_attachments li").size.should == 1
|
||||
end
|
||||
|
||||
it "should save multiple attachments" do
|
||||
student_in_course
|
||||
file1 = get_file("testfile1.txt")
|
||||
file2 = get_file("testfile2.txt")
|
||||
|
||||
new_conversation
|
||||
message = submit_message_form(:attachments => [file1[1], file2[1]])
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
message = "#message_#{message.id}"
|
||||
ffj("#{message} .message_attachments li").size.should == 2
|
||||
fj("#{message} .message_attachments li:first a .title").text.should == file1[0]
|
||||
fj("#{message} .message_attachments li:last a .title").text.should == file2[0]
|
||||
end
|
||||
|
||||
it "should show forwarded attachments" do
|
||||
student_in_course
|
||||
@course.enroll_user(User.create(:name => 'student1'))
|
||||
@course.enroll_user(User.create(:name => 'student2'))
|
||||
|
||||
filename, fullpath, data = get_file('testfile1.txt')
|
||||
new_conversation
|
||||
add_recipient('student1')
|
||||
submit_message_form(:attachments => [fullpath], :add_recipient => false)
|
||||
wait_for_ajaximations
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
get_messages(false).first.click
|
||||
wait_for_ajaximations
|
||||
f('#action_forward').click
|
||||
|
||||
add_recipient('student2', '#forward_recipients')
|
||||
f('#forward_body').send_keys('ohai look an attachment')
|
||||
f('#forward_message_form').submit
|
||||
wait_for_ajaximations
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
ff('img.attachments').size.should == 2
|
||||
messages = get_messages(false) # conversation already loaded
|
||||
messages.size.should == 1
|
||||
messages.first.text.should include "ohai look an attachment"
|
||||
messages.first.text.should include filename
|
||||
end
|
||||
|
||||
it "should save attachments on initial messages on new conversations" do
|
||||
pending('connection refused - connect(2) - line 108')
|
||||
student_in_course
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
|
||||
new_conversation
|
||||
message = submit_message_form(:attachments => [fullpath])
|
||||
message = "#message_#{message.id}"
|
||||
|
||||
ffj("#{message} .message_attachments li").size.should == 1
|
||||
fj("#{message} .message_attachments li a .title").text.should == filename
|
||||
download_link = f("#{message} .message_attachments li a")
|
||||
keep_trying_until do
|
||||
file = open(download_link.attribute('href'))
|
||||
file.read.should match data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "conversations attachments S3 tests" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before do
|
||||
conversation_setup
|
||||
s3_storage!(:stubs => false)
|
||||
end
|
||||
|
||||
it "should be able to add an attachment" do
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
new_conversation
|
||||
|
||||
new_conversation
|
||||
submit_message_form(:attachments => [fullpath])
|
||||
@user.all_conversations.order("conversation_id DESC").last.has_attachments.should be_true
|
||||
@user.conversation_attachments_folder.attachments.count.should == 1
|
||||
end
|
||||
|
||||
it "should be able to remove attachments from the message form" do
|
||||
new_conversation
|
||||
|
||||
add_attachment_link = f(".action_add_attachment")
|
||||
add_attachment_link.click
|
||||
wait_for_ajaximations
|
||||
add_attachment_link.click
|
||||
wait_for_ajaximations
|
||||
ffj(".attachment_list > .attachment:visible .remove_link")[1].click
|
||||
wait_for_ajaximations
|
||||
ffj(".attachment_list > .attachment:visible").size.should == 1
|
||||
ffj(".attachment_list > .attachment:visible .remove_link")[0].click
|
||||
submit_message_form
|
||||
@user.all_conversations.order("conversation_id DESC").last.has_attachments.should be_false
|
||||
end
|
||||
|
||||
it "should save just one attachment when sending a bulk private message" do
|
||||
student_in_course
|
||||
@course.enroll_user(User.create(:name => "student1"))
|
||||
@course.enroll_user(User.create(:name => "student2"))
|
||||
@course.enroll_user(User.create(:name => "student3"))
|
||||
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
new_conversation
|
||||
add_recipient("student1")
|
||||
add_recipient("student2")
|
||||
add_recipient("student3")
|
||||
ConversationBatch.any_instance.stubs(:mode).returns(:sync)
|
||||
expect {
|
||||
submit_message_form(:attachments => [fullpath], :add_recipient => false, :group_conversation => false)
|
||||
}.to change(Attachment, :count).by(1)
|
||||
end
|
||||
|
||||
it "should save attachments on new messages on existing conversations" do
|
||||
student_in_course
|
||||
filename, fullpath, data = get_file("testfile1.txt")
|
||||
|
||||
new_conversation
|
||||
submit_message_form
|
||||
|
||||
message = submit_message_form(:attachments => [fullpath])
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
message = "#message_#{message.id}"
|
||||
ffj("#{message} .message_attachments li").size.should == 1
|
||||
end
|
||||
|
||||
it "should save multiple attachments" do
|
||||
student_in_course
|
||||
file1 = get_file("testfile1.txt")
|
||||
file2 = get_file("testfile2.txt")
|
||||
|
||||
new_conversation
|
||||
message = submit_message_form(:attachments => [file1[1], file2[1]])
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
message = "#message_#{message.id}"
|
||||
ffj("#{message} .message_attachments li").size.should == 2
|
||||
fj("#{message} .message_attachments li:first a .title").text.should == file1[0]
|
||||
fj("#{message} .message_attachments li:last a .title").text.should == file2[0]
|
||||
end
|
||||
|
||||
it "should show forwarded attachments" do
|
||||
student_in_course
|
||||
@course.enroll_user(User.create(:name => 'student1'))
|
||||
@course.enroll_user(User.create(:name => 'student2'))
|
||||
|
||||
filename, fullpath, data = get_file('testfile1.txt')
|
||||
new_conversation
|
||||
add_recipient('student1')
|
||||
submit_message_form(:attachments => [fullpath], :add_recipient => false)
|
||||
wait_for_ajaximations
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
get_messages(false).first.click
|
||||
wait_for_ajaximations
|
||||
f('#action_forward').click
|
||||
|
||||
add_recipient('student2', '#forward_recipients')
|
||||
f('#forward_body').send_keys('ohai look an attachment')
|
||||
f('#forward_message_form').submit
|
||||
wait_for_ajaximations
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
ff('img.attachments').size.should == 2
|
||||
messages = get_messages(false) # conversation already loaded
|
||||
messages.size.should == 1
|
||||
messages.first.text.should include "ohai look an attachment"
|
||||
messages.first.text.should include filename
|
||||
end
|
||||
end
|
|
@ -1,234 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations context filtering" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before (:each) do
|
||||
conversation_setup
|
||||
@course.update_attribute(:name, "the course")
|
||||
@course1 = @course
|
||||
@s1 = User.create(:name => "student1")
|
||||
@s2 = User.create(:name => "student2")
|
||||
@course1.enroll_user(@s1).update_attribute(:workflow_state, 'active')
|
||||
@course1.enroll_user(@s2).update_attribute(:workflow_state, 'active')
|
||||
@group = @course1.groups.create(:name => "the group")
|
||||
@group.users << @user << @s1 << @s2
|
||||
|
||||
@course2 = course(:active_all => true, :course_name => "that course")
|
||||
@course2.enroll_teacher(@user).accept
|
||||
@course2.enroll_user(@s1).update_attribute(:workflow_state, 'active')
|
||||
end
|
||||
|
||||
it "should capture the course when sending a message to a group" do
|
||||
new_conversation
|
||||
browse_menu
|
||||
|
||||
browse("the course", "Student Groups", "the group") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false)
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
audience = fj("#create_message_form ul.conversations .audience")
|
||||
audience.text.should include @course1.name
|
||||
audience.text.should_not include @course2.name
|
||||
audience.text.should include @group.name
|
||||
end
|
||||
|
||||
it "should capture the course when sending a message to a user under a course" do
|
||||
new_conversation
|
||||
browse_menu
|
||||
|
||||
browse("the course") { search("stu") { click "student1" } }
|
||||
submit_message_form(:add_recipient => false)
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
audience = fj("#create_message_form ul.conversations .audience")
|
||||
audience.text.should include @course1.name
|
||||
audience.text.should_not include @course2.name
|
||||
audience.text.should_not include @group.name
|
||||
end
|
||||
|
||||
it "should order by active-ness before name or type" do
|
||||
@course2.complete!
|
||||
new_conversation
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("th", "#context_tags") do
|
||||
menu.should == ["the course", "the group", "that course"]
|
||||
end
|
||||
end
|
||||
|
||||
it "should let you browse for filters" do
|
||||
new_conversation
|
||||
@browser = fj("#context_tags_filter .browser:visible")
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
browse_menu
|
||||
|
||||
menu.should == ["that course", "the course", "the group"]
|
||||
browse "that course" do
|
||||
menu.should == ["that course", "Everyone", "Teachers", "Students"]
|
||||
browse("Everyone") { menu.should == ["nobody@example.com", "student1", "User"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com", "User"] }
|
||||
browse("Students") { menu.should == ["student1"] }
|
||||
end
|
||||
browse "the course" do
|
||||
menu.should == ["the course", "Everyone", "Teachers", "Students", "Student Groups"]
|
||||
browse("Everyone") { menu.should == ["nobody@example.com", "student1", "student2"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com"] }
|
||||
browse("Students") { menu.should == ["student1", "student2"] }
|
||||
browse "Student Groups" do
|
||||
menu.should == ["the group"]
|
||||
browse("the group") { menu.should == ["the group", "nobody@example.com", "student1", "student2"] }
|
||||
end
|
||||
end
|
||||
browse("the group") { menu.should == ["the group", "nobody@example.com", "student1", "student2"] }
|
||||
end
|
||||
|
||||
it "should let you filter by a course" do
|
||||
pending("xvfb issues")
|
||||
new_conversation
|
||||
browse_menu
|
||||
browse("the course", "Everyone") { click "student2" }
|
||||
browse_menu
|
||||
browse("that course", "Everyone") { click "student1" }
|
||||
submit_message_form(:add_recipient => false, :message => "asdf") # tagged with both courses
|
||||
|
||||
new_conversation(false)
|
||||
browse_menu
|
||||
browse("that course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "qwerty")
|
||||
|
||||
get_conversations.size.should == 2
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("the course", "#context_tags") { browse("the course") { click("the course") } }
|
||||
|
||||
keep_trying_until do
|
||||
conversations = get_conversations
|
||||
conversations.size.should == 1
|
||||
conversations.first.find_element(:css, 'p').text.should == 'asdf'
|
||||
end
|
||||
|
||||
#filtered course should be first in the audience's contexts
|
||||
get_conversations.first.find_element(:css, '.audience em').text.should == 'the course and that course'
|
||||
end
|
||||
|
||||
it "should let you filter by a course that was concluded a long time ago" do
|
||||
new_conversation
|
||||
browse_menu
|
||||
browse("the course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "asdf")
|
||||
|
||||
new_conversation(false)
|
||||
browse_menu
|
||||
browse("that course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "qwerty")
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
get_conversations.size.should == 2
|
||||
|
||||
@course1.complete!
|
||||
@course1.update_attribute :conclude_at, 1.year.ago
|
||||
|
||||
get "/conversations/sent"
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("the course", "#context_tags") { browse("the course") { click("the course") } }
|
||||
|
||||
keep_trying_until do
|
||||
conversations = get_conversations
|
||||
conversations.size.should == 1
|
||||
conversations.first.find_element(:css, 'p').text.should == 'asdf'
|
||||
end
|
||||
end
|
||||
|
||||
it "should let you filter by a user" do
|
||||
pending("need to fix")
|
||||
new_conversation
|
||||
browse_menu
|
||||
browse("the course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "asdf")
|
||||
|
||||
new_conversation(false)
|
||||
browse_menu
|
||||
browse("that course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "qwerty")
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("student2", "#context_tags") { click("student2") }
|
||||
|
||||
keep_trying_until do
|
||||
conversations = get_conversations
|
||||
conversations.size.should == 1
|
||||
conversations.first.find_element(:css, 'p').text.should == 'asdf'
|
||||
end
|
||||
|
||||
# filtered student should be first in the audience
|
||||
get_conversations.first.find_element(:css, '.audience').text.should == 'student2 and student1 the course'
|
||||
end
|
||||
|
||||
it "should let you filter by a group" do
|
||||
pending("xvfb issues")
|
||||
new_conversation
|
||||
browse_menu
|
||||
browse("the course", "Everyone") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "asdf")
|
||||
|
||||
new_conversation(false)
|
||||
browse_menu
|
||||
browse("the group") { click "Select All" }
|
||||
submit_message_form(:add_recipient => false, :message => "qwerty")
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("the group", "#context_tags") {
|
||||
menu.should == ["the group"]
|
||||
elements.first.first.text.should include "the course" # make sure the group context is shown
|
||||
browse("the group") { click("the group") }
|
||||
}
|
||||
|
||||
keep_trying_until do
|
||||
conversations = get_conversations
|
||||
conversations.size.should == 1
|
||||
conversations.first.find_element(:css, 'p').text.should == 'qwerty'
|
||||
end
|
||||
end
|
||||
|
||||
it "should show the term name by the course" do
|
||||
new_conversation
|
||||
browse_menu
|
||||
|
||||
browse("the course") { search("stu") { click "student1" } }
|
||||
submit_message_form(:add_recipient => false)
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("the course", "#context_tags") do
|
||||
term_info = f('.autocomplete_menu .name .context_info')
|
||||
term_info.text.should == "(#{@course1.enrollment_term.name})"
|
||||
end
|
||||
end
|
||||
|
||||
it "should not show the default term name" do
|
||||
new_conversation
|
||||
browse_menu
|
||||
|
||||
browse("the course") { search("stu") { click "student1" } }
|
||||
submit_message_form(:add_recipient => false)
|
||||
|
||||
@input = fj("#context_tags_filter input:visible")
|
||||
search("that course", "#context_tags") do
|
||||
term_info = f('.autocomplete_menu .name')
|
||||
term_info.text.should == "that course"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,84 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations group" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before(:each) do
|
||||
conversation_setup
|
||||
@course.update_attribute(:name, "the course")
|
||||
@course.default_section.update_attribute(:name, "the section")
|
||||
@other_section = @course.course_sections.create(:name => "the other section")
|
||||
|
||||
s1 = User.create(:name => "student 1")
|
||||
@course.enroll_user(s1)
|
||||
s2 = User.create(:name => "student 2")
|
||||
@course.enroll_user(s2, "StudentEnrollment", :section => @other_section)
|
||||
|
||||
@group = @course.groups.create(:name => "the group")
|
||||
@group.users << s1
|
||||
|
||||
new_conversation
|
||||
@input = fj("#create_message_form input:visible")
|
||||
@checkbox = f(".group_conversation")
|
||||
end
|
||||
|
||||
def choose_recipient(*names)
|
||||
name = names.shift
|
||||
level = 1
|
||||
|
||||
@input.send_keys(name)
|
||||
wait_for_ajaximations(250)
|
||||
loop do
|
||||
keep_trying_until { ffj('.autocomplete_menu:visible .list').size == level }
|
||||
driver.execute_script("return $('.autocomplete_menu:visible .list').last().find('ul').last().find('li').toArray();").detect { |e|
|
||||
(e.find_element(:tag_name, :b).text rescue e.text) == name
|
||||
}.click
|
||||
wait_for_ajaximations(250)
|
||||
|
||||
break if names.empty?
|
||||
|
||||
level += 1
|
||||
name = names.shift
|
||||
end
|
||||
keep_trying_until { fj('.autocomplete_menu:visible').nil? }
|
||||
end
|
||||
|
||||
it "should not be an option with no recipients" do
|
||||
@checkbox.should_not be_displayed
|
||||
end
|
||||
|
||||
it "should not be an option for a single individual recipient" do
|
||||
choose_recipient("student 1")
|
||||
@checkbox.should_not be_displayed
|
||||
end
|
||||
|
||||
it "should be an option, default false, for a single bulk recipient" do
|
||||
choose_recipient("the course", "Everyone", "Select All")
|
||||
@checkbox.should be_displayed
|
||||
is_checked(".group_conversation").should be_false
|
||||
end
|
||||
|
||||
it "should be an option, default false, for multiple individual recipients" do
|
||||
choose_recipient("student 1")
|
||||
choose_recipient("student 2")
|
||||
@checkbox.should be_displayed
|
||||
is_checked(".group_conversation").should be_false
|
||||
end
|
||||
|
||||
it "should disappear when there are no longer multiple recipients" do
|
||||
choose_recipient("student 1")
|
||||
choose_recipient("student 2")
|
||||
@input.send_keys([:backspace])
|
||||
@checkbox.should_not be_displayed
|
||||
end
|
||||
|
||||
it "should revert to false after disappearing and reappearing" do
|
||||
choose_recipient("student 1")
|
||||
choose_recipient("student 2")
|
||||
@checkbox.click
|
||||
@input.send_keys([:backspace])
|
||||
choose_recipient("student 2")
|
||||
@checkbox.should be_displayed
|
||||
is_checked(".group_conversation").should be_false
|
||||
end
|
||||
end
|
|
@ -137,9 +137,6 @@ describe "conversations new" do
|
|||
|
||||
before do
|
||||
conversation_setup
|
||||
@teacher.preferences[:use_new_conversations] = true
|
||||
@teacher.save!
|
||||
|
||||
@s1 = user(name: "first student")
|
||||
@s2 = user(name: "second student")
|
||||
[@s1, @s2].each { |s| @course.enroll_student(s).update_attribute(:workflow_state, 'active') }
|
||||
|
@ -165,8 +162,6 @@ describe "conversations new" do
|
|||
|
||||
it "should allow admins to send a message without picking a context" do
|
||||
user = account_admin_user
|
||||
user.preferences[:use_new_conversations] = true
|
||||
user.save!
|
||||
user_logged_in({:user => user})
|
||||
get_conversations
|
||||
compose to: [@s1], subject: 'context-free', body: 'hallo!'
|
||||
|
@ -184,8 +179,6 @@ describe "conversations new" do
|
|||
|
||||
it "should allow admins to message users from their profiles" do
|
||||
user = account_admin_user
|
||||
user.preferences[:use_new_conversations] = true
|
||||
user.save!
|
||||
user_logged_in({:user => user})
|
||||
get "/accounts/#{Account.default.id}/users"
|
||||
wait_for_ajaximations
|
||||
|
@ -223,8 +216,6 @@ describe "conversations new" do
|
|||
end
|
||||
|
||||
it "should not be allowed for students" do
|
||||
@s1.preferences[:use_new_conversations] = true
|
||||
@s1.save!
|
||||
user_session(@s1)
|
||||
get_conversations
|
||||
compose course: @course, to: [@s2], body: 'hallo!', send: false
|
||||
|
|
|
@ -1,279 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations recipient finder" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
def conversations_path(params={})
|
||||
hash = params.to_json.unpack('H*').first
|
||||
"/conversations##{hash}"
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
conversation_setup
|
||||
@course.update_attribute(:name, "the course")
|
||||
@course.default_section.update_attribute(:name, "the section")
|
||||
@other_section = @course.course_sections.create(:name => "the other section")
|
||||
|
||||
@s1 = User.create(:name => "student 1")
|
||||
@course.enroll_user(@s1)
|
||||
@s2 = User.create(:name => "student 2")
|
||||
@course.enroll_user(@s2, "StudentEnrollment", :section => @other_section)
|
||||
|
||||
@group = @course.groups.create(:name => "the group")
|
||||
@group.users << @s1 << @user
|
||||
|
||||
new_conversation
|
||||
end
|
||||
|
||||
it "should allow browsing" do
|
||||
browse_menu
|
||||
|
||||
menu.should == ["the course", "the group"]
|
||||
browse "the course" do
|
||||
menu.should == ["Everyone", "Teachers", "Students", "Course Sections", "Student Groups"]
|
||||
toggleable.should == ["Everyone", "Teachers", "Students"]
|
||||
browse("Everyone") { menu.should == ["Select All", "nobody@example.com", "student 1", "student 2"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com"] }
|
||||
browse("Students") { menu.should == ["Select All", "student 1", "student 2"] }
|
||||
browse "Course Sections" do
|
||||
menu.should == ["the other section", "the section"]
|
||||
browse "the other section" do
|
||||
menu.should == ["Students"]
|
||||
browse("Students") { menu.should == ["student 2"] }
|
||||
end
|
||||
browse "the section" do
|
||||
menu.should == ["Everyone", "Teachers", "Students"]
|
||||
browse("Everyone") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com"] }
|
||||
browse("Students") { menu.should == ["student 1"] }
|
||||
end
|
||||
end
|
||||
browse "Student Groups" do
|
||||
menu.should == ["the group"]
|
||||
browse("the group") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
|
||||
end
|
||||
end
|
||||
browse("the group") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
|
||||
end
|
||||
|
||||
it "should respect permissions" do
|
||||
RoleOverride.create!(:context => Account.default, :permission => 'send_messages_all', :enrollment_type => 'TeacherEnrollment', :enabled => false)
|
||||
|
||||
browse_menu
|
||||
|
||||
menu.should == ["the course", "the group"]
|
||||
browse "the course" do
|
||||
menu.should == ["Everyone", "Teachers", "Students", "Course Sections", "Student Groups"]
|
||||
toggleable.should == []
|
||||
browse("Everyone") { menu.should == ["nobody@example.com", "student 1", "student 2"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com"] }
|
||||
browse("Students") { menu.should == ["student 1", "student 2"] }
|
||||
browse "Course Sections" do
|
||||
menu.should == ["the other section", "the section"]
|
||||
browse "the other section" do
|
||||
menu.should == ["Students"]
|
||||
browse("Students") { menu.should == ["student 2"] }
|
||||
end
|
||||
browse "the section" do
|
||||
menu.should == ["Everyone", "Teachers", "Students"]
|
||||
browse("Everyone") { menu.should == ["nobody@example.com", "student 1"] }
|
||||
browse("Teachers") { menu.should == ["nobody@example.com"] }
|
||||
browse("Students") { menu.should == ["student 1"] }
|
||||
end
|
||||
end
|
||||
browse "Student Groups" do
|
||||
menu.should == ["the group"]
|
||||
browse("the group") { menu.should == ["nobody@example.com", "student 1"] }
|
||||
end
|
||||
end
|
||||
browse("the group") { menu.should == ["nobody@example.com", "student 1"] }
|
||||
end
|
||||
|
||||
it "should not show concluded enrollments as students in the course" do
|
||||
pending('bug 7583 - concluded students in a live course still show up as students in the course when addressing messages in the inbox') do
|
||||
student_1_enrollment = @s1.enrollments.last
|
||||
student_1_enrollment.update_attributes(:workflow_state => 'completed')
|
||||
student_1_enrollment.save!
|
||||
student_1_enrollment.reload
|
||||
browse_menu
|
||||
browse("the course") do
|
||||
browse("Students") { menu.should == ["Select All", "student 2"] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should not return courses concluded a long time ago" do
|
||||
@course.complete!
|
||||
@course.update_attribute :conclude_at, 1.year.ago
|
||||
|
||||
browse_menu
|
||||
menu.should == ["No results found"]
|
||||
|
||||
search("course") do
|
||||
menu.should == ["No results found"]
|
||||
end
|
||||
end
|
||||
|
||||
it "should check already-added tokens when browsing" do
|
||||
browse_menu
|
||||
|
||||
browse("the group") do
|
||||
menu.should == ["Select All", "nobody@example.com", "student 1"]
|
||||
toggle "student 1"
|
||||
tokens.should == ["student 1"]
|
||||
end
|
||||
|
||||
browse("the course") do
|
||||
browse("Everyone") do
|
||||
toggled.should == ["student 1"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should have working select all checkboxes in appropriate contexts" do
|
||||
browse_menu
|
||||
|
||||
browse "the course" do
|
||||
toggle "Everyone"
|
||||
toggled.should == ["Everyone", "Teachers", "Students"]
|
||||
tokens.should == ["the course: Everyone"]
|
||||
|
||||
toggle "Everyone"
|
||||
toggled.should == []
|
||||
tokens.should == []
|
||||
|
||||
toggle "Students"
|
||||
toggled.should == ["Students"]
|
||||
tokens.should == ["the course: Students"]
|
||||
|
||||
toggle "Teachers"
|
||||
toggled.should == ["Everyone", "Teachers", "Students"]
|
||||
tokens.should == ["the course: Everyone"]
|
||||
|
||||
toggle "Teachers"
|
||||
toggled.should == ["Students"]
|
||||
tokens.should == ["the course: Students"]
|
||||
|
||||
browse "Teachers" do
|
||||
toggle "nobody@example.com"
|
||||
toggled.should == ["nobody@example.com"]
|
||||
tokens.should == ["the course: Students", "nobody@example.com"]
|
||||
|
||||
toggle "nobody@example.com"
|
||||
toggled.should == []
|
||||
tokens.should == ["the course: Students"]
|
||||
end
|
||||
toggled.should == ["Students"]
|
||||
|
||||
toggle "Teachers"
|
||||
toggled.should == ["Everyone", "Teachers", "Students"]
|
||||
tokens.should == ["the course: Everyone"]
|
||||
|
||||
browse "Students" do
|
||||
toggle "Select All"
|
||||
toggled.should == []
|
||||
tokens.should == ["the course: Teachers"]
|
||||
|
||||
toggle "student 1"
|
||||
toggle "student 2"
|
||||
toggled.should == ["Select All", "student 1", "student 2"]
|
||||
tokens.should == ["the course: Everyone"]
|
||||
end
|
||||
toggled.should == ["Everyone", "Teachers", "Students"]
|
||||
|
||||
browse "Everyone" do
|
||||
toggle "student 1"
|
||||
toggled.should == ["nobody@example.com", "student 2"]
|
||||
tokens.should == ["nobody@example.com", "student 2"]
|
||||
end
|
||||
toggled.should == []
|
||||
end
|
||||
end
|
||||
|
||||
it "should allow searching" do
|
||||
search("t") do
|
||||
menu.should == ["the course", "the other section", "the section", "the group", "student 1", "student 2"]
|
||||
end
|
||||
end
|
||||
|
||||
it "should show the group context when searching at the top level" do
|
||||
search("the group") do
|
||||
menu.first.should == "the group"
|
||||
elements.first.first.text.should include "the course"
|
||||
end
|
||||
end
|
||||
|
||||
it "should omit already-added tokens when searching" do
|
||||
search("student") do
|
||||
menu.should == ["student 1", "student 2"]
|
||||
click "student 1"
|
||||
end
|
||||
tokens.should == ["student 1"]
|
||||
search("stu") do
|
||||
menu.should == ["student 2"]
|
||||
end
|
||||
end
|
||||
|
||||
it "should allow searching under supported contexts" do
|
||||
browse_menu
|
||||
browse "the course" do
|
||||
search("t") { menu.should == ["the other section", "the section", "the group", "student 1", "student 2"] }
|
||||
browse "Everyone" do
|
||||
# only returns users
|
||||
search("T") { menu.should == ["student 1", "student 2"] }
|
||||
end
|
||||
browse "Course Sections" do
|
||||
# only returns sections
|
||||
search("student") { menu.should == ["No results found"] }
|
||||
search("r") { menu.should == ["the other section"] }
|
||||
browse "the section" do
|
||||
search("s") { menu.should == ["student 1"] }
|
||||
end
|
||||
end
|
||||
browse "Student Groups" do
|
||||
# only returns groups
|
||||
search("student") { menu.should == ["No results found"] }
|
||||
search("the") { menu.should == ["the group"] }
|
||||
browse "the group" do
|
||||
search("s") { menu.should == ["student 1"] }
|
||||
search("group") { menu.should == ["No results found"] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should allow a user id in the url hash to add recipient" do
|
||||
# check without any user_name
|
||||
get conversations_path(:user_id => @s1.id)
|
||||
wait_for_ajaximations
|
||||
tokens.should == ["student 1"]
|
||||
# explanation of user_name param: we used to pass the user name in the
|
||||
# hash fragment, and it was spoofable. now we load that data via ajax.
|
||||
get conversations_path(:user_id => @s1.id, :user_name => "some_fake_name")
|
||||
wait_for_ajaximations
|
||||
tokens.should == ["student 1"]
|
||||
end
|
||||
|
||||
it "should reject a non-contactable user id in the url hash" do
|
||||
other = User.create(:name => "other guy")
|
||||
get conversations_path(:user_id => other.id)
|
||||
wait_for_ajaximations
|
||||
tokens.should == []
|
||||
end
|
||||
|
||||
it "should allow a non-contactable user in the hash if a shared conversation exists" do
|
||||
other = User.create(:name => "other guy")
|
||||
# if the users have a conversation in common already, then the recipient can be added
|
||||
c = Conversation.initiate([@user, other], true)
|
||||
get conversations_path(:user_id => other.id, :from_conversation_id => c.id)
|
||||
wait_for_ajaximations
|
||||
tokens.should == ["other guy"]
|
||||
end
|
||||
|
||||
it "should not show student view student to other students" do
|
||||
@fake_student = @course.student_view_student
|
||||
search(@fake_student.name) do
|
||||
menu.should == ["No results found"]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,70 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations sent filter" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before (:each) do
|
||||
conversation_setup
|
||||
@course.update_attribute(:name, "the course")
|
||||
@course1 = @course
|
||||
@s1 = User.create(:name => "student1")
|
||||
@s2 = User.create(:name => "student2")
|
||||
@course1.enroll_user(@s1)
|
||||
@course1.enroll_user(@s2)
|
||||
|
||||
ConversationMessage.any_instance.stubs(:current_time_from_proper_timezone).returns(*100.times.to_a.reverse.map { |h| Time.now.utc - h.hours })
|
||||
|
||||
@c1 = conversation(@user, @s1)
|
||||
@c2 = conversation(@user, @s2)
|
||||
@c1.add_message('yay i sent this')
|
||||
@c2.conversation.add_message(@s2, "ohai im not u so this wont show up on the left")
|
||||
|
||||
get "/conversations/sent"
|
||||
|
||||
conversations = get_conversations
|
||||
conversations.first.should have_attribute('data-id', @c1.conversation_id.to_s)
|
||||
conversations.first.should include_text('yay i sent this')
|
||||
conversations.last.should have_attribute('data-id', @c2.conversation_id.to_s)
|
||||
conversations.last.should include_text('test')
|
||||
end
|
||||
|
||||
it "should reorder based on last authored message" do
|
||||
first_message_text = 'qwerty'
|
||||
get_conversations.last.click
|
||||
get_messages(false)
|
||||
|
||||
submit_message_form(:message => first_message_text, :existing_conversation => true)
|
||||
|
||||
ff(".last_author").length.should == 2
|
||||
ff(".last_author")[0].should include_text(first_message_text)
|
||||
ff(".last_author")[1].should include_text('yay i sent this')
|
||||
end
|
||||
|
||||
it "should remove the conversation when the last message by the author is deleted" do
|
||||
get_conversations.last.click
|
||||
|
||||
msgs = get_messages(false)
|
||||
msgs.size.should == 2
|
||||
msgs.last.click
|
||||
|
||||
delete_selected_messages
|
||||
end
|
||||
|
||||
it "should show/update all conversations when sending a bulk private message" do
|
||||
message_text = 'ohai guys'
|
||||
@s3 = User.create(:name => "student3")
|
||||
@course1.enroll_user(@s3)
|
||||
|
||||
new_conversation(false)
|
||||
add_recipient("student1")
|
||||
add_recipient("student2")
|
||||
add_recipient("student3")
|
||||
|
||||
ConversationBatch.any_instance.stubs(:mode).returns(:sync)
|
||||
submit_message_form(:message => message_text, :add_recipient => false, :group_conversation => false)
|
||||
|
||||
conversations = get_conversations
|
||||
conversations.size.should == 3
|
||||
conversations.each { |conversation| conversation.should include_text(message_text) }
|
||||
end
|
||||
end
|
|
@ -1,386 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before (:each) do
|
||||
conversation_setup
|
||||
end
|
||||
|
||||
it "should not allow double form submissions" do
|
||||
new_message = 'new conversation message'
|
||||
@s1 = User.create(:name => 'student1')
|
||||
@course.enroll_user(@s1)
|
||||
new_conversation
|
||||
add_recipient("student1")
|
||||
|
||||
expect {
|
||||
f('#create_message_form .conversation_body').send_keys(new_message)
|
||||
5.times { submit_form('#create_message_form form') rescue nil }
|
||||
assert_message_status("sent", new_message[0, 10])
|
||||
}.to change(ConversationMessage, :count).by(1)
|
||||
end
|
||||
|
||||
describe 'actions' do
|
||||
def create_conversation(workflow_state = 'unread', starred = false, url = '/conversations')
|
||||
@me = @user
|
||||
conversation(@me, user, :workflow_state => workflow_state, :starred => starred)
|
||||
get url unless url == nil
|
||||
end
|
||||
|
||||
it "should auto-mark as read" do
|
||||
@me = @user
|
||||
5.times { conversation(@me, user, :workflow_state => 'unread') }
|
||||
get "/conversations/unread"
|
||||
ce = get_conversations.first
|
||||
ce.should have_class('unread') # not marked immediately
|
||||
ce.click
|
||||
wait_for_ajaximations
|
||||
@me.conversations.unread.size.should == 5
|
||||
keep_trying_until do
|
||||
get_conversations.first.should_not have_class('unread')
|
||||
true
|
||||
end
|
||||
@me.conversations.unread.size.should == 4
|
||||
|
||||
get_conversations.last.click
|
||||
get_conversations.size.should == 4 # removed once deselected
|
||||
end
|
||||
|
||||
it "should not open the conversation when the gear menu is clicked" do
|
||||
create_conversation
|
||||
wait_for_ajaximations
|
||||
f('#menu-wrapper .al-options').should be_nil
|
||||
driver.execute_script "$('.admin-link-hover-area').addClass('active')"
|
||||
f('.admin-links button').click
|
||||
wait_for_ajaximations
|
||||
f('#menu-wrapper .al-options').should be_displayed
|
||||
f('.messages').should_not be_displayed
|
||||
end
|
||||
|
||||
it "should star a conversation" do
|
||||
create_conversation
|
||||
|
||||
f('#conversations .action_star').click
|
||||
wait_for_ajaximations
|
||||
f('#conversations .action_unstar').should be_displayed
|
||||
f('#conversations .action_star').should_not be_displayed
|
||||
end
|
||||
|
||||
it "should unstar a conversation" do
|
||||
create_conversation('unread', true)
|
||||
|
||||
f('#conversations .action_unstar').click
|
||||
wait_for_ajaximations
|
||||
f('#conversations .action_star').should be_displayed
|
||||
f('#conversations .action_unstar').should_not be_displayed
|
||||
end
|
||||
|
||||
it "should mark a conversation as unread" do
|
||||
create_conversation('read', false)
|
||||
|
||||
f('.action_mark_as_unread').click
|
||||
wait_for_ajaximations
|
||||
f('.action_mark_as_unread').should_not be_displayed
|
||||
f('.action_mark_as_read').should be_displayed
|
||||
expect_new_page_load { get '/conversations/archived' }
|
||||
f('.conversations .audience').should include_text('New Message')
|
||||
end
|
||||
|
||||
it "should delete a conversation" do
|
||||
create_conversation
|
||||
wait_for_ajaximations
|
||||
driver.execute_script "$('.admin-link-hover-area').addClass('active')"
|
||||
|
||||
f('.admin-links button').click
|
||||
f('.al-options .action_delete_all').click
|
||||
driver.switch_to.alert.accept
|
||||
wait_for_ajaximations
|
||||
|
||||
f('#no_messages').should be_displayed
|
||||
end
|
||||
|
||||
it "should archive a conversation" do
|
||||
create_conversation
|
||||
|
||||
wait_for_ajaximations
|
||||
driver.execute_script("$('.admin-link-hover-area').addClass('active')")
|
||||
f('.admin-links button').click
|
||||
f('.al-options .action_archive').click
|
||||
wait_for_ajaximations
|
||||
f('#no_messages').should be_displayed
|
||||
expect_new_page_load { get '/conversations/archived' }
|
||||
f('.conversations .audience').should include_text('User')
|
||||
end
|
||||
|
||||
it "should allow you to filter a conversation by sent" do
|
||||
create_conversation
|
||||
|
||||
expect_new_page_load { get '/conversations/archived' }
|
||||
f('.conversations .audience').should include_text('New Message')
|
||||
end
|
||||
end
|
||||
|
||||
context "New message... link" do
|
||||
before :each do
|
||||
@me = @user
|
||||
@other = user(:name => 'Some OtherDude')
|
||||
@course.enroll_student(@other)
|
||||
conversation(@me, @other, :workflow_state => 'unread')
|
||||
@participant_me = @conversation
|
||||
@convo = @participant_me.conversation
|
||||
@convo.add_message(@other, "Hey bud!")
|
||||
@convo.add_message(@me, "Howdy friend!")
|
||||
get '/conversations'
|
||||
f('.unread').click
|
||||
wait_for_ajaximations
|
||||
end
|
||||
|
||||
it "should not display on my own message" do
|
||||
# Hover over own message
|
||||
driver.execute_script("$('.message.self:first .send_private_message').focus()")
|
||||
f(".message.self .send_private_message").should_not be_displayed
|
||||
end
|
||||
|
||||
it "should display on messages from others" do
|
||||
# Hover over the message from the other writer to display link
|
||||
# This spec fails locally in isolation and in this context block.
|
||||
driver.execute_script("$('.message.other .send_private_message').focus()")
|
||||
f(".message.other .send_private_message").should be_displayed
|
||||
end
|
||||
|
||||
it "should start new message to the user" do
|
||||
f(".message.other .send_private_message").click()
|
||||
wait_for_ajaximations
|
||||
# token gets added after brief delay
|
||||
sleep(0.4)
|
||||
# create "token" with the 'other' user
|
||||
f("#create_message_form .token_input ul").text().should == @other.name
|
||||
end
|
||||
end
|
||||
|
||||
context 'messages' do
|
||||
before(:each) do
|
||||
@me = @user
|
||||
conversation(@me, user, :workflow_state => 'unread')
|
||||
get '/conversations'
|
||||
f('.unread').click
|
||||
wait_for_ajaximations
|
||||
f(".messages #message_#{ConversationMessage.last.id}").click
|
||||
end
|
||||
|
||||
it "should forward a message" do
|
||||
forward_body_text = 'new forward'
|
||||
f('#action_forward').click
|
||||
fj('#forward_message_form .token_input input').send_keys('nobody')
|
||||
wait_for_ajaximations
|
||||
f('.selectable').click
|
||||
f('#forward_body').send_keys(forward_body_text)
|
||||
f('.ui-dialog-buttonset > .btn-primary').click
|
||||
wait_for_ajaximations
|
||||
expect_new_page_load { get '/conversations/sent' }
|
||||
f('.conversations li.read').should include_text(forward_body_text)
|
||||
end
|
||||
|
||||
it "should delete a message" do
|
||||
f('#action_delete').click
|
||||
driver.switch_to.alert.accept
|
||||
wait_for_ajaximations
|
||||
f('#no_messages').should be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
context "conversation loading" do
|
||||
it "should load all conversations" do
|
||||
@me = @user
|
||||
num = 51
|
||||
num.times { conversation(@me, user) }
|
||||
get "/conversations"
|
||||
keep_trying_until do
|
||||
elements = get_conversations
|
||||
elements.last.location_once_scrolled_into_view
|
||||
elements.size.should == num
|
||||
end
|
||||
end
|
||||
|
||||
it "should properly clear the identity header when conversations are read" do
|
||||
enable_cache do
|
||||
@me = @user
|
||||
5.times { conversation(@me, user, :workflow_state => 'unread') }
|
||||
get_messages # loads the page, clicks the first conversation
|
||||
keep_trying_until do
|
||||
get_conversations.first.should_not have_class('unread')
|
||||
true
|
||||
end
|
||||
get '/conversations'
|
||||
f('.unread-messages-count').text.should == '4'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "media comments" do
|
||||
it "should add audio and video comments to the message form" do
|
||||
# don't have a good way to test kaltura here, so we just fake it up
|
||||
CanvasKaltura::ClientV3.expects(:config).at_least(1).returns({})
|
||||
|
||||
['audio', 'video'].each_with_index do |media_comment_type, index|
|
||||
mo = MediaObject.new
|
||||
mo.media_id = "0_12345678#{index}"
|
||||
mo.media_type = media_comment_type
|
||||
mo.context = @user
|
||||
mo.user = @user
|
||||
mo.title = "test title"
|
||||
mo.save!
|
||||
|
||||
new_conversation(:message => media_comment_type)
|
||||
|
||||
message = submit_message_form(:media_comment => [mo.media_id, mo.media_type])
|
||||
|
||||
expect_new_page_load { get '/conversations/sent' }
|
||||
f('.conversations li').click
|
||||
wait_for_ajaximations
|
||||
message = "#message_#{message.id}"
|
||||
ff("#{message} .message_attachments li").size.should == 1
|
||||
f("#{message} .message_attachments li a .title").text.should == mo.title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "form audience" do
|
||||
before (:each) do
|
||||
# have @course, @teacher from before
|
||||
# creates @student
|
||||
student_in_course(:course => @course, :active_all => true)
|
||||
|
||||
@course.update_attribute(:name, "the course")
|
||||
|
||||
@group = @course.groups.create(:name => "the group")
|
||||
@group.participating_users << @student
|
||||
|
||||
conversation(@teacher, @student)
|
||||
end
|
||||
|
||||
it "should link to the course page" do
|
||||
get_messages
|
||||
|
||||
expect_new_page_load { fj("#create_message_form .audience a").click }
|
||||
driver.current_url.should match %r{/courses/#{@course.id}}
|
||||
end
|
||||
|
||||
it "should not be a link in the left conversation list panel" do
|
||||
new_conversation
|
||||
|
||||
ffj("#conversations .audience a").should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "private messages" do
|
||||
before do
|
||||
@course.update_attribute(:name, "the course")
|
||||
@course1 = @course
|
||||
@s1 = User.create(:name => "student1")
|
||||
@s2 = User.create(:name => "student2")
|
||||
@course1.enroll_user(@s1)
|
||||
@course1.enroll_user(@s2)
|
||||
|
||||
ConversationMessage.any_instance.stubs(:current_time_from_proper_timezone).returns(*100.times.to_a.reverse.map { |h| Time.now.utc - h.hours })
|
||||
|
||||
@c1 = conversation(@user, @s1)
|
||||
@c1.add_message('yay i sent this')
|
||||
end
|
||||
|
||||
it "should select the new conversation" do
|
||||
new_conversation
|
||||
add_recipient("student2")
|
||||
|
||||
submit_message_form(:message => "ohai", :add_recipient => false).should_not be_nil
|
||||
end
|
||||
|
||||
it "should select the existing conversation" do
|
||||
new_conversation
|
||||
add_recipient("student1")
|
||||
|
||||
submit_message_form(:message => "ohai", :add_recipient => false, :existing_conversation => true).should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "batch messages" do
|
||||
it "shouldn't show anything in conversation list when sending batch messages to new recipients" do
|
||||
@course.default_section.update_attribute(:name, "the section")
|
||||
|
||||
@s1 = User.create(:name => "student1")
|
||||
@s2 = User.create(:name => "student2")
|
||||
@course.enroll_user(@s1)
|
||||
@course.enroll_user(@s2)
|
||||
|
||||
new_conversation
|
||||
|
||||
add_recipient("student1")
|
||||
add_recipient("student2")
|
||||
f("#create_message_form .conversation_body").send_keys "testing testing"
|
||||
submit_form('#create_message_form')
|
||||
|
||||
wait_for_ajaximations
|
||||
|
||||
assert_message_status "sending"
|
||||
run_jobs
|
||||
assert_message_status "sent"
|
||||
|
||||
# no conversations should show up in the conversation list
|
||||
get_conversations(false).should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "bulk popovers" do
|
||||
before (:each) do
|
||||
@number_of_people = 10
|
||||
@conversation_students = []
|
||||
@number_of_people.times do |i|
|
||||
u = User.create!(:name => "conversation student #{i}")
|
||||
@course.enroll_user(u, "StudentEnrollment").accept!
|
||||
@conversation_students << u
|
||||
end
|
||||
end
|
||||
|
||||
it "should validate the others popover" do
|
||||
new_conversation
|
||||
@conversation_students.each { |student| add_recipient(student.name) }
|
||||
f("#create_message_form .conversation_body").send_keys "testing testing"
|
||||
f('.group_conversation').click
|
||||
submit_form('#create_message_form')
|
||||
wait_for_ajaximations
|
||||
run_jobs
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
wait_for_ajaximations
|
||||
f('.others').click
|
||||
f('#others_popup').should be_displayed
|
||||
ff('#others_popup li').count.should == (@conversation_students.count - 2) # - 2 because the first 2 show up in the conversation summary
|
||||
end
|
||||
end
|
||||
|
||||
context "help menu" do
|
||||
it "should switch to new conversations and redirect" do
|
||||
site_admin_logged_in
|
||||
@user.watched_conversations_intro
|
||||
@user.save
|
||||
new_conversation
|
||||
f('#help-btn').click
|
||||
expect_new_page_load { fj('#try-new-conversations-menu-item').click }
|
||||
f('#inbox').should be_nil # #inbox is in the old conversations ui and not the new ui
|
||||
driver.execute_script("$('#help-btn').click()") #selenium.clik() not working in this case...
|
||||
expect_new_page_load { fj('#switch-to-old-conversations-menu-item').click }
|
||||
f('#inbox').should be_displayed
|
||||
end
|
||||
|
||||
it "should show the intro" do
|
||||
site_admin_logged_in
|
||||
@user.watched_conversations_intro
|
||||
@user.save
|
||||
new_conversation
|
||||
f('#help-btn').click
|
||||
fj('#conversations-intro-menu-item').click
|
||||
wait_for_ajaximations
|
||||
ff('#conversations_intro').last.should be_displayed
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,92 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations submissions" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before (:each) do
|
||||
conversation_setup
|
||||
end
|
||||
|
||||
it "should list submission comments in the conversation" do
|
||||
@me = @user
|
||||
@bob = student_in_course(:name => "bob", :active_all => true).user
|
||||
submission1 = submission_model(:course => @course, :user => @bob)
|
||||
submission2 = submission_model(:course => @course, :user => @bob)
|
||||
submission1.add_comment(:comment => "hey bob", :author => @me)
|
||||
submission1.add_comment(:comment => "wut up teacher", :author => @bob)
|
||||
submission2.add_comment(:comment => "my name is bob", :author => @bob)
|
||||
submission2.assignment.grade_student(@bob, {:grade => 0.9})
|
||||
conversation(@bob)
|
||||
get "/conversations"
|
||||
elements = nil
|
||||
keep_trying_until do
|
||||
elements = get_conversations
|
||||
elements.size == 1
|
||||
end
|
||||
elements.first.click
|
||||
wait_for_ajaximations
|
||||
subs = ff("#messages .submission")
|
||||
subs.size.should == 2
|
||||
subs[0].find_element(:css, '.score').text.should == '0.9 / 1.5'
|
||||
subs[1].find_element(:css, '.score').text.should == 'no score'
|
||||
|
||||
coms = subs[0].find_elements(:css, '.comment')
|
||||
coms.size.should == 1
|
||||
coms.first.find_element(:css, '.audience').text.should == 'bob'
|
||||
coms.first.find_element(:css, 'p').text.should == 'my name is bob'
|
||||
|
||||
coms = subs[1].find_elements(:css, '.comment')
|
||||
coms.size.should == 2
|
||||
coms.first.find_element(:css, '.audience').text.should == 'bob'
|
||||
coms.first.find_element(:css, 'p').text.should == 'wut up teacher'
|
||||
coms.last.find_element(:css, '.audience').text.should == 'nobody@example.com'
|
||||
coms.last.find_element(:css, 'p').text.should == 'hey bob'
|
||||
end
|
||||
|
||||
it "should interleave submissions with messages based on comment time" do
|
||||
SubmissionComment.any_instance.stubs(:current_time_from_proper_timezone).returns(10.minutes.ago, 8.minutes.ago)
|
||||
@me = @user
|
||||
@bob = student_in_course(:name => "bob", :active_all => true).user
|
||||
@conversation = conversation(@bob).conversation
|
||||
@conversation.conversation_messages.first.update_attribute(:created_at, 9.minutes.ago)
|
||||
submission1 = submission_model(:course => @course, :user => @bob)
|
||||
submission1.add_comment(:comment => "hey bob", :author => @me)
|
||||
|
||||
# message comes first, then submission, due to creation times
|
||||
msgs = get_messages
|
||||
msgs.size.should == 2
|
||||
msgs[0].should have_class('message')
|
||||
msgs[1].should have_class('submission')
|
||||
|
||||
# now new submission comment bumps it up
|
||||
submission1.add_comment(:comment => "hey teach", :author => @bob)
|
||||
msgs = get_messages
|
||||
msgs.size.should == 2
|
||||
msgs[0].should have_class('submission')
|
||||
msgs[1].should have_class('message')
|
||||
|
||||
# new message appears on top, submission now in the middle
|
||||
@conversation.add_message(@bob, 'ohai there').update_attribute(:created_at, 7.minutes.ago)
|
||||
msgs = get_messages
|
||||
msgs.size.should == 3
|
||||
msgs[0].should have_class('message')
|
||||
msgs[1].should have_class('submission')
|
||||
msgs[2].should have_class('message')
|
||||
end
|
||||
|
||||
it "should allow deleting submission messages from the conversation" do
|
||||
@me = @user
|
||||
@bob = student_in_course(:name => "bob", :active_all => true).user
|
||||
submission1 = submission_model(:course => @course, :user => @bob)
|
||||
submission1.add_comment(:comment => "hey teach", :author => @bob)
|
||||
@conversation = @me.conversations.first
|
||||
@conversation.should be_present
|
||||
|
||||
msgs = get_messages
|
||||
msgs.size.should == 1
|
||||
msgs.first.click
|
||||
delete_selected_messages
|
||||
@conversation.reload
|
||||
@conversation.last_message_at.should be_nil
|
||||
end
|
||||
end
|
|
@ -1,65 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/helpers/conversations_common')
|
||||
|
||||
describe "conversations user notes" do
|
||||
include_examples "in-process server selenium tests"
|
||||
|
||||
before(:each) do
|
||||
conversation_setup
|
||||
@the_teacher = User.create(:name => "teacher bob")
|
||||
@course.enroll_teacher(@the_teacher)
|
||||
@the_student = User.create(:name => "student bob")
|
||||
@course.enroll_student(@the_student)
|
||||
end
|
||||
|
||||
it "should not allow user notes if not enabled" do
|
||||
@course.account.update_attribute(:enable_user_notes, false)
|
||||
new_conversation
|
||||
add_recipient("student bob")
|
||||
f(".user_note").should_not be_displayed
|
||||
end
|
||||
|
||||
it "should not allow user notes to teachers" do
|
||||
@course.account.update_attribute(:enable_user_notes, true)
|
||||
new_conversation
|
||||
add_recipient("teacher bob")
|
||||
f(".user_note").should_not be_displayed
|
||||
end
|
||||
|
||||
it "should not allow user notes on group conversations" do
|
||||
@course.account.update_attribute(:enable_user_notes, true)
|
||||
new_conversation
|
||||
add_recipient("student bob")
|
||||
add_recipient("teacher bob")
|
||||
f(".user_note").should_not be_displayed
|
||||
fj("#create_message_form input:visible").send_keys :backspace
|
||||
f(".user_note").should be_displayed
|
||||
end
|
||||
|
||||
it "should allow user notes on new private conversations with students" do
|
||||
@course.account.update_attribute(:enable_user_notes, true)
|
||||
new_conversation
|
||||
add_recipient("student bob")
|
||||
checkbox = f(".user_note")
|
||||
checkbox.should be_displayed
|
||||
checkbox.click
|
||||
submit_message_form(:add_recipient => false)
|
||||
@the_student.user_notes.size.should == 1
|
||||
end
|
||||
|
||||
it "should allow user notes on existing private conversations with students" do
|
||||
@course.account.update_attribute(:enable_user_notes, true)
|
||||
new_conversation
|
||||
add_recipient("student bob")
|
||||
submit_message_form(:add_recipient => false)
|
||||
|
||||
expect_new_page_load { get "/conversations/sent" }
|
||||
f(".conversations li").click
|
||||
wait_for_ajaximations
|
||||
|
||||
checkbox = f(".user_note")
|
||||
checkbox.should be_displayed
|
||||
checkbox.click
|
||||
submit_message_form(:existing_conversation => true)
|
||||
@the_student.user_notes.size.should == 1
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue