message students who haven't joined a group, fixes CNVS-5408

test plan:
1. allow self signup on a group category
2. click the "message students..." link that appears in the Unassigned
   section
3. you should be able to send a message to those students who have not
   joined a group yet

Change-Id: I1766c8ade8233226a16dfd88b53524ae2e3d2f7e
Reviewed-on: https://gerrit.instructure.com/20338
Reviewed-by: Jon Jensen <jon@instructure.com>
Product-Review: Jon Jensen <jon@instructure.com>
QA-Review: Jon Jensen <jon@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
This commit is contained in:
Jon Jensen 2013-05-02 17:43:03 -06:00
parent 88ca0e82da
commit 853e4a163e
8 changed files with 129 additions and 50 deletions

View File

@ -116,24 +116,28 @@ class GroupsController < ApplicationController
category = @context.group_categories.find_by_id(params[:category_id])
return render :json => {}, :status => :not_found unless category
page = (params[:page] || 1).to_i rescue 1
per_page = [[(params[:per_page] || 15).to_i, 1].max, 100].min
if category && !category.student_organized?
groups = category.groups.active
else
groups = []
end
users = @context.paginate_users_not_in_groups(groups, page)
users = @context.paginate_users_not_in_groups(groups, page, per_page)
if authorized_action(@context, @current_user, :manage)
respond_to do |format|
format.json { render :json => {
:pages => users.total_pages,
:current_page => users.current_page,
:next_page => users.next_page,
:previous_page => users.previous_page,
:total_entries => users.total_entries,
:pagination_html => render_to_string(:partial => 'user_pagination', :locals => { :users => users }),
:users => users.map { |u| u.group_member_json(@context) }
} }
format.json {
json = {
:pages => users.total_pages,
:current_page => users.current_page,
:next_page => users.next_page,
:previous_page => users.previous_page,
:total_entries => users.total_entries,
:users => users.map { |u| u.group_member_json(@context) }
}
json[:pagination_html] = render_to_string(:partial => 'user_pagination', :locals => { :users => users }) unless params[:no_html]
render :json => json
}
end
end
end

View File

@ -1,6 +1,7 @@
.message-students-dialog {
textarea {
box-sizing: border-box;
-moz-box-sizing: border-box;
width: 100%;
height: 200px;
}

View File

@ -62,11 +62,15 @@
<h3 class="name blank_name"><%= t :group_category_unassigned, "Unassigned" %></h3>
<% end %>
<% unless @context.is_a?(Account) || protected_category %>
<a href="#" class="assign_students_link no-hover"
title="<%= t :random_distribution_explanation, "Distribute Unassigned Students Equally Among Groups" %>"
style="<%= hidden if category && category.restricted_self_signup? %>">
<%= t :randomly_distribute_students, "randomly assign students" %>
</a>
<span class="student_links">
<a href="#" class="assign_students_link no-hover"
title="<%= t :random_distribution_explanation, "Distribute Unassigned Students Equally Among Groups" %>"
style="<%= hidden if !category || category.restricted_self_signup? %>">
<%= t :randomly_distribute_students, "randomly assign students" %>
</a>
<span style="<%= hidden if !category || !category.self_signup? || category.restricted_self_signup? %>" class="students_link_separator"> | </span>
<a href="#" style="<%= hidden if !category || !category.self_signup? %>" class="message_students_link"><%= t "message_students", "message students..." %></a>
</span>
<% end %>
<div style="display: none;">
<span class="category_name">&nbsp;</span>

View File

@ -165,15 +165,13 @@ a.load_members_link, .loading_members {
<% end %>
</div>
<% else %>
<div style="display: none;" id="manage_group_urls">
<a href="<%= group_add_user_url("{{ id }}") %>" class="add_user_url">&nbsp;</a>
<a href="<%= group_remove_user_url("{{ id }}") %>" class="remove_user_url">&nbsp;</a>
<a href="<%= group_members_url("{{ id }}") %>" class="list_users_url">&nbsp;</a>
<a href="<%= context_url(@context, :context_group_unassigned_members_url) %>" class="list_unassigned_users_url">&nbsp;</a>
<a href="<%= context_url(@context, :context_group_assign_unassigned_members_url, :category_id => "{{ category_id }}") %>" class="assign_unassigned_users_url">&nbsp;</a>
<a href="<%= context_url(@context, :context_groups_url) %>" class="add_group_url" %>&nbsp;</a>
</div>
<% js_env :add_user_url => group_add_user_url("{{ id }}"),
: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 }}"),
:add_group_url => context_url(@context, :context_groups_url) %>
<div id="tabs_loading_wrapper" style="display: none;">
<div style="text-align: left; <%= hidden unless @categories.empty? %>" id="no_groups_message">
<% if @context.is_a?(Account) %>

View File

