group leaders
fixes CNVS-11833 test plan - regression test teacher view of groups page a group can only have one group leader, and that user must be in the group. anything that can be done to remove a user from a group should revoke their leadership if they have it. a user's leadership should not be revoked unless the teacher revokes it, a different leader is chosen, or that user leaves the group. a leader's user should have a user icon on it and their name should appear next to the group's name a few test cases: - set group leader using gear menu on user in group - revoke the leadership of the user using the gear menu - ensure that the group is now leaderless - set a group leader - set a different user as leader - ensure that a screenreader identifies the group leader link as "Group Leader" - ensure that the first user is no longer the leader - set a group leader - remove that user from the group by dragging and dropping - ensure that the user is no longer the group leader - set a group leader - move the user to another group using the option on the gear menu - ensure that the user is not the leader of their original or new group - in a large-roster course, add users to a group user the plus button next to the gear menu - set a group leader using the gear menu next to a user - fill a group up to it's maximum limit of students - ensure that the "full" label shows up for that group - ensure that when you narrow the browser size, you can still tell that the group is full Change-Id: I8bb1b62e0f36a37a24e050878c945f822fe9f66c Reviewed-on: https://gerrit.instructure.com/34360 Reviewed-by: Ethan Vizitei <evizitei@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Trevor deHaan <tdehaan@instructure.com> Product-Review: Ethan Vizitei <evizitei@instructure.com>
This commit is contained in:
parent
a99e904d11
commit
0d186e3c1a
|
@ -19,8 +19,8 @@ define [
|
|||
initialize: (models) ->
|
||||
super
|
||||
@loaded = @loadedAll = models?
|
||||
@on 'change:groupId', @onChangeGroupId
|
||||
@model = GroupUser.extend defaults: {groupId: @group.id, @category}
|
||||
@on 'change:group', @onChangeGroup
|
||||
@model = GroupUser.extend defaults: {group: @group, @category}
|
||||
|
||||
load: (target = 'all') ->
|
||||
@loadAll = target is 'all'
|
||||
|
@ -28,9 +28,9 @@ define [
|
|||
@fetch() if target isnt 'none'
|
||||
@load = ->
|
||||
|
||||
onChangeGroupId: (model, groupId) =>
|
||||
onChangeGroup: (model, group) =>
|
||||
@removeUser model
|
||||
@groupUsersFor(groupId)?.addUser model
|
||||
@groupUsersFor(group)?.addUser model
|
||||
|
||||
membershipsLocked: ->
|
||||
false
|
||||
|
@ -64,11 +64,11 @@ define [
|
|||
removeUser: (user) ->
|
||||
return if @membershipsLocked()
|
||||
@increment -1
|
||||
@group.set('leader', null) if @group?.get('leader')?.id == user.id
|
||||
@remove user if @loaded
|
||||
|
||||
increment: (amount) ->
|
||||
@group.increment 'members_count', amount
|
||||
|
||||
groupUsersFor: (id) ->
|
||||
@category?.groupUsersFor(id)
|
||||
|
||||
groupUsersFor: (group) ->
|
||||
@category?.groupUsersFor(group)
|
||||
|
|
|
@ -5,10 +5,6 @@ define [
|
|||
|
||||
class UnassignedGroupUserCollection extends GroupUserCollection
|
||||
|
||||
defaults:
|
||||
group:
|
||||
id: null
|
||||
|
||||
url: ->
|
||||
_url = "/api/v1/group_categories/#{@category.id}/users?per_page=50"
|
||||
_url += "&unassigned=true" unless @category.get('allows_multiple_memberships')
|
||||
|
|
|
@ -44,24 +44,24 @@ define [
|
|||
users = group.users()
|
||||
if users.loadedAll
|
||||
models = users.models.slice()
|
||||
user.set 'groupId', null for user in models
|
||||
user.set 'group', null for user in models
|
||||
else if not @get('allows_multiple_memberships')
|
||||
@_unassignedUsers.increment group.usersCount()
|
||||
|
||||
if not @get('allows_multiple_memberships') and (not users.loadedAll or not @_unassignedUsers.loadedAll)
|
||||
@_unassignedUsers.fetch()
|
||||
|
||||
reassignUser: (user, newGroupId) ->
|
||||
oldGroupId = user.get('groupId')
|
||||
return if oldGroupId is newGroupId
|
||||
reassignUser: (user, newGroup) ->
|
||||
oldGroup = user.get('group')
|
||||
return if oldGroup is newGroup
|
||||
|
||||
# if user is in _unassignedUsers and we allow multiple memberships,
|
||||
# don't actually move the user, move a copy instead
|
||||
if not oldGroupId? and @get('allows_multiple_memberships')
|
||||
if not oldGroup? and @get('allows_multiple_memberships')
|
||||
user = user.clone()
|
||||
user.once 'change:groupId', => @groupUsersFor(newGroupId).addUser user
|
||||
user.once 'change:group', => @groupUsersFor(newGroup).addUser user
|
||||
|
||||
user.save groupId: newGroupId
|
||||
user.save group: newGroup
|
||||
|
||||
groupsCount: ->
|
||||
if @_groups?.loadedAll
|
||||
|
@ -69,9 +69,9 @@ define [
|
|||
else
|
||||
@get('groups_count')
|
||||
|
||||
groupUsersFor: (id) ->
|
||||
if id?
|
||||
@_groups?.get(id)?._users
|
||||
groupUsersFor: (group) ->
|
||||
if group?
|
||||
group._users
|
||||
else
|
||||
@_unassignedUsers
|
||||
|
||||
|
|
|
@ -9,26 +9,26 @@ define [
|
|||
##
|
||||
# janky sync override cuz we don't have the luxury of (ember data || backbone-relational)
|
||||
sync: (method, model, options) =>
|
||||
groupId = @get('groupId')
|
||||
previousGroupId = @previous('groupId')
|
||||
# return unless changing groupId
|
||||
return if groupId is previousGroupId
|
||||
group = @get('group')
|
||||
previousGroup = @previous('group')
|
||||
# return unless changing group
|
||||
return if group is previousGroup
|
||||
# if the user is joining another group
|
||||
if groupId?
|
||||
@joinGroup(groupId)
|
||||
if group?
|
||||
@joinGroup(group)
|
||||
# if the user is being removed from a group, or is being moved to
|
||||
# another group AND the category allows multiple memberships (in
|
||||
# which case rails won't delete the old membership, so we have to)
|
||||
if previousGroupId and (not groupId? or @get('category').get('allows_multiple_memberships'))
|
||||
@leaveGroup(previousGroupId)
|
||||
if previousGroup and (not group? or @get('category').get('allows_multiple_memberships'))
|
||||
@leaveGroup(previousGroup)
|
||||
|
||||
# creating membership will delete pre-existing membership in same group category
|
||||
joinGroup: (groupId) ->
|
||||
$.ajaxJSON "/api/v1/groups/#{groupId}/memberships", 'POST', {user_id: @get('id')},
|
||||
joinGroup: (group) ->
|
||||
$.ajaxJSON "/api/v1/groups/#{group.id}/memberships", 'POST', {user_id: @get('id')},
|
||||
(data) => @trigger('ajaxJoinGroupSuccess', data)
|
||||
|
||||
leaveGroup: (groupId) ->
|
||||
$.ajaxJSON "/api/v1/groups/#{groupId}/users/#{@get('id')}", 'DELETE'
|
||||
leaveGroup: (group) ->
|
||||
$.ajaxJSON "/api/v1/groups/#{group.id}/users/#{@get('id')}", 'DELETE'
|
||||
|
||||
# e.g. so the view can give the user an indication of what happened
|
||||
# once everything is done
|
||||
|
|
|
@ -34,7 +34,7 @@ define [
|
|||
e.stopPropagation()
|
||||
$target = $(e.currentTarget)
|
||||
user = @collection.get($target.data('user-id'))
|
||||
user.save({'groupId': @groupId})
|
||||
user.save({'group': @group})
|
||||
@hide()
|
||||
|
||||
showBy: ($target, focus = false) ->
|
||||
|
|
|
@ -30,7 +30,7 @@ define [
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
newGroupId = $(e.currentTarget).data('group-id')
|
||||
@collection.category.reassignUser(@model, newGroupId)
|
||||
@collection.category.reassignUser(@model, @collection.get(newGroupId))
|
||||
@hide()
|
||||
|
||||
toJSON: ->
|
||||
|
|
|
@ -41,7 +41,7 @@ define [
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
targetGroup = @$('option:selected').val()
|
||||
if targetGroup then @group.collection.category.reassignUser(@model, targetGroup)
|
||||
if targetGroup then @group.collection.category.reassignUser(@model, @group.collection.get(targetGroup))
|
||||
@close()
|
||||
# focus override to the user's new group heading if they're moved
|
||||
$("[data-id='#{targetGroup}'] .group-heading")?.focus()
|
||||
|
@ -54,7 +54,7 @@ define [
|
|||
hasGroups = groupCollection.length > 0
|
||||
{
|
||||
allFull: hasGroups and groupCollection.models.every (g) -> g.isFull()
|
||||
groupId: @group.get('id')
|
||||
groupId: @group.id
|
||||
userName: @model.get('name')
|
||||
groups: groupCollection.toJSON()
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ define [
|
|||
|
||||
toJSON: ->
|
||||
json = @model.toJSON()
|
||||
json.leader = @model.get('leader')
|
||||
json.canAssignUsers = ENV.IS_LARGE_ROSTER and not @model.isLocked()
|
||||
json.canEdit = not @model.isLocked()
|
||||
json.summary = @summary()
|
||||
|
|
|
@ -34,4 +34,7 @@ define [
|
|||
, 1000
|
||||
|
||||
toJSON: ->
|
||||
_.extend {}, this, super
|
||||
_.extend {groupId: @model.get('group')?.id}, this, super
|
||||
|
||||
isLeader: ->
|
||||
@model.get('group')?.get?('leader')?.id == @model.get('id')
|
||||
|
|
|
@ -38,6 +38,7 @@ define [
|
|||
|
||||
attach: ->
|
||||
@model.on 'change:members_count', @render
|
||||
@model.on 'change:leader', @render
|
||||
@collection.on 'moved', @highlightUser
|
||||
|
||||
highlightUser: (user) ->
|
||||
|
@ -49,13 +50,26 @@ define [
|
|||
|
||||
events:
|
||||
'click .remove-from-group': 'removeUserFromGroup'
|
||||
'click .remove-as-leader': 'removeLeader'
|
||||
'click .set-as-leader': 'setLeader'
|
||||
'click .edit-group-assignment': 'editGroupAssignment'
|
||||
|
||||
removeUserFromGroup: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$target = $(e.currentTarget)
|
||||
@collection.get($target.data('user-id')).save 'groupId', null
|
||||
@collection.get($target.data('user-id')).save 'group', null
|
||||
|
||||
removeLeader: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@model.save(leader: null)
|
||||
|
||||
setLeader: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$target = $(e.currentTarget)
|
||||
@model.save(leader: {id: $target.data('user-id').toString()})
|
||||
|
||||
editGroupAssignment: (e) ->
|
||||
e.preventDefault()
|
||||
|
|
|
@ -73,7 +73,7 @@ define [
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$target = $(e.currentTarget)
|
||||
@addUnassignedMenu.groupId = @model.id
|
||||
@addUnassignedMenu.group = @model
|
||||
@addUnassignedMenu.showBy $target, e.type is 'click'
|
||||
|
||||
hideAddUser: (e) ->
|
||||
|
@ -93,4 +93,4 @@ define [
|
|||
user = ui.draggable.data('model')
|
||||
newGroupId = $(e.currentTarget).data('id')
|
||||
setTimeout =>
|
||||
@model.collection.category.reassignUser(user, newGroupId)
|
||||
@model.collection.category.reassignUser(user, @model.collection.get(newGroupId))
|
||||
|
|
|
@ -4,14 +4,13 @@ define [
|
|||
'compiled/views/groups/manage/GroupView'
|
||||
'compiled/views/groups/manage/GroupUsersView'
|
||||
'compiled/views/groups/manage/GroupDetailView'
|
||||
'compiled/views/groups/manage/Scrollable'
|
||||
'compiled/views/Filterable'
|
||||
'jst/groups/manage/groups'
|
||||
], (_, PaginatedCollectionView, GroupView, GroupUsersView, GroupDetailView, Scrollable, Filterable, template) ->
|
||||
], (_, PaginatedCollectionView, GroupView, GroupUsersView, GroupDetailView, Filterable, template) ->
|
||||
|
||||
class GroupsView extends PaginatedCollectionView
|
||||
|
||||
@mixin Filterable, Scrollable
|
||||
@mixin Filterable
|
||||
|
||||
template: template
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ class GroupsController < ApplicationController
|
|||
include Api::V1::Group
|
||||
include Api::V1::GroupCategory
|
||||
|
||||
SETTABLE_GROUP_ATTRIBUTES = %w(name description join_level is_public group_category avatar_attachment storage_quota_mb max_membership)
|
||||
SETTABLE_GROUP_ATTRIBUTES = %w(name description join_level is_public group_category avatar_attachment storage_quota_mb max_membership leader)
|
||||
|
||||
include TextHelper
|
||||
|
||||
|
@ -494,6 +494,14 @@ class GroupsController < ApplicationController
|
|||
attrs[:avatar_attachment] = @group.active_images.find_by_id(avatar_id)
|
||||
end
|
||||
|
||||
if attrs[:leader]
|
||||
membership = @group.group_memberships.find_by_user_id(attrs[:leader][:id])
|
||||
return render :json => {}, :status => :bad_request unless membership
|
||||
attrs[:leader] = membership.user
|
||||
else
|
||||
attrs[:leader] = nil
|
||||
end
|
||||
|
||||
if authorized_action(@group, @current_user, :update)
|
||||
respond_to do |format|
|
||||
if @group.update_attributes(attrs.slice(*SETTABLE_GROUP_ATTRIBUTES))
|
||||
|
|
|
@ -21,7 +21,7 @@ class Group < ActiveRecord::Base
|
|||
include Workflow
|
||||
include CustomValidations
|
||||
|
||||
attr_accessible :name, :context, :max_membership, :group_category, :join_level, :default_view, :description, :is_public, :avatar_attachment, :storage_quota_mb
|
||||
attr_accessible :name, :context, :max_membership, :group_category, :join_level, :default_view, :description, :is_public, :avatar_attachment, :storage_quota_mb, :leader
|
||||
validates_presence_of :context_id, :context_type, :account_id, :root_account_id, :workflow_state
|
||||
validates_allowed_transitions :is_public, false => true
|
||||
|
||||
|
@ -61,6 +61,7 @@ class Group < ActiveRecord::Base
|
|||
has_many :zip_file_imports, :as => :context
|
||||
has_many :content_migrations, :as => :context
|
||||
belongs_to :avatar_attachment, :class_name => "Attachment"
|
||||
belongs_to :leader, :class_name => "User"
|
||||
|
||||
EXPORTABLE_ATTRIBUTES = [
|
||||
:id, :name, :workflow_state, :created_at, :updated_at, :context_id, :context_type, :category, :max_membership, :hashtag, :show_public_context_messages, :is_public,
|
||||
|
@ -432,6 +433,9 @@ class Group < ActiveRecord::Base
|
|||
can :moderate_forum and
|
||||
can :update
|
||||
|
||||
given { |user| user && self.leader == user }
|
||||
can :update
|
||||
|
||||
given { |user| self.group_category.try(:communities?) }
|
||||
can :create
|
||||
|
||||
|
|
|
@ -36,9 +36,11 @@ class GroupMembership < ActiveRecord::Base
|
|||
before_validation :verify_section_homogeneity_if_necessary
|
||||
validate :validate_within_group_limit
|
||||
|
||||
after_save :revoke_leadership_if_necessary
|
||||
after_save :ensure_mutually_exclusive_membership
|
||||
after_save :touch_groups
|
||||
after_save :update_cached_due_dates
|
||||
after_destroy :revoke_leadership
|
||||
after_destroy :touch_groups
|
||||
|
||||
has_a_broadcast_policy
|
||||
|
@ -104,6 +106,21 @@ class GroupMembership < ActiveRecord::Base
|
|||
end
|
||||
protected :auto_join
|
||||
|
||||
def revoke_leadership
|
||||
self.group.update_attribute(:leader, nil) if self.group && self.group.leader == self.user
|
||||
end
|
||||
protected :revoke_leadership
|
||||
|
||||
def revoke_leadership_if_necessary
|
||||
if self.old_group_id # moved to new group
|
||||
old_group = Group.find(self.old_group_id)
|
||||
old_group.update_attribute(:leader, nil) if old_group && old_group.leader == self.user
|
||||
elsif self.deleted? && self.group && self.group.leader == self.user # deleted
|
||||
self.group.update_attribute(:leader, nil)
|
||||
end
|
||||
end
|
||||
protected :revoke_leadership_if_necessary
|
||||
|
||||
def ensure_mutually_exclusive_membership
|
||||
return unless self.group
|
||||
return if self.deleted?
|
||||
|
|
|
@ -51,22 +51,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
.group-leader {
|
||||
color: #555;
|
||||
|
||||
a {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
.group-header {
|
||||
.group-summary {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
.group .group-user {
|
||||
float: left;
|
||||
width: 31%;
|
||||
margin: 0 2% 3px 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.group-category-contents-condensed & {
|
||||
width: 23.5%;
|
||||
margin: 0 1% 3px 0;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
|
||||
.group-user-name {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.group-leader {
|
||||
float: right;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.group-user-name {
|
||||
max-width: 85%;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<div class="row-fluid">
|
||||
<div class="{{#if canAssignUsers}}span7{{else}}span6{{/if}} ellipsis">
|
||||
<div class="row-fluid group-header">
|
||||
<div class="{{#if leader}}span5{{else}}span8{{/if}} ellipsis">
|
||||
<a href="#" class="toggle-group group-heading" aria-label="{{#t "show_group_details"}}Show details for group {{name}}{{/t}}" title="{{name}}" aria-expanded="false">
|
||||
<i class="group-collapsed-item icon-mini-arrow-right"></i>
|
||||
<i class="group-expanded-item icon-mini-arrow-down"></i>
|
||||
<span class="group-name">{{name}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="{{#if canAssignUsers}}span3{{else}}span4{{/if}} ellipsis">
|
||||
<span class="toggle-group group-summary">{{summary}}</span>
|
||||
{{#if leader}}
|
||||
<div class="span3 ellipsis group-leader">
|
||||
<i class="icon-user"></i>
|
||||
<a href="{{leader.html_url}}">
|
||||
<span class="screenreader-only">{{#t "group_leader"}}Group Leader{{/t}}</span>
|
||||
{{leader.display_name}}
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="span2 ellipsis">
|
||||
<span class="label label-info show-group-full">{{#t "group_full"}}Full{{/t}}</span>
|
||||
<br class="show-group-full"/>
|
||||
<span class="toggle-group group-summary">{{summary}}</span>
|
||||
</div>
|
||||
<div class="span2 group-actions">
|
||||
{{#if canAssignUsers}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="group-user-name ellipsis" title="{{name}}"><i class="icon-drag-handle"></i>{{name}}</div>
|
||||
<div class="group-user-name ellipsis" title="{{name}}"><i class="icon-drag-handle"></i>{{#if isLeader}}<i class="icon-user group-leader"></i>{{/if}}{{name}}</div>
|
||||
{{#if canAssignToGroup}}
|
||||
<a href="#"
|
||||
data-user-id="{{id}}"
|
||||
|
@ -9,7 +9,7 @@
|
|||
</a>
|
||||
{{/if}}
|
||||
{{#if canEditGroupAssignment}}
|
||||
<div class="inline-block" role="application">
|
||||
<div role="application">
|
||||
<a href="#"
|
||||
id='group-{{groupId}}-user-{{id}}-actions'
|
||||
data-user-id="{{id}}"
|
||||
|
@ -18,18 +18,37 @@
|
|||
role="button">
|
||||
<i class="icon-settings"></i><i class="icon-mini-arrow-down"></i>
|
||||
<span class="screenreader-only">
|
||||
{{#t "edit_user_group_assignment"}}Remove or move {{name}} from group{{/t}}
|
||||
{{#t "edit_group_membership"}}Edit {{name}}'s membership{{/t}}
|
||||
</span>
|
||||
</a>
|
||||
<ul class="al-options">
|
||||
<li>
|
||||
<a href="#"
|
||||
data-user-id="{{id}}"
|
||||
aria-label="{{#t "remove_from_group"}}Remove {{name}} from group{{/t}}"
|
||||
class="icon-trash remove-from-group">
|
||||
{{#t "remove"}}Remove{{/t}}
|
||||
</a>
|
||||
</li>
|
||||
{{#if isLeader}}
|
||||
<li>
|
||||
<a href="#"
|
||||
data-user-id="{{id}}"
|
||||
aria-label="{{#t "remove_user_as_leader"}}Remove {{name}} as leader{{/t}}"
|
||||
class="icon-user remove-as-leader">
|
||||
{{#t "remove_as_leader"}}Remove as Leader{{/t}}
|
||||
</a>
|
||||
</li>
|
||||
{{else}}
|
||||
<li>
|
||||
<a href="#"
|
||||
data-user-id="{{id}}"
|
||||
aria-label="{{#t "remove_from_group"}}Remove {{name}} from group{{/t}}"
|
||||
class="icon-trash remove-from-group">
|
||||
{{#t "remove"}}Remove{{/t}}
|
||||
aria-label="{{#t "set_user_as_leader"}}Set {{name}} as leader{{/t}}"
|
||||
class="icon-user set-as-leader">
|
||||
{{#t "set_as_leader"}}Set as Leader{{/t}}
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li>
|
||||
<a href="#"
|
||||
data-focus-returns-to='group-{{groupId}}-user-{{id}}-actions'
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
class AddGroupLeaderIdToGroups < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_column :groups, :leader_id, :integer, :limit => 8
|
||||
add_foreign_key :groups, :users, column: :leader_id
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_foreign_key :groups, column: :leader_id
|
||||
remove_column :groups, :leader_id
|
||||
end
|
||||
end
|
|
@ -41,6 +41,8 @@ module Api::V1::Group
|
|||
image = group.avatar_attachment
|
||||
hash['avatar_url'] = image && thumbnail_image_url(image, image.uuid)
|
||||
hash['role'] = group.group_category.role if group.group_category
|
||||
#hash['leader_id'] = group.leader_id
|
||||
hash['leader'] = group.leader ? user_display_json(group.leader, group) : nil
|
||||
|
||||
if includes.include?('users')
|
||||
# TODO: this should be switched to user_display_json
|
||||
|
|
|
@ -34,7 +34,8 @@ describe "Groups API", type: :request do
|
|||
"#{group.context_type.downcase}_id" => group.context_id,
|
||||
'role' => group.group_category.role,
|
||||
'group_category_id' => group.group_category_id,
|
||||
'storage_quota_mb' => group.storage_quota_mb
|
||||
'storage_quota_mb' => group.storage_quota_mb,
|
||||
'leader' => group.leader
|
||||
}
|
||||
if group.context_type == 'Account' && is_admin == true
|
||||
json['sis_import_id'] = group.sis_batch_id
|
||||
|
|
|
@ -10,6 +10,7 @@ define [
|
|||
source = null
|
||||
target = null
|
||||
users = null
|
||||
group = null
|
||||
|
||||
module 'GroupUserCollection',
|
||||
setup: ->
|
||||
|
@ -17,8 +18,8 @@ define [
|
|||
category = new GroupCategory()
|
||||
category._groups = new Collection([group])
|
||||
users = [
|
||||
new GroupUser(id: 1, name: "bob", sortable_name: "bob", groupId: null),
|
||||
new GroupUser(id: 2, name: "joe", sortable_name: "joe", groupId: null)
|
||||
new GroupUser(id: 1, name: "bob", sortable_name: "bob", group: null),
|
||||
new GroupUser(id: 2, name: "joe", sortable_name: "joe", group: null)
|
||||
]
|
||||
source = new UnassignedGroupUserCollection users, {category}
|
||||
category._unassignedUsers = source
|
||||
|
@ -26,13 +27,12 @@ define [
|
|||
target.loaded = true
|
||||
group._users = target
|
||||
|
||||
test "moves user to target group's collection when groupId changes", ->
|
||||
users[0].set('groupId', 1)
|
||||
test "moves user to target group's collection when group changes", ->
|
||||
users[0].set('group', group)
|
||||
equal source.length, 1
|
||||
equal target.length, 1
|
||||
|
||||
test "removes user when target group's collection is not yet loaded", ->
|
||||
users[0].set('groupId', 2) # not the target
|
||||
users[0].set('group', new Group(id: 2)) # not the target
|
||||
equal source.length, 1
|
||||
equal target.length, 0
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
define [
|
||||
'Backbone'
|
||||
'compiled/models/Group'
|
||||
'compiled/models/GroupUser'
|
||||
'compiled/models/GroupCategory'
|
||||
'jquery'
|
||||
], (Backbone, GroupUser, GroupCategory, $) ->
|
||||
], (Backbone, Group, GroupUser, GroupCategory, $) ->
|
||||
|
||||
module 'GroupUser',
|
||||
setup: ->
|
||||
|
@ -15,19 +16,21 @@ define [
|
|||
@leaveGroupStub.restore()
|
||||
@joinGroupStub.restore()
|
||||
|
||||
test "updates groupId correctly upon save and fires joinGroup and leaveGroup appropriately", ->
|
||||
@groupUser.save({'groupId': 777})
|
||||
equal @groupUser.get('groupId'), 777
|
||||
test "updates group correctly upon save and fires joinGroup and leaveGroup appropriately", ->
|
||||
group1 = new Group(id: 777)
|
||||
@groupUser.save({'group': group1})
|
||||
equal @groupUser.get('group'), group1
|
||||
equal @joinGroupStub.callCount, 1
|
||||
ok @joinGroupStub.calledWith 777
|
||||
ok @joinGroupStub.calledWith group1
|
||||
equal @leaveGroupStub.callCount, 0
|
||||
|
||||
@groupUser.save({'groupId': 123})
|
||||
equal @groupUser.get('groupId'), 123
|
||||
group2 = new Group(id: 123)
|
||||
@groupUser.save({'group': group2})
|
||||
equal @groupUser.get('group'), group2
|
||||
equal @joinGroupStub.callCount, 2
|
||||
ok @joinGroupStub.calledWith 123
|
||||
ok @joinGroupStub.calledWith group2
|
||||
|
||||
@groupUser.save({'groupId': null})
|
||||
equal @groupUser.get('groupId'), null
|
||||
@groupUser.save({'group': null})
|
||||
equal @groupUser.get('group'), null
|
||||
equal @joinGroupStub.callCount, 2
|
||||
equal @leaveGroupStub.callCount, 1
|
|
@ -38,7 +38,7 @@ define [
|
|||
users.loaded = true
|
||||
view = new AddUnassignedMenu
|
||||
collection: users
|
||||
view.groupId = 777
|
||||
view.group = new Group(id: 777)
|
||||
users.reset([
|
||||
new GroupUser(id: 1, name: "Frank Herbert", sortable_name: "Herbert, Frank"),
|
||||
new GroupUser(id: 2, name: "Neal Stephenson", sortable_name: "Stephenson, Neal"),
|
||||
|
@ -53,14 +53,14 @@ define [
|
|||
server.restore()
|
||||
view.remove()
|
||||
|
||||
test "updates the user's groupId and removes from unassigned collection", ->
|
||||
equal waldo.get('groupId'), null
|
||||
test "updates the user's group and removes from unassigned collection", ->
|
||||
equal waldo.get('group'), null
|
||||
$links = view.$('.assign-user-to-group')
|
||||
equal $links.length, 4
|
||||
|
||||
$waldoLink = $links.last()
|
||||
$waldoLink.click()
|
||||
sendResponse 'POST',"/api/v1/groups/777/memberships", {}
|
||||
equal waldo.get('groupId'), 777
|
||||
equal waldo.get('group'), view.group
|
||||
|
||||
ok not users.contains(waldo)
|
||||
|
|
|
@ -13,7 +13,7 @@ define [
|
|||
module 'AssignToGroupMenu',
|
||||
setup: ->
|
||||
groupCategory = new GroupCategory
|
||||
user = new GroupUser(id: 1, name: "bob", groupId: null, category: groupCategory)
|
||||
user = new GroupUser(id: 1, name: "bob", group: null, category: groupCategory)
|
||||
groups = new GroupCollection [
|
||||
new Group id: 1, name: "a group"
|
||||
], {category: groupCategory}
|
||||
|
@ -26,11 +26,9 @@ define [
|
|||
teardown: ->
|
||||
view.remove()
|
||||
|
||||
test "updates the user's groupId", ->
|
||||
equal user.get('groupId'), null
|
||||
test "updates the user's group", ->
|
||||
equal user.get('group'), null
|
||||
$link = view.$('.set-group')
|
||||
equal $link.length, 1
|
||||
$link.click()
|
||||
equal user.get('groupId'), 1
|
||||
|
||||
|
||||
equal user.get('group').id, 1
|
||||
|
|
|
@ -249,6 +249,58 @@ describe GroupMembership do
|
|||
end
|
||||
end
|
||||
|
||||
describe "group leadership revocation" do
|
||||
before(:each) do
|
||||
course
|
||||
@category = @course.group_categories.build(:name => "category 1")
|
||||
@category.save!
|
||||
@group = @category.groups.create!(:context => @course)
|
||||
@leader = user_model
|
||||
@leader_membership = @group.group_memberships.create!(:user => @leader, :workflow_state => 'accepted')
|
||||
@group.leader = @leader
|
||||
@group.save!
|
||||
@membership = @group.group_memberships.create!(:user => user_model, :workflow_state => 'accepted')
|
||||
@group.reload
|
||||
@leader_membership.reload
|
||||
end
|
||||
|
||||
context "leader membership" do
|
||||
it "should revoke when deleted" do
|
||||
@group.leader.should_not be_nil
|
||||
@leader_membership.destroy!
|
||||
@group.reload.leader.should be_nil
|
||||
end
|
||||
|
||||
it "should revoke when soft deleted" do
|
||||
@group.leader.should_not be_nil
|
||||
@leader_membership.destroy
|
||||
@group.reload.leader.should be_nil
|
||||
end
|
||||
|
||||
it "should revoke when group is changed" do
|
||||
@group.leader.should_not be_nil
|
||||
group2 = @category.groups.create!(:context => @course)
|
||||
@leader_membership.update_attribute(:group_id, group2.id)
|
||||
@group.reload.leader.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "non-leader membership" do
|
||||
it "should not revoke when deleted" do
|
||||
@group.leader.should_not be_nil
|
||||
@membership.destroy!
|
||||
@group.reload.leader.should_not be_nil
|
||||
end
|
||||
|
||||
it "should not revoke when group is changed" do
|
||||
@group.leader.should_not be_nil
|
||||
group2 = @category.groups.create!(:context => @course)
|
||||
@membership.update_attribute(:group_id, group2.id)
|
||||
@group.reload.leader.should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating cached due dates" do
|
||||
before do
|
||||
course
|
||||
|
|
Loading…
Reference in New Issue