adds api endpoint for randomly assigning unassigned members in a group category
fixes CNVS-6696 test plan: 1) use postman to make a request to this new endpoint for a group category that has unassigned members --asynchronous example: POST http://localhost:3000/api/v1/group_categories/<group_category_id>/assign_unassigned_members --synchronous example: POST http://localhost:3000/api/v1/group_categories/<group_category_id> \ /assign_unassigned_members?sync=true 2) verify that the group category's unassigned members have now been assigned among the available groups --NOTE: this may take a minute depending on the number of unassigned members 3) verify the api documentation for Group Categories - "Assign unassigned members" makes sense 4) verify that an asychronous request returns a Progress JSON (see api documentation for example) 5) verify that a synchronous request returns an array of Group Memberships (see documentation for example) NOTE: I've made some tweaks to the current Groups page such that you can still randomly assign students. However, this shouldn't matter because it's all getting replaced anyway. Change-Id: I894ff2b1e11c7919a110b5159710683869caedc4 Reviewed-on: https://gerrit.instructure.com/22044 Reviewed-by: Jon Jensen <jon@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com>
This commit is contained in:
parent
c2a5ae1239
commit
261d7725dc
|
@ -55,11 +55,12 @@
|
|||
class GroupCategoriesController < ApplicationController
|
||||
before_filter :get_context
|
||||
before_filter :require_context, :only => [:create, :index]
|
||||
before_filter :get_category_context, :only => [:show, :update, :destroy, :groups, :users]
|
||||
before_filter :get_category_context, :only => [:show, :update, :destroy, :groups, :users, :assign_unassigned_members]
|
||||
|
||||
include Api::V1::Attachment
|
||||
include Api::V1::GroupCategory
|
||||
include Api::V1::Group
|
||||
include Api::V1::Progress
|
||||
|
||||
SETTABLE_GROUP_ATTRIBUTES = %w(name description join_level is_public group_category avatar_attachment)
|
||||
|
||||
|
@ -284,6 +285,119 @@ class GroupCategoriesController < ApplicationController
|
|||
render :json => users.map { |u| user_json(u, @current_user, session, [], @context) }
|
||||
end
|
||||
|
||||
# @API Assign unassigned members
|
||||
#
|
||||
# Assign all unassigned members as evenly as possible among the existing
|
||||
# student groups.
|
||||
#
|
||||
# @argument sync (optional)
|
||||
# The assigning is done asynchronously by default. If you would like to
|
||||
# override this and have the assigning done synchronously, set this value
|
||||
# to true.
|
||||
#
|
||||
# @example_request
|
||||
# curl https://<canvas>/api/v1/group_categories/1/assign_unassigned_members \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# # Progress (default)
|
||||
# {
|
||||
# "completion": 0,
|
||||
# "context_id": 20,
|
||||
# "context_type": "GroupCategory",
|
||||
# "created_at": "2013-07-05T10:57:48-06:00",
|
||||
# "id": 2,
|
||||
# "message": null,
|
||||
# "tag": "assign_unassigned_members",
|
||||
# "updated_at": "2013-07-05T10:57:48-06:00",
|
||||
# "user_id": null,
|
||||
# "workflow_state": "running",
|
||||
# "url": "http://localhost:3000/api/v1/progress/2"
|
||||
# }
|
||||
#
|
||||
# @example_response
|
||||
# # New Group Memberships (when sync = true)
|
||||
# [
|
||||
# {
|
||||
# "id": 65,
|
||||
# "new_members": [
|
||||
# {
|
||||
# "user_id": 2,
|
||||
# "name": "Sam",
|
||||
# "display_name": "Sam",
|
||||
# "sections": [
|
||||
# {
|
||||
# "section_id": 1,
|
||||
# "section_code": "Section 1"
|
||||
# }
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# "user_id": 3,
|
||||
# "name": "Sue",
|
||||
# "display_name": "Sue",
|
||||
# "sections": [
|
||||
# {
|
||||
# "section_id": 2,
|
||||
# "section_code": "Section 2"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# "id": 66,
|
||||
# "new_members": [
|
||||
# {
|
||||
# "user_id": 5,
|
||||
# "name": "Joe",
|
||||
# "display_name": "Joe",
|
||||
# "sections": [
|
||||
# {
|
||||
# "section_id": 2,
|
||||
# "section_code": "Section 2"
|
||||
# }
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# "user_id": 11,
|
||||
# "name": "Cecil",
|
||||
# "display_name": "Cecil",
|
||||
# "sections": [
|
||||
# {
|
||||
# "section_id": 3,
|
||||
# "section_code": "Section 3"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
#
|
||||
# @returns Group Membership or Progress
|
||||
def assign_unassigned_members
|
||||
return unless authorized_action(@context, @current_user, :manage_groups)
|
||||
|
||||
# option disabled for student organized groups or section-restricted
|
||||
# self-signup groups. (but self-signup is ignored for non-Course groups)
|
||||
return render(:json => {}, :status => :bad_request) if @group_category.student_organized?
|
||||
return render(:json => {}, :status => :bad_request) if @context.is_a?(Course) && @group_category.restricted_self_signup?
|
||||
|
||||
if value_to_boolean(params[:sync])
|
||||
# do the distribution and note the changes
|
||||
memberships = @group_category.assign_unassigned_members
|
||||
|
||||
# render the changes
|
||||
json = memberships.group_by{ |m| m.group_id }.map do |group_id, new_members|
|
||||
{ :id => group_id, :new_members => new_members.map{ |m| m.user.group_member_json(@context) } }
|
||||
end
|
||||
render :json => json
|
||||
else
|
||||
@group_category.assign_unassigned_members_in_background
|
||||
render :json => progress_json(@group_category.current_progress, @current_user, session)
|
||||
end
|
||||
end
|
||||
|
||||
def populate_group_category_from_params
|
||||
if api_request?
|
||||
args = params
|
||||
|
@ -372,3 +486,7 @@ class GroupCategoriesController < ApplicationController
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -98,7 +98,6 @@ class GroupsController < ApplicationController
|
|||
include Api::V1::Attachment
|
||||
include Api::V1::Group
|
||||
include Api::V1::UserFollow
|
||||
include Api::V1::Progress
|
||||
|
||||
SETTABLE_GROUP_ATTRIBUTES = %w(name description join_level is_public group_category avatar_attachment storage_quota_mb)
|
||||
|
||||
|
@ -636,33 +635,6 @@ class GroupsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def assign_unassigned_members
|
||||
return unless authorized_action(@context, @current_user, :manage_groups)
|
||||
|
||||
# valid category?
|
||||
category = @context.group_categories.find_by_id(params[:category_id])
|
||||
return render(:json => {}, :status => :not_found) unless category
|
||||
|
||||
# option disabled for student organized groups or section-restricted
|
||||
# self-signup groups. (but self-signup is ignored for non-Course groups)
|
||||
return render(:json => {}, :status => :bad_request) if category.student_organized?
|
||||
return render(:json => {}, :status => :bad_request) if @context.is_a?(Course) && category.restricted_self_signup?
|
||||
|
||||
if value_to_boolean(params[:async])
|
||||
category.assign_unassigned_members_in_background
|
||||
render :json => progress_json(category.current_progress, @current_user, session)
|
||||
else
|
||||
# do the distribution and note the changes
|
||||
memberships = category.assign_unassigned_members
|
||||
|
||||
# render the changes
|
||||
json = memberships.group_by{ |m| m.group_id }.map do |group_id, new_members|
|
||||
{ :id => group_id, :new_members => new_members.map{ |m| m.user.group_member_json(@context) } }
|
||||
end
|
||||
render :json => json
|
||||
end
|
||||
end
|
||||
|
||||
# @API Upload a file
|
||||
#
|
||||
# Upload a file to the group.
|
||||
|
|
|
@ -197,6 +197,11 @@ class GroupCategory < ActiveRecord::Base
|
|||
send_later_enqueue_args :assign_unassigned_members, :priority => Delayed::LOW_PRIORITY
|
||||
end
|
||||
|
||||
set_policy do
|
||||
given { |user, session| context.grants_right?(user, session, :read) }
|
||||
can :read
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def start_progress
|
||||
|
|
|
@ -169,7 +169,7 @@ a.load_members_link, .loading_members {
|
|||
:remove_user_url => group_remove_user_url("{{ id }}"),
|
||||
:list_users_url => group_members_url("{{ id }}") ,
|
||||
:list_unassigned_users_url => context_url(@context, :context_group_unassigned_members_url),
|
||||
:assign_unassigned_users_url => context_url(@context, :context_group_assign_unassigned_members_url, :category_id => "{{ category_id }}"),
|
||||
:assign_unassigned_users_url => api_v1_group_category_assign_unassigned_members_url(:group_category_id => "{{ category_id }}"),
|
||||
:add_group_url => context_url(@context, :context_groups_url) %>
|
||||
|
||||
<div id="tabs_loading_wrapper" style="display: none;">
|
||||
|
|
|
@ -67,7 +67,6 @@ FakeRails3Routes.draw do
|
|||
resources :group_categories, :only => [:create, :update, :destroy]
|
||||
match 'group_unassigned_members' => 'groups#unassigned_members', :as => :group_unassigned_members, :via => :get
|
||||
match 'group_unassigned_members.:format' => 'groups#unassigned_members', :as => :group_unassigned_members, :via => :get
|
||||
match 'group_assign_unassigned_members' => 'groups#assign_unassigned_members', :as => :group_assign_unassigned_members, :via => :post
|
||||
end
|
||||
|
||||
concern :files do
|
||||
|
@ -1301,6 +1300,7 @@ FakeRails3Routes.draw do
|
|||
post 'courses/:course_id/group_categories', :action => :create
|
||||
get 'group_categories/:group_category_id/groups', :action => :groups, :path_name => 'group_category_groups'
|
||||
get 'group_categories/:group_category_id/users', :action => :users, :path_name => 'group_category_users'
|
||||
post 'group_categories/:group_category_id/assign_unassigned_members', :action => 'assign_unassigned_members', :path_name => 'group_category_assign_unassigned_members'
|
||||
end
|
||||
|
||||
scope(:controller => :progress) do
|
||||
|
|
|
@ -740,7 +740,7 @@ define([
|
|||
// perform ajax request to do the assignment server side
|
||||
var url = ENV.assign_unassigned_users_url;
|
||||
url = $.replaceTags(url, "category_id", $category.data('category_id'));
|
||||
$.ajaxJSON(url, "POST", null, function(data) {
|
||||
$.ajaxJSON(url, "POST", {'sync': true}, function(data) {
|
||||
if (!data.length) {
|
||||
// reset visual state
|
||||
$unassigned.find(".assign_students_link").show();
|
||||
|
|
|
@ -369,6 +369,103 @@ describe "Group Categories API", :type => :integration do
|
|||
response.code.should == '401'
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'assign_unassigned_members'" do
|
||||
it "should require :manage_groups permission" do
|
||||
course_with_teacher(:active_all => true)
|
||||
student = @course.enroll_student(user_model).user
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
user_session(student)
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param),
|
||||
{'sync' => true}
|
||||
response.status.should == '401 Unauthorized'
|
||||
end
|
||||
|
||||
it "should require valid group :category_id" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id + 1}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => (category.id + 1).to_param),
|
||||
{'sync' => true}
|
||||
response.status.should == '404 Not Found'
|
||||
end
|
||||
|
||||
it "should fail for student organized groups" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = GroupCategory.student_organized_for(@course)
|
||||
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param),
|
||||
{'sync' => true}
|
||||
response.status.should == '400 Bad Request'
|
||||
end
|
||||
|
||||
it "should fail for restricted self signup groups" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.build(:name => "Group Category")
|
||||
category.configure_self_signup(true, true)
|
||||
category.save
|
||||
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param),
|
||||
{'sync' => true}
|
||||
response.status.should == '400 Bad Request'
|
||||
|
||||
category.configure_self_signup(true, false)
|
||||
category.save
|
||||
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param),
|
||||
{'sync' => true}
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "should otherwise assign ungrouped users to groups in the category" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
teacher = @user
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
group1 = category.groups.create(:name => "Group 1", :context => @course)
|
||||
group2 = category.groups.create(:name => "Group 2", :context => @course)
|
||||
student1 = @course.enroll_student(user_model).user
|
||||
student2 = @course.enroll_student(user_model).user # not in a group
|
||||
group2.add_user(student1)
|
||||
|
||||
@user = teacher
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param)
|
||||
|
||||
response.should be_success
|
||||
|
||||
run_jobs
|
||||
|
||||
group1.reload.users.should include(student2)
|
||||
end
|
||||
|
||||
it "should render progress_json" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
expect {
|
||||
raw_api_call :post, "/api/v1/group_categories/#{category.id}/assign_unassigned_members",
|
||||
@category_path_options.merge(:action => 'assign_unassigned_members',
|
||||
:group_category_id => category.to_param)
|
||||
|
||||
response.should be_success
|
||||
json = JSON.parse(response.body)
|
||||
json['url'].should =~ Regexp.new("http://www.example.com/api/v1/progress/\\d+")
|
||||
json['completion'].should == 0
|
||||
}.to change(Delayed::Job, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "account group categories" do
|
||||
|
|
|
@ -476,80 +476,6 @@ describe GroupsController do
|
|||
end
|
||||
end
|
||||
|
||||
context "POST 'assign_unassigned_members'" do
|
||||
it "should require :manage_groups permission" do
|
||||
course_with_teacher(:active_all => true)
|
||||
student = @course.enroll_student(user_model).user
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
user_session(student)
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id
|
||||
response.status.should == '401 Unauthorized'
|
||||
end
|
||||
|
||||
it "should require valid group :category_id" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id + 1
|
||||
response.status.should == '404 Not Found'
|
||||
end
|
||||
|
||||
it "should fail for student organized groups" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = GroupCategory.student_organized_for(@course)
|
||||
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id
|
||||
response.status.should == '400 Bad Request'
|
||||
end
|
||||
|
||||
it "should fail for restricted self signup groups" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.build(:name => "Group Category")
|
||||
category.configure_self_signup(true, true)
|
||||
category.save
|
||||
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id
|
||||
response.status.should == '400 Bad Request'
|
||||
|
||||
category.configure_self_signup(true, false)
|
||||
category.save
|
||||
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "should otherwise assign ungrouped users to groups in the category" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
group1 = category.groups.create(:name => "Group 1", :context => @course)
|
||||
group2 = category.groups.create(:name => "Group 2", :context => @course)
|
||||
student1 = @course.enroll_student(user_model).user
|
||||
student2 = @course.enroll_student(user_model).user # not in a group
|
||||
group2.add_user(student1)
|
||||
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id, :async => 1
|
||||
response.should be_success
|
||||
|
||||
run_jobs
|
||||
|
||||
group1.reload.users.should include(student2)
|
||||
end
|
||||
|
||||
it "should render progress_json" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
category = @course.group_categories.create(:name => "Group Category")
|
||||
|
||||
expect {
|
||||
post 'assign_unassigned_members', :course_id => @course.id, :category_id => category.id, :async => 1
|
||||
response.should be_success
|
||||
json = JSON.parse(response.body)
|
||||
json['url'].should =~ Regexp.new("http://test.host/api/v1/progress/\\d+")
|
||||
json['completion'].should == 0
|
||||
}.to change(Delayed::Job, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'public_feed.atom'" do
|
||||
before(:each) do
|
||||
group_with_user(:active_all => true)
|
||||
|
|
Loading…
Reference in New Issue