@ -19,6 +19,8 @@ define([
'i18n!groups',
'jquery' /* $ */,
'underscore',
'compiled/fn/preventDefault',
'compiled/views/MessageStudentsDialog',
'jqueryui/draggable' /* /\.draggable/ */,
'jquery.ajaxJSON' /* ajaxJSON */,
'jquery.instructure_forms' /* formSubmit, fillFormData, formErrors */,
@ -31,7 +33,7 @@ define([
'vendor/jquery.scrollTo' /* /\.scrollTo/ */,
'jqueryui/droppable' /* /\.droppable/ */,
'jqueryui/tabs' /* /\.tabs/ */
], function(I18n, $, _) {
], function(I18n, $, _, preventDefault, MessageStudentsDialog) {
window.contextGroups = {
autoLoadGroupThreshold: 15,
@ -49,7 +51,7 @@ define([
},
loadMembersForGroup: function($group) {
var url = $("#manage_group_urls .list_users_url").attr('href');
var url = ENV.list_users_url;
var id = $group.getTemplateData({textValues: ['group_id']}).group_id;
url = $.replaceTags(url, "id", id)
@ -98,7 +100,7 @@ define([
// This is lots of duplicated code from above, with tweaks. TODO: Refactor
var category_id = $group.closest(".group_category").data('category_id');
var url = $("#manage_group_urls .list_unassigned_users_url").attr('href');
var url = ENV.list_unassigned_users_url;
url += "?category_id=" + category_id + "&page=" + page;
$group.find(".load_members_link").hide();
@ -110,6 +112,7 @@ define([
$group.find(".user_count_hidden").text(data['total_entries'] - data['users'].length);
$group.find(".group_user_count").show();
$group.find(".student").remove();
$group.find(".student_links").showIf(data['total_entries'] > 0);
var $user_template = $(".user_template");
var users = data['users'];
@ -139,10 +142,10 @@ define([
var id = $group.getTemplateData({textValues: ['group_id']}).group_id;
var user_id = $student.getTemplateData({textValues: ['user_id']}).user_id;
var $original_group = $student.parents(".group");
var url = $("#manage_group_urls .add_user_url").attr('href');
var url = ENV.add_user_url;
method = "POST";
if(!id || id.length == 0) {
url = $("#manage_group_urls .remove_user_url").attr('href');
url = ENV.remove_user_url;
method = "DELETE";
id = $original_group.getTemplateData({textValues: ['group_id']}).group_id;
}
@ -246,8 +249,10 @@ define([
updateCategoryCounts: function($category) {
$category.find(".group").each(function() {
var userCount = $(this).find(".student_list .student").length + parseInt($(this).find(".user_count_hidden").text());
$(this).find(".user_count").text(I18n.t('category.student', 'student', {count: userCount}));
var $this = $(this);
var userCount = $this.find(".student_list .student").length + parseInt($this.find(".user_count_hidden").text());
$this.find(".user_count").text(I18n.t('category.student', 'student', {count: userCount}));
$this.find(".student_links").showIf(userCount > 0);
});
var groupCount = $category.find(".group:not(.group_blank)").length;
@ -319,6 +324,8 @@ define([
$category.find('.assign_students_link').showIf(category.self_signup !== 'restricted');
$category.find('.group_limit_blurb').showIf(category.group_limit);
$category.find('.group_limit, .group_limit_text').text(category.group_limit || '');
$category.find('.students_link_separator').showIf(category.self_signup && category.self_signup !== 'restricted');
$category.find('.message_students_link').showIf(category.self_signup);
},
addGroupToSidebar: function(group) {
@ -368,6 +375,7 @@ define([
var $category = $(this).parents(".group_category");
var $group = $("#category_template").find(".group_blank").clone(true);
$group.removeAttr('id');
$group.find(".student_links").remove();
$group.find(".student_list").empty();
$group.removeClass('group_blank');
$group.find(".load-more").hide();
@ -423,7 +431,7 @@ define([
if($group.attr('id')) {
$form.attr('method', 'PUT').attr('action', $group.find(".edit_group_link").attr('href'));
} else {
$form.attr('method', 'POST').attr('action', $("#manage_group_urls .add_group_url").attr('href'));
$form.attr('method', 'POST').attr('action', ENV.add_group_url);
}
if($group.length > 0) {
$group.parents(".group_category").scrollTo($group);
@ -601,6 +609,8 @@ define([
$category.find('.self_signup_text').showIf(group_category.self_signup);
$category.find('.restricted_self_signup_text').showIf(group_category.self_signup == 'restricted');
$category.find('.assign_students_link').showIf(group_category.self_signup !== 'restricted');
$category.find('.students_link_separator').showIf(group_category.self_signup && group_category.self_signup !== 'restricted');
$category.find('.message_students_link').showIf(group_category.self_signup);
var newIndex = $("#group_tabs").tabs('length');
if ($("li.category").last().hasClass('student_organized')) {
@ -728,7 +738,7 @@ define([
$unassigned.find(".loading_members").show();
// perform ajax request to do the assignment server side
var url = $("#manage_group_urls .assign_unassigned_users_url").attr('href');
var url = ENV.assign_unassigned_users_url;
url = $.replaceTags(url, "category_id", $category.data('category_id'));
$.ajaxJSON(url, "POST", null, function(data) {
if (!data.length) {
@ -823,6 +833,44 @@ define([
contextGroups.updateCategoryCounts($(this));
});
$("#tabs_loading_wrapper").show();
function loadUnassignedStudentsFor(categoryId) {
var $studentsDfrd = $.Deferred();
var students = [];
var baseUrl = ENV.list_unassigned_users_url + "?no_html=1&category_id=" + categoryId + "&per_page=100&page=";
var fetch = function(url) {
$.ajaxJSON(url, 'GET', null, function(data) {
_.each(data.users, function(user) {
students.push({id: user.user_id, short_name: user.display_name});
});
if (data.next_page)
fetch(baseUrl + data.next_page);
else
$studentsDfrd.resolve(students);
}, function() { $studentsDfrd.reject(); });
};
fetch(baseUrl + "1");
return $studentsDfrd;
}
$(".message_students_link").click(preventDefault(function() {
// jQuery sadness until we rewrite public/javascripts/manage_groups.js :(
var $category = $(this).closest('.group_category');
var categoryName = $category.find('.category_name').first().text();
var categoryId = $category.data('category_id');
loadUnassignedStudentsFor(categoryId).then(function(students) {
var dialog = new MessageStudentsDialog({
context: categoryName,
recipientGroups: [
{name: I18n.t('students_who_have_not_joined_a_group', 'Students who have not joined a group'), recipients: students}
]});
dialog.open();
});
}));
});
});

View File

@ -30,6 +30,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../common')
keep_trying_until { find_with_jquery("#add_category_form:visible").should be_nil }
category = course.group_categories.find_by_name(name)
category.should_not be_nil
keep_trying_until { fj("#category_#{category.id} .student_links:visible") }
category
end
@ -105,4 +106,4 @@ require File.expand_path(File.dirname(__FILE__) + '/../common')
$('#{from_group} .user_id_#{user_id}'),
$('#{to_group}'))
SCRIPT
end
end

View File

@ -171,6 +171,29 @@ describe "manage groups" do
f('#no_students_message').should be_nil
end
it "should let you message students not in a group" do
groups_student_enrollment 3
group_category1 = @course.group_categories.create(:name => "Project Groups")
group_category2 = @course.group_categories.create(:name => "Self Signup Groups")
group_category2.configure_self_signup(true, false)
group_category2.save
get "/courses/#{@course.id}/groups"
wait_for_ajaximations
ff(".group_category").size.should == 3
keep_trying_until { !f("#category_#{group_category1.id} .right_side .loading_members").displayed? }
f('.group_category .student_links').should be_displayed
f('.group_category .message_students_link').should_not be_displayed # only self signup can do it
ff('.ui-tabs-anchor')[1].click
keep_trying_until { !f("#category_#{group_category2.id} .right_side .loading_members").displayed? }
message_students_link = ff('.group_category .message_students_link')[1]
message_students_link.should be_displayed
message_students_link.click
keep_trying_until{ f('.message-students-dialog').should be_displayed }
end
context "data validation" do
before (:each) do
student_in_course

View File

@ -369,21 +369,21 @@ describe "manage groups students" do
it "should give Assigning Students... visual feedback" do
#pending "causes whatever spec follows this to fail even in different files"
assign_students = fj("#category_#{@category.id} .assign_students_link:visible")
assign_students.should_not be_nil
assign_students.click
# Do some magic to make sure the next ajax request doesn't complete until we're ready for it to
lock = Mutex.new
lock.lock
GroupsController.before_filter { lock.lock; lock.unlock; true }
confirm_dialog = driver.switch_to.alert
confirm_dialog.accept
loading = fj("#category_#{@category.id} .group_blank .loading_members:visible")
loading.text.should == 'Assigning Students...'
lock.unlock
GroupsController.filter_chain.pop
# make sure we wait before moving on
wait_for_ajax_requests
end
assign_students = fj("#category_#{@category.id} .assign_students_link:visible")
assign_students.should_not be_nil
assign_students.click
# Do some magic to make sure the next ajax request doesn't complete until we're ready for it to
lock = Mutex.new
lock.lock
GroupsController.before_filter { lock.lock; lock.unlock; true }
confirm_dialog = driver.switch_to.alert
confirm_dialog.accept
loading = fj("#category_#{@category.id} .group_blank .loading_members:visible")
loading.text.should == 'Assigning Students...'
lock.unlock
GroupsController.filter_chain.pop
# make sure we wait before moving on
wait_for_ajax_requests
end
end
end