{"action":"created","comment":{"url":"https://api.github.com/repos/sakaiproject/sakai/pulls/comments/31461274","id":31461274,"diff_hunk":"@@ -854,268 +870,376 @@ public String processCancelDelete()\n     }\n     \n     /**\n-     * atuo create group(s) based on selected roster(s) or role(s)\n+     * SAK-29373 - Common validation algorithm for both roster and role based random groups\n+     * \n+     * @param option\n+     * @param isGroupSplit\n+     * @param numToSplitForGroups\n+     * @param numToSplitForUsers\n+     * @param groupTitleForGroups\n+     * @param groupTitleForUsers\n+     * @return 0 if not for random groups, -1 if for random groups and validation fails. Otherwise, returns the \n+     * integer value of the number of groups or number of users per group requested.\n+     */\n+    private int validateAutoGroupsFields( int option, boolean isGroupSplit, String numToSplitForGroups, String numToSplitForUsers,\n+                                          String groupTitleForGroups, String groupTitleForUsers)\n+    {\n+        int intToSplit = 0;\n+        if( OPTION_ASSIGN_RANDOM == option )\n+        {\n+            String numToSplit = isGroupSplit ? numToSplitForGroups : numToSplitForUsers;\n+            String groupTitle = isGroupSplit ? groupTitleForGroups : groupTitleForUsers;\n+            if( numToSplit == null )\n+            {\n+                if( isGroupSplit )\n+                {\n+                    messages.addMessage( new TargettedMessage( \"numToSplit.group.empty.alert\", \"numToSplit\" ) );\n+                }\n+                else\n+                {\n+                    messages.addMessage( new TargettedMessage( \"numToSplit.user.empty.alert\", \"numToSplit\" ) );\n+                }\n+                return -1;\n+            }\n+            else\n+            {\n+                try\n+                {\n+                    intToSplit = Integer.parseInt( numToSplit );\n+                    if( intToSplit <= 0 )\n+                    {\n+                        if( isGroupSplit )\n+                        {\n+                            messages.addMessage( new TargettedMessage( \"numToSplit.group.notanumber.alert\", \"numToSplit\" ) );\n+                        }\n+                        else\n+                        {\n+                            messages.addMessage( new TargettedMessage( \"numToSplit.user.notanumber.alert\", \"numToSplit\" ) );\n+                        }\n+                        return -1;\n+                    }\n+                }\n+                catch( NumberFormatException ex )\n+                {\n+                    if( isGroupSplit )\n+                    {\n+                        messages.addMessage( new TargettedMessage( \"numToSplit.group.notanumber.alert\", \"numToSplit\" ) );\n+                    }\n+                    else\n+                    {\n+                        messages.addMessage( new TargettedMessage( \"numToSplit.user.notanumber.alert\", \"numToSplit\" ) );\n+                    }\n+                    return -1;\n+                }\n+            }\n+\n+            if( groupTitle == null || \"\".equals( groupTitle ) )\n+            {\n+                messages.addMessage( new TargettedMessage( \"groupTitle.empty.alert\", \"groupTitle\" ) );\n+                return -1;\n+            }\n+        }\n+        \n+        return intToSplit;\n+    }\n+\n+    /**\n+     * auto create group(s) based on selected roster(s) or role(s)\n      *\n      */\n     public String processAutoCreateGroup() {\n-    \t// reset the warning messages\n-    \tresetTargettedMessageList();\n-    \t\t\t\n-    \t//check if fields are correct:\n-    \tint intToSplit=-1;\n-    \t\n-    \tif(OPTION_ASSIGN_RANDOM == optionAssign){\n-    \t\tString numToSplit = groupSplit ? numToSplitGroup : numToSplitUser;\n-        \tString groupTitle = groupSplit ? groupTitleGroup : groupTitleUser;\n-    \t\tif(numToSplit == null){\n-    \t\t\tif(groupSplit){\n-    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.group.empty.alert\",\"numToSplit\"));\t\n-    \t\t\t}else{\n-    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.user.empty.alert\",\"numToSplit\"));\n-    \t\t\t}    \t\t\t\n-    \t\t\treturn null;\n-    \t\t}else{\n-    \t\t\ttry {\n-    \t\t\t\tintToSplit = Integer.parseInt(numToSplit);\n-    \t\t\t\tif(intToSplit <= 0){\n-    \t\t\t\t\tif(groupSplit){\n-    \t    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.group.notanumber.alert\",\"numToSplit\"));\t\n-    \t    \t\t\t}else{\n-    \t    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.user.notanumber.alert\",\"numToSplit\"));\n-    \t    \t\t\t}\n-    \t\t\t\t\treturn null;\n-    \t\t\t\t}\n-\t\t\t\t} catch (Exception e) {\n-\t\t\t\t\tif(groupSplit){\n-\t    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.group.notanumber.alert\",\"numToSplit\"));\t\n-\t    \t\t\t}else{\n-\t    \t\t\t\tmessages.addMessage(new TargettedMessage(\"numToSplit.user.notanumber.alert\",\"numToSplit\"));\n-\t    \t\t\t}\n-\t\t\t\t\treturn null;\n-\t\t\t\t}\n-    \t\t}\n-    \t\t\n-    \t\tif(groupTitle == null || \"\".equals(groupTitle)){\n-    \t\t\tmessages.addMessage(new TargettedMessage(\"groupTitle.empty.alert\",\"groupTitle\"));\n-    \t\t\treturn null;\n-    \t\t}\n-    \t}\n-    \t    \t    \t\n-    \tList<String> rosterList = new Vector<String>();\n-    \tif (!selectedRosters.isEmpty())\n-    \t{\n-    \t\tfor (Iterator<String> iterRosters= selectedRosters.keySet().iterator(); iterRosters.hasNext(); ) {\n-    \t\t\tString roster = iterRosters.next();\n-    \t\t\tif (Boolean.TRUE.equals(selectedRosters.get(roster)))\n-    \t\t\t{\n-    \t\t\t\t// selected roster\n-    \t\t\t\trosterList.add(roster);\n-    \t\t\t}\n-    \t\t}\n-    \t}\n-    \t\n-    \tList<String> roleList = new Vector<String>();\n-    \tif (!selectedRoles.isEmpty())\n-    \t{\n-    \t\tfor (Iterator<String> iterRoles = selectedRoles.keySet().iterator(); iterRoles.hasNext(); ) {\n-    \t\t\tString role = iterRoles.next();\n-    \t\t\tif (Boolean.TRUE.equals(selectedRoles.get(role)))\n-    \t\t\t{\n-    \t\t\t\t// selected role\n-    \t\t\t\troleList.add(role);\n-    \t\t\t}\n-    \t\t}\n-    \t}\n-    \t\n-    \tif (rosterList.isEmpty() && roleList.isEmpty())\n-    \t{\n-    \t\t// nothing selected, generate alert\n-    \t\tmessages.addMessage(new TargettedMessage(\"group.autocreate.selectrosterorrole\",\"Please select at lease one roster or role.\"));\n-    \t}\n-    \telse\n-    \t{\n-    \t\t// go and create the new group\n-    \t\tif (!rosterList.isEmpty())\n-    \t\t{\n-\t    \t\tfor (String roster:rosterList)\n-\t    \t\t{\n-\t    \t\t\tGroup group = site.addGroup();\n-        \t\t\tgroup.getProperties().addProperty(group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString());\n-\t\t        \t\t\n-\t\t        \t// roster provider string\n-        \t\t\t//rsf doesn't like \".\"'s, so these have been escaped.  Now unescape them\n-        \t\t\troster = roster.replaceAll(\"-_p_-\", \".\");\n-\t\t        \tgroup.setProviderGroupId(roster);\n-\t\t        \t\t\n-\t\t        \tString title = truncateGroupTitle(roster);\n-\t\t        \tgroup.setTitle(title);\n-\t    \t\t}\n-    \t\t}\n-\t        \t\n-        \t// role based\n-        \tif (!roleList.isEmpty())\n-        \t{\n-        \t\tif(OPTION_ASSIGN_BY_ROLES == optionAssign){\n-\t        \t\tfor(String role:roleList)\n-\t        \t\t{\n-\t        \t\t\tGroup group = site.addGroup();\n-\t        \t\t\t// make the provider id as of SITEID_ROLEID\n-\t        \t\t\t//group.setProviderGroupId(site.getId() + \"_\" + role);\n-\t        \t\t\tgroup.getProperties().addProperty(group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString());\n-\t        \t\t\tgroup.getProperties().addProperty(SiteConstants.GROUP_PROP_ROLE_PROVIDERID, role);\n-\t\n-\t\t\t        \tString title = truncateGroupTitle(role);\n-\t\t\t        \tgroup.setTitle(title);\n-\t        \t\t\t\n-\t        \t\t\t// get the authz group\n-\t                \tString siteReference = siteService.siteReference(site.getId());\n-\t                \ttry\n-\t                \t{\n-\t                \t\tAuthzGroup siteGroup = authzGroupService.getAuthzGroup(siteReference);\n-\t                \t\tSet<String> usersHasRole = siteGroup.getUsersHasRole(role);\n-\t                \t\tif (usersHasRole != null)\n-\t                \t\t{\n-\t                \t\t\tfor (Iterator<String> uIterator = usersHasRole.iterator(); uIterator.hasNext();)\n-\t                \t\t\t{\n-\t                \t\t\t\tString userId = uIterator.next();\n-\t                \t\t\t\tMember member = site.getMember(userId);\n-\t            \t\t\t\t\tgroup.addMember(userId, role, member.isActive(), false);\n-\t                \t\t\t}\n-\t                \t\t}\n-\t                \t}\n-\t                \tcatch (GroupNotDefinedException e)\n-\t                \t{\n-\t                \t\tM_log.debug(this + \".processAutoCreateGroup: no authzgroup found for \" + siteReference);\n-\t                \t}\n-\t        \t\t}\n-        \t\t}else{\n-        \t\t\tcreateRandomGroups(roleList, intToSplit);\n-        \t\t}\n-        \t}\n-        \t\t\n-    \t\t// save the changes\n-    \t\ttry\n-    \t\t{\n-    \t\t\tsiteService.save(site);\n-    \t\t\t// reset the form params\n-    \t\t\tresetParams();\n-\t        } \n-\t        catch (IdUnusedException e) {\n-\t        \tM_log.warn(this + \".processAutoCreateGroup: cannot find site \" + site.getId(), e);\n-\t            return null;\n-\t        } \n-\t        catch (PermissionException e) {\n-\t        \tM_log.warn(this + \".processAutoCreateGroup: cannot find site \" + site.getId(), e);\n-\t            return null;\n-\t        }\n-        \t\n-    \t}\n+        // reset the warning messages\n+        resetTargettedMessageList();\n \n-        return \"done\";\n-    }\n-    \n-    private void createRandomGroups(List<String> roleList, int unit){\n-    \tString groupTitle = groupSplit ? groupTitleGroup : groupTitleUser;\n-    \t\n-    \tif(groupTitle != null && !\"\".equals(groupTitle)){\n-    \t\t//get list of all users:\n+        // Check if fields are valid\n+        int intToSplit = validateAutoGroupsFields( optionAssign, groupSplit, numToSplitGroup, numToSplitUser, groupTitleGroup, groupTitleUser );\n+        int rosterIntToSplit = validateAutoGroupsFields( rosterOptionAssign, rosterGroupSplit, rosterNumToSplitGroup, rosterNumToSplitUser, rosterGroupTitleGroup, rosterGroupTitleUser );\n+        if( intToSplit == -1 || rosterIntToSplit == -1 )\n+        {\n+            return null;\n+        }\n \n-    \t\tList<String> usersList = new ArrayList<String>();\n+        List<String> rosterList = new ArrayList<>();\n+        if (!selectedRosters.isEmpty())\n+        {\n+            for( String roster : selectedRosters.keySet() )\n+            {\n+                if (Boolean.TRUE.equals(selectedRosters.get(roster)))\n+                {\n+                    // RSF doesn't like periods, so these have been escaped; now unescape them\n+                    roster = roster.replaceAll(\"-_p_-\", \".\");\n+                    rosterList.add(roster);\n+                }\n+            }\n+        }\n \n-    \t\tfor(String role:roleList)\n-    \t\t{\n-    \t\t\t// get the authz group\n-    \t\t\tString siteReference = siteService.siteReference(site.getId());\n+        List<String> roleList = new ArrayList<>();\n+        if (!selectedRoles.isEmpty())\n+        {\n+            for( String role : selectedRoles.keySet() )\n+            {\n+                if (Boolean.TRUE.equals(selectedRoles.get(role)))\n+                {\n+                    // selected role\n+                    roleList.add(role);\n+                }\n+            }\n+        }\n \n-    \t\t\ttry\n-    \t\t\t{\n-    \t\t\t\tAuthzGroup siteGroup = authzGroupService.getAuthzGroup(siteReference);\n-    \t\t\t\tSet<String> usersHasRole = siteGroup.getUsersHasRole(role);\n-    \t\t\t\tif (usersHasRole != null)\n-    \t\t\t\t{\n-    \t\t\t\t\tfor (Iterator<String> uIterator = usersHasRole.iterator(); uIterator.hasNext();)\n-    \t\t\t\t\t{\n-    \t\t\t\t\t\tString userId = uIterator.next();\n-    \t\t\t\t\t\tif(!usersList.contains(userId)){\n-    \t\t\t\t\t\t\tusersList.add(userId);\n-    \t\t\t\t\t\t}\n-    \t\t\t\t\t}\n-    \t\t\t\t}\n-    \t\t\t}\n-    \t\t\tcatch (GroupNotDefinedException e)\n-    \t\t\t{\n-    \t\t\t\tM_log.debug(this + \".processAutoCreateGroup: no authzgroup found for \" + siteReference);\n-    \t\t\t}\n-    \t\t}\n+        if (rosterList.isEmpty() && roleList.isEmpty())\n+        {\n+            // nothing selected, generate alert\n+            messages.addMessage(new TargettedMessage(\"group.autocreate.selectrosterorrole\",\"Please select at lease one roster or role.\"));\n+        }\n+        else\n+        {\n+            // Roster based\n+            if (!rosterList.isEmpty())\n+            {\n+                // SAK-29373\n+                if( OPTION_ASSIGN_RANDOM == rosterOptionAssign )\n+                {\n+                    createRandomGroupsForRoster( rosterList, rosterIntToSplit );\n+                }\n+                else\n+                {\n+                    for (String roster:rosterList)\n+                    {\n+                        Group group = site.addGroup();\n+                        group.getProperties().addProperty(Group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString());\n+                        group.setProviderGroupId(roster);\n \n-    \t\t//split users into random groups:\n+                        String groupTitle = truncateGroupTitle(roster);\n+                        group.setTitle(groupTitle);\n+                    }\n+                }\n+            }\n \n-    \t\tint numOfGroups=-1;\n-    \t\tint numOfUsersPerGroup=-1;\n-    \t\tif(groupSplit){\n-    \t\t\tnumOfGroups = (unit > usersList.size()) ? usersList.size() : unit;\n-\t\t\t\tif(numOfGroups > 0){\n-\t    \t\t\tnumOfUsersPerGroup = usersList.size()/numOfGroups;\n-\t\t\t\t}\n-    \t\t}else{\n-    \t\t\tnumOfUsersPerGroup = (unit > usersList.size()) ? usersList.size() : unit;\n-\t\t\t\tif(numOfUsersPerGroup > 0){\n-\t    \t\t\tnumOfGroups = usersList.size()/numOfUsersPerGroup;\n-\t\t\t\t}\n-    \t\t}\n+            // role based\n+            if (!roleList.isEmpty())\n+            {\n+                if(OPTION_ASSIGN_BY_ROLES_OR_ROSTER == optionAssign){\n+                    for(String role:roleList)\n+                    {\n+                        Group group = site.addGroup();\n+                        // make the provider id as of SITEID_ROLEID\n+                        //group.setProviderGroupId(site.getId() + \"_\" + role);\n+                        group.getProperties().addProperty(Group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString());\n+                        group.getProperties().addProperty(SiteConstants.GROUP_PROP_ROLE_PROVIDERID, role);\n+\n+                        String groupTitle = truncateGroupTitle(role);\n+                        group.setTitle(groupTitle);\n+\n+                        // get the authz group\n+                        String siteReference = siteService.siteReference(site.getId());\n+                        try\n+                        {\n+                            AuthzGroup siteGroup = authzGroupService.getAuthzGroup(siteReference);\n+                            Set<String> usersHasRole = siteGroup.getUsersHasRole(role);\n+                            if (usersHasRole != null)\n+                            {\n+                                for( String userId : usersHasRole )\n+                                {\n+                                    Member member = site.getMember(userId);\n+                                    group.addMember(userId, role, member.isActive(), false);\n+                                }\n+                            }\n+                        }\n+                        catch (GroupNotDefinedException e)\n+                        {\n+                            M_log.debug(this + \".processAutoCreateGroup: no authzgroup found for \" + siteReference);\n+                        }\n+                    }\n+                }else{\n+                    createRandomGroupsForRole( roleList, intToSplit );\n+                }\n+            }\n+\n+            // save the changes\n+            try\n+            {\n+                siteService.save(site);\n+                // reset the form params\n+                resetParams();\n+            } \n+            catch (IdUnusedException e) {\n+                M_log.warn(this + \".processAutoCreateGroup: cannot find site \" + site.getId(), e);\n+                return null;\n+            } \n+            catch (PermissionException e) {\n+                M_log.warn(this + \".processAutoCreateGroup: cannot find site \" + site.getId(), e);\n+                return null;\n+            }\n+        }\n+\n+        return \"done\";\n+    }\n+    \n+    /**\n+     * SAK-29373 - Create random groups of users from the given roster.\n+     * This could be random number of users in specified number of groups,\n+     * or vice versa.\n+     * \n+     * @param rosters a list of provider IDs (should only ever be one in the list)\n+     * @param unit the number of groups or users per group requested\n+     */\n+    private void createRandomGroupsForRoster( List<String> providerIDs, int unit )\n+    {\n+        String groupTitle = rosterGroupSplit ? rosterGroupTitleGroup: rosterGroupTitleUser;\n+        if( !StringUtils.isEmpty( groupTitle ) )\n+        {\n+            List<String> userList = new ArrayList<>();\n+            for( String providerID : providerIDs )\n+            {\n+                try\n+                {\n+                    Set<Membership> sectionMembers = cms.getSectionMemberships( providerID );\n+                    for( Membership member : sectionMembers )\n+                    {\n+                        String userEID = member.getUserId();\n+                        try\n+                        {\n+                            String userID = userDirectoryService.getUserId( userEID );\n+                            if( !userList.contains( userID ) )\n+                            {\n+                                userList.add( userID );\n+                            }\n+                        }\n+                        catch( UserNotDefinedException ex )\n+                        {\n+                            M_log.debug( this + \".createRandomGroupsForRoster: user not defined = \" + userEID, ex );\n+                        }\n+                    }\n+                }\n+                catch( IdNotFoundException ex )\n+                {\n+                    M_log.debug( this + \".createRandomGroupsForRoster: can't find section for provider ID = \" + providerID, ex );\n+                }\n+            }\n+\n+            createRandomGroups( rosterGroupSplit, userList, groupTitle, unit );\n+        }\n+    }\n+\n+    /**\n+     * Common algorithm called to create random groups.\n+     * @param isGroupSplit\n+     * @param userIDs\n+     * @param groupTitle\n+     * @param unit \n+     */\n+    private void createRandomGroups( boolean isGroupSplit, List<String> userIDs, String groupTitle, int unit )\n+    {\n+        int numOfGroups = -1;\n+        int numOfUsersPerGroup = -1;\n+        int totalNumOfUsers = userIDs.size();\n+        \n+        // Determine number of users per group and number of groups to create\n+        if( isGroupSplit )\n+        {\n+            numOfGroups = unit > totalNumOfUsers ? totalNumOfUsers : unit;\n+            if( numOfGroups > 0 )\n+            {\n+                numOfUsersPerGroup = totalNumOfUsers / numOfGroups;\n+            }\n+        }\n+        else\n+        {\n+            numOfUsersPerGroup = unit > totalNumOfUsers ? totalNumOfUsers : unit;\n+            if( numOfUsersPerGroup > 0 )\n+            {\n+                numOfGroups = totalNumOfUsers / numOfUsersPerGroup;\n+            }\n+        }\n \n-    \t\tint groupCount = 0;\n-    \t\tList<Group> gList = new ArrayList<Group>();\n-    \t\twhile(groupCount < numOfGroups){\n-    \t\t\tGroup group = site.addGroup();\n-    \t\t\tgroup.getProperties().addProperty(group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString());\n+        int groupCount = 0;\n+        List<Group> groupList = new ArrayList<>();\n+        while( groupCount < numOfGroups )\n+        {\n+            // Create the group\n+            Group group = site.addGroup();\n+            group.getProperties().addProperty( Group.GROUP_PROP_WSETUP_CREATED, Boolean.TRUE.toString() );\n+            group.setTitle( groupTitle + \"-\" + (groupCount + 1) );","path":"site-manage/site-manage-group-section-role-helper/tool/src/java/org/sakaiproject/site/tool/helper/managegroupsectionrole/impl/SiteManageGroupSectionRoleHandler.java","position":564,"original_position":564,"commit_id":"08ef1c6f555ae3ed54b05d76bbf13009734d728d","original_commit_id":"08ef1c6f555ae3ed54b05d76bbf13009734d728d","user":{"login":"plukasew","id":10634340,"avatar_url":"https://avatars.githubusercontent.com/u/10634340?v=3","gravatar_id":"","url":"https://api.github.com/users/plukasew","html_url":"https://github.com/plukasew","followers_url":"https://api.github.com/users/plukasew/followers","following_url":"https://api.github.com/users/plukasew/following{/other_user}","gists_url":"https://api.github.com/users/plukasew/gists{/gist_id}","starred_url":"https://api.github.com/users/plukasew/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/plukasew/subscriptions","organizations_url":"https://api.github.com/users/plukasew/orgs","repos_url":"https://api.github.com/users/plukasew/repos","events_url":"https://api.github.com/users/plukasew/events{/privacy}","received_events_url":"https://api.github.com/users/plukasew/received_events","type":"User","site_admin":false},"body":"string concat in a loop","created_at":"2015-06-01T19:35:01Z","updated_at":"2015-06-01T19:35:01Z","html_url":"https://github.com/sakaiproject/sakai/pull/542#discussion_r31461274","pull_request_url":"https://api.github.com/repos/sakaiproject/sakai/pulls/542","_links":{"self":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/comments/31461274"},"html":{"href":"https://github.com/sakaiproject/sakai/pull/542#discussion_r31461274"},"pull_request":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/542"}}},"pull_request":{"url":"https://api.github.com/repos/sakaiproject/sakai/pulls/542","id":35743374,"html_url":"https://github.com/sakaiproject/sakai/pull/542","diff_url":"https://github.com/sakaiproject/sakai/pull/542.diff","patch_url":"https://github.com/sakaiproject/sakai/pull/542.patch","issue_url":"https://api.github.com/repos/sakaiproject/sakai/issues/542","number":542,"state":"open","locked":false,"title":"SAK-29373 add the ability to create auto groups from roster","user":{"login":"bjones86","id":10403943,"avatar_url":"https://avatars.githubusercontent.com/u/10403943?v=3","gravatar_id":"","url":"https://api.github.com/users/bjones86","html_url":"https://github.com/bjones86","followers_url":"https://api.github.com/users/bjones86/followers","following_url":"https://api.github.com/users/bjones86/following{/other_user}","gists_url":"https://api.github.com/users/bjones86/gists{/gist_id}","starred_url":"https://api.github.com/users/bjones86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bjones86/subscriptions","organizations_url":"https://api.github.com/users/bjones86/orgs","repos_url":"https://api.github.com/users/bjones86/repos","events_url":"https://api.github.com/users/bjones86/events{/privacy}","received_events_url":"https://api.github.com/users/bjones86/received_events","type":"User","site_admin":false},"body":"https://jira.sakaiproject.org/browse/SAK-29373\r\n\r\nSeveral faculty members have requested the ability to create Auto Groups from a selected roster, just like how they can create Auto Groups from a selected role.\r\n\r\nThe linked PR introduces the same set of choices you get for Auto Groups for a role for a selected roster as well:\r\n\r\n    \"Create a single group for the selected roster\" (default)\r\n    \"Create random groups from members in the selected roster\"\r\n\r\nIf you choose the later of the two options, you get two sub-options just like with role (random number of groups with specified number of users per group, or random number of users in a specified number of groups).\r\n\r\nI continued with the existing design in such a way that you can effectively create groups (random or not) for both sections and rosters at the same time.","created_at":"2015-05-19T15:34:53Z","updated_at":"2015-06-01T19:35:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d4c4ee1385944a89f5a0c1a6edc24050dc545005","assignee":{"login":"bjones86","id":10403943,"avatar_url":"https://avatars.githubusercontent.com/u/10403943?v=3","gravatar_id":"","url":"https://api.github.com/users/bjones86","html_url":"https://github.com/bjones86","followers_url":"https://api.github.com/users/bjones86/followers","following_url":"https://api.github.com/users/bjones86/following{/other_user}","gists_url":"https://api.github.com/users/bjones86/gists{/gist_id}","starred_url":"https://api.github.com/users/bjones86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bjones86/subscriptions","organizations_url":"https://api.github.com/users/bjones86/orgs","repos_url":"https://api.github.com/users/bjones86/repos","events_url":"https://api.github.com/users/bjones86/events{/privacy}","received_events_url":"https://api.github.com/users/bjones86/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/sakaiproject/sakai/pulls/542/commits","review_comments_url":"https://api.github.com/repos/sakaiproject/sakai/pulls/542/comments","review_comment_url":"https://api.github.com/repos/sakaiproject/sakai/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sakaiproject/sakai/issues/542/comments","statuses_url":"https://api.github.com/repos/sakaiproject/sakai/statuses/08ef1c6f555ae3ed54b05d76bbf13009734d728d","head":{"label":"bjones86:SAK-29373","ref":"SAK-29373","sha":"08ef1c6f555ae3ed54b05d76bbf13009734d728d","user":{"login":"bjones86","id":10403943,"avatar_url":"https://avatars.githubusercontent.com/u/10403943?v=3","gravatar_id":"","url":"https://api.github.com/users/bjones86","html_url":"https://github.com/bjones86","followers_url":"https://api.github.com/users/bjones86/followers","following_url":"https://api.github.com/users/bjones86/following{/other_user}","gists_url":"https://api.github.com/users/bjones86/gists{/gist_id}","starred_url":"https://api.github.com/users/bjones86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bjones86/subscriptions","organizations_url":"https://api.github.com/users/bjones86/orgs","repos_url":"https://api.github.com/users/bjones86/repos","events_url":"https://api.github.com/users/bjones86/events{/privacy}","received_events_url":"https://api.github.com/users/bjones86/received_events","type":"User","site_admin":false},"repo":{"id":28820362,"name":"sakai","full_name":"bjones86/sakai","owner":{"login":"bjones86","id":10403943,"avatar_url":"https://avatars.githubusercontent.com/u/10403943?v=3","gravatar_id":"","url":"https://api.github.com/users/bjones86","html_url":"https://github.com/bjones86","followers_url":"https://api.github.com/users/bjones86/followers","following_url":"https://api.github.com/users/bjones86/following{/other_user}","gists_url":"https://api.github.com/users/bjones86/gists{/gist_id}","starred_url":"https://api.github.com/users/bjones86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bjones86/subscriptions","organizations_url":"https://api.github.com/users/bjones86/orgs","repos_url":"https://api.github.com/users/bjones86/repos","events_url":"https://api.github.com/users/bjones86/events{/privacy}","received_events_url":"https://api.github.com/users/bjones86/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bjones86/sakai","description":"Sakai is a freely available, feature-rich technology solution for learning, teaching, research and collaboration. Sakai is an open source software suite developed by a diverse and global adopter community.","fork":true,"url":"https://api.github.com/repos/bjones86/sakai","forks_url":"https://api.github.com/repos/bjones86/sakai/forks","keys_url":"https://api.github.com/repos/bjones86/sakai/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bjones86/sakai/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bjones86/sakai/teams","hooks_url":"https://api.github.com/repos/bjones86/sakai/hooks","issue_events_url":"https://api.github.com/repos/bjones86/sakai/issues/events{/number}","events_url":"https://api.github.com/repos/bjones86/sakai/events","assignees_url":"https://api.github.com/repos/bjones86/sakai/assignees{/user}","branches_url":"https://api.github.com/repos/bjones86/sakai/branches{/branch}","tags_url":"https://api.github.com/repos/bjones86/sakai/tags","blobs_url":"https://api.github.com/repos/bjones86/sakai/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bjones86/sakai/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bjones86/sakai/git/refs{/sha}","trees_url":"https://api.github.com/repos/bjones86/sakai/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bjones86/sakai/statuses/{sha}","languages_url":"https://api.github.com/repos/bjones86/sakai/languages","stargazers_url":"https://api.github.com/repos/bjones86/sakai/stargazers","contributors_url":"https://api.github.com/repos/bjones86/sakai/contributors","subscribers_url":"https://api.github.com/repos/bjones86/sakai/subscribers","subscription_url":"https://api.github.com/repos/bjones86/sakai/subscription","commits_url":"https://api.github.com/repos/bjones86/sakai/commits{/sha}","git_commits_url":"https://api.github.com/repos/bjones86/sakai/git/commits{/sha}","comments_url":"https://api.github.com/repos/bjones86/sakai/comments{/number}","issue_comment_url":"https://api.github.com/repos/bjones86/sakai/issues/comments{/number}","contents_url":"https://api.github.com/repos/bjones86/sakai/contents/{+path}","compare_url":"https://api.github.com/repos/bjones86/sakai/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bjones86/sakai/merges","archive_url":"https://api.github.com/repos/bjones86/sakai/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bjones86/sakai/downloads","issues_url":"https://api.github.com/repos/bjones86/sakai/issues{/number}","pulls_url":"https://api.github.com/repos/bjones86/sakai/pulls{/number}","milestones_url":"https://api.github.com/repos/bjones86/sakai/milestones{/number}","notifications_url":"https://api.github.com/repos/bjones86/sakai/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bjones86/sakai/labels{/name}","releases_url":"https://api.github.com/repos/bjones86/sakai/releases{/id}","created_at":"2015-01-05T16:14:36Z","updated_at":"2015-05-14T14:54:02Z","pushed_at":"2015-06-01T13:46:53Z","git_url":"git://github.com/bjones86/sakai.git","ssh_url":"git@github.com:bjones86/sakai.git","clone_url":"https://github.com/bjones86/sakai.git","svn_url":"https://github.com/bjones86/sakai","homepage":"https://www.sakaiproject.org","size":200550,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"sakaiproject:master","ref":"master","sha":"88dea66a78522e8140ffaadce92b73e743251b1b","user":{"login":"sakaiproject","id":314726,"avatar_url":"https://avatars.githubusercontent.com/u/314726?v=3","gravatar_id":"","url":"https://api.github.com/users/sakaiproject","html_url":"https://github.com/sakaiproject","followers_url":"https://api.github.com/users/sakaiproject/followers","following_url":"https://api.github.com/users/sakaiproject/following{/other_user}","gists_url":"https://api.github.com/users/sakaiproject/gists{/gist_id}","starred_url":"https://api.github.com/users/sakaiproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sakaiproject/subscriptions","organizations_url":"https://api.github.com/users/sakaiproject/orgs","repos_url":"https://api.github.com/users/sakaiproject/repos","events_url":"https://api.github.com/users/sakaiproject/events{/privacy}","received_events_url":"https://api.github.com/users/sakaiproject/received_events","type":"Organization","site_admin":false},"repo":{"id":28589709,"name":"sakai","full_name":"sakaiproject/sakai","owner":{"login":"sakaiproject","id":314726,"avatar_url":"https://avatars.githubusercontent.com/u/314726?v=3","gravatar_id":"","url":"https://api.github.com/users/sakaiproject","html_url":"https://github.com/sakaiproject","followers_url":"https://api.github.com/users/sakaiproject/followers","following_url":"https://api.github.com/users/sakaiproject/following{/other_user}","gists_url":"https://api.github.com/users/sakaiproject/gists{/gist_id}","starred_url":"https://api.github.com/users/sakaiproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sakaiproject/subscriptions","organizations_url":"https://api.github.com/users/sakaiproject/orgs","repos_url":"https://api.github.com/users/sakaiproject/repos","events_url":"https://api.github.com/users/sakaiproject/events{/privacy}","received_events_url":"https://api.github.com/users/sakaiproject/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sakaiproject/sakai","description":"Sakai is a freely available, feature-rich technology solution for learning, teaching, research and collaboration. Sakai is an open source software suite developed by a diverse and global adopter community.","fork":false,"url":"https://api.github.com/repos/sakaiproject/sakai","forks_url":"https://api.github.com/repos/sakaiproject/sakai/forks","keys_url":"https://api.github.com/repos/sakaiproject/sakai/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sakaiproject/sakai/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sakaiproject/sakai/teams","hooks_url":"https://api.github.com/repos/sakaiproject/sakai/hooks","issue_events_url":"https://api.github.com/repos/sakaiproject/sakai/issues/events{/number}","events_url":"https://api.github.com/repos/sakaiproject/sakai/events","assignees_url":"https://api.github.com/repos/sakaiproject/sakai/assignees{/user}","branches_url":"https://api.github.com/repos/sakaiproject/sakai/branches{/branch}","tags_url":"https://api.github.com/repos/sakaiproject/sakai/tags","blobs_url":"https://api.github.com/repos/sakaiproject/sakai/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sakaiproject/sakai/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sakaiproject/sakai/git/refs{/sha}","trees_url":"https://api.github.com/repos/sakaiproject/sakai/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sakaiproject/sakai/statuses/{sha}","languages_url":"https://api.github.com/repos/sakaiproject/sakai/languages","stargazers_url":"https://api.github.com/repos/sakaiproject/sakai/stargazers","contributors_url":"https://api.github.com/repos/sakaiproject/sakai/contributors","subscribers_url":"https://api.github.com/repos/sakaiproject/sakai/subscribers","subscription_url":"https://api.github.com/repos/sakaiproject/sakai/subscription","commits_url":"https://api.github.com/repos/sakaiproject/sakai/commits{/sha}","git_commits_url":"https://api.github.com/repos/sakaiproject/sakai/git/commits{/sha}","comments_url":"https://api.github.com/repos/sakaiproject/sakai/comments{/number}","issue_comment_url":"https://api.github.com/repos/sakaiproject/sakai/issues/comments{/number}","contents_url":"https://api.github.com/repos/sakaiproject/sakai/contents/{+path}","compare_url":"https://api.github.com/repos/sakaiproject/sakai/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sakaiproject/sakai/merges","archive_url":"https://api.github.com/repos/sakaiproject/sakai/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sakaiproject/sakai/downloads","issues_url":"https://api.github.com/repos/sakaiproject/sakai/issues{/number}","pulls_url":"https://api.github.com/repos/sakaiproject/sakai/pulls{/number}","milestones_url":"https://api.github.com/repos/sakaiproject/sakai/milestones{/number}","notifications_url":"https://api.github.com/repos/sakaiproject/sakai/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sakaiproject/sakai/labels{/name}","releases_url":"https://api.github.com/repos/sakaiproject/sakai/releases{/id}","created_at":"2014-12-29T11:14:17Z","updated_at":"2015-06-01T11:23:51Z","pushed_at":"2015-06-01T14:58:19Z","git_url":"git://github.com/sakaiproject/sakai.git","ssh_url":"git@github.com:sakaiproject/sakai.git","clone_url":"https://github.com/sakaiproject/sakai.git","svn_url":"https://github.com/sakaiproject/sakai","homepage":"https://www.sakaiproject.org","size":1299267,"stargazers_count":54,"watchers_count":54,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":102,"mirror_url":null,"open_issues_count":22,"forks":102,"open_issues":22,"watchers":54,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/542"},"html":{"href":"https://github.com/sakaiproject/sakai/pull/542"},"issue":{"href":"https://api.github.com/repos/sakaiproject/sakai/issues/542"},"comments":{"href":"https://api.github.com/repos/sakaiproject/sakai/issues/542/comments"},"review_comments":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/542/comments"},"review_comment":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sakaiproject/sakai/pulls/542/commits"},"statuses":{"href":"https://api.github.com/repos/sakaiproject/sakai/statuses/08ef1c6f555ae3ed54b05d76bbf13009734d728d"}}}}