2011-02-01 09:57:29 +08:00
|
|
|
#
|
2013-11-28 06:10:07 +08:00
|
|
|
# Copyright (C) 2011 - 2013 Instructure, Inc.
|
2011-02-01 09:57:29 +08:00
|
|
|
#
|
|
|
|
# This file is part of Canvas.
|
|
|
|
#
|
|
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
|
|
# Software Foundation, version 3 of the License.
|
|
|
|
#
|
|
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
|
|
# details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
2012-08-14 03:50:18 +08:00
|
|
|
# @API Search
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
class SearchController < ApplicationController
|
2012-04-16 23:27:28 +08:00
|
|
|
include SearchHelper
|
2012-09-11 23:23:43 +08:00
|
|
|
include Api::V1::Conversation
|
2012-04-16 23:27:28 +08:00
|
|
|
|
2017-02-09 01:08:10 +08:00
|
|
|
before_action :require_user, :except => [:all_courses]
|
|
|
|
before_action :get_context, except: :recipients
|
2011-02-01 09:57:29 +08:00
|
|
|
|
|
|
|
def rubrics
|
|
|
|
contexts = @current_user.management_contexts rescue []
|
|
|
|
res = []
|
|
|
|
contexts.each do |context|
|
|
|
|
res += context.rubrics rescue []
|
|
|
|
end
|
|
|
|
res += Rubric.publicly_reusable.matching(params[:q])
|
|
|
|
res = res.select{|r| r.title.downcase.match(params[:q].downcase) }
|
2013-08-23 06:22:14 +08:00
|
|
|
render :json => res
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-04-16 23:27:28 +08:00
|
|
|
|
|
|
|
# @API Find recipients
|
|
|
|
# Find valid recipients (users, courses and groups) that the current user
|
2012-08-14 03:50:18 +08:00
|
|
|
# can send messages to. The /api/v1/search/recipients path is the preferred
|
|
|
|
# endpoint, /api/v1/conversations/find_recipients is deprecated.
|
2012-04-16 23:27:28 +08:00
|
|
|
#
|
2013-02-27 01:30:06 +08:00
|
|
|
# Pagination is supported.
|
2012-04-16 23:27:28 +08:00
|
|
|
#
|
2013-08-15 00:19:44 +08:00
|
|
|
# @argument search [String]
|
|
|
|
# Search terms used for matching users/courses/groups (e.g. "bob smith"). If
|
|
|
|
# multiple terms are given (separated via whitespace), only results matching
|
|
|
|
# all terms will be returned.
|
|
|
|
#
|
|
|
|
# @argument context [String]
|
|
|
|
# Limit the search to a particular course/group (e.g. "course_3" or "group_4").
|
|
|
|
#
|
|
|
|
# @argument exclude[] [String]
|
|
|
|
# Array of ids to exclude from the search. These may be user ids or
|
|
|
|
# course/group ids prefixed with "course_" or "group_" respectively,
|
2012-04-16 23:27:28 +08:00
|
|
|
# e.g. exclude[]=1&exclude[]=2&exclude[]=course_3
|
2013-08-15 00:19:44 +08:00
|
|
|
#
|
|
|
|
# @argument type [String, "user"|"context"]
|
|
|
|
# Limit the search just to users or contexts (groups/courses).
|
|
|
|
#
|
|
|
|
# @argument user_id [Integer]
|
|
|
|
# Search for a specific user id. This ignores the other above parameters,
|
|
|
|
# and will never return more than one result.
|
|
|
|
#
|
|
|
|
# @argument from_conversation_id [Integer]
|
|
|
|
# When searching by user_id, only users that could be normally messaged by
|
|
|
|
# this user will be returned. This parameter allows you to specify a
|
|
|
|
# conversation that will be referenced for a shared context -- if both the
|
|
|
|
# current user and the searched user are in the conversation, the user will
|
|
|
|
# be returned. This is used to start new side conversations.
|
|
|
|
#
|
|
|
|
# @argument permissions[] [String]
|
|
|
|
# Array of permission strings to be checked for each matched context (e.g.
|
|
|
|
# "send_messages"). This argument determines which permissions may be
|
|
|
|
# returned in the response; it won't prevent contexts from being returned if
|
|
|
|
# they don't grant the permission(s).
|
2012-04-16 23:27:28 +08:00
|
|
|
#
|
|
|
|
# @example_response
|
|
|
|
# [
|
|
|
|
# {"id": "group_1", "name": "the group", "type": "context", "user_count": 3},
|
|
|
|
# {"id": 2, "name": "greg", "common_courses": {}, "common_groups": {"1": ["Member"]}}
|
|
|
|
# ]
|
|
|
|
#
|
|
|
|
# @response_field id The unique identifier for the user/context. For
|
|
|
|
# groups/courses, the id is prefixed by "group_"/"course_" respectively.
|
|
|
|
# @response_field name The name of the user/context
|
|
|
|
# @response_field avatar_url Avatar image url for the user/context
|
|
|
|
# @response_field type ["context"|"course"|"section"|"group"|"user"|null]
|
|
|
|
# Type of recipients to return, defaults to null (all). "context"
|
|
|
|
# encompasses "course", "section" and "group"
|
|
|
|
# @response_field types[] Array of recipient types to return (see type
|
|
|
|
# above), e.g. types[]=user&types[]=course
|
|
|
|
# @response_field user_count Only set for contexts, indicates number of
|
|
|
|
# messageable users
|
|
|
|
# @response_field common_courses Only set for users. Hash of course ids and
|
|
|
|
# enrollment types for each course to show what they share with this user
|
|
|
|
# @response_field common_groups Only set for users. Hash of group ids and
|
|
|
|
# enrollment types for each group to show what they share with this user
|
2013-02-07 08:40:16 +08:00
|
|
|
# @response_field permissions[] Only set for contexts. Mapping of requested
|
|
|
|
# permissions that the context grants the current user, e.g.
|
2013-02-13 01:04:57 +08:00
|
|
|
# { send_messages: true }
|
2012-04-16 23:27:28 +08:00
|
|
|
def recipients
|
2013-07-16 06:52:25 +08:00
|
|
|
Shackles.activate(:slave) do
|
|
|
|
# admins may not be able to see the course listed at the top level (since
|
|
|
|
# they aren't enrolled in it), but if they search within it, we want
|
|
|
|
# things to work, so we set everything up here
|
2013-11-28 06:10:07 +08:00
|
|
|
|
|
|
|
if params[:user_id]
|
|
|
|
params[:user_id] = api_find(User, params[:user_id]).id
|
|
|
|
end
|
|
|
|
|
2013-12-07 06:11:44 +08:00
|
|
|
permissions = params[:permissions] || []
|
|
|
|
permissions << :send_messages if params[:messageable_only]
|
2013-07-16 06:52:25 +08:00
|
|
|
load_all_contexts :context => get_admin_search_context(params[:context]),
|
2013-12-07 06:11:44 +08:00
|
|
|
:permissions => permissions
|
2013-07-16 06:52:25 +08:00
|
|
|
|
|
|
|
types = (params[:types] || [] + [params[:type]]).compact
|
|
|
|
types |= [:course, :section, :group] if types.delete('context')
|
|
|
|
types = if types.present?
|
|
|
|
{:user => types.delete('user').present?, :context => types.present? && types.map(&:to_sym)}
|
|
|
|
else
|
|
|
|
{:user => true, :context => [:course, :section, :group]}
|
MessageableUser refactor with sharding
Separates, streamlines, and makes shard-aware all use cases of
User#messageable_users *other* than searching (call site in
SearchController#matching_participants).
Produces three new methods that take the bulk of that responsibility:
* User#load_messageable_users -- given a set of users, filter out the
ones that aren't messageable, and load any common contexts for those
that are.
* User#load_messageable_user -- as User#load_messageable_users, but for
just one user.
* User#messageable_users_in_context -- given a context (a course,
section, or group), return the list of messageable users in that
context.
refs CNVS-2519
remaining on CNVS-2519 is to tackle the search application of
User#messageable_user. mostly there, but reconciling pagination
with ranking by number of shared contexts is proving problematic, so I'm
going to separate that into another commit.
meanwhile, I've renamed User#messageable_users to
User#deprecated_search_messageable_users to discourage any accidental
new uses and left it otherwise untouched. searching for users on the
same shard should be unaffected. You can still locate messageable users
on other shards to insert into conversations by browsing the shared
contexts.
test-plan:
* create user A in shard X
* create user B in shard Y
* for situations where A could message B if on the same shard:
- setup the situation where the common tie is on shard X (e.g. course
on shard X and both A and B in it). run exercises below
- setup the situation where the common tie is on shard Y. run
exercises.
- if appropriate, setup the situation where the common tie is on
shard Z. run exercises.
* for each setup described above, login as A:
- A should see the "message this user" button on B's profile
- if the common tie is a course, section, or group, A should see B
under that context when the context is selected in the recipient
browser
- if a conversation exists involving both A and B, when A loads the
conversation they should see B tagged with the common contexts
* regression test searching for messageable users from the same shard
Change-Id: Ibba5551f8afc2435fd14a2e827a400bf95eae76a
Reviewed-on: https://gerrit.instructure.com/17569
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Clare Hetherington <clare@instructure.com>
Reviewed-by: Jon Jensen <jon@instructure.com>
2013-02-05 06:18:20 +08:00
|
|
|
end
|
2012-04-16 23:27:28 +08:00
|
|
|
|
2013-07-16 06:52:25 +08:00
|
|
|
@blank_fallback = !api_request?
|
|
|
|
|
|
|
|
params[:per_page] = nil if params[:per_page].to_i <= 0
|
|
|
|
exclude = params[:exclude] || []
|
|
|
|
|
|
|
|
recipients = []
|
|
|
|
if params[:user_id]
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
known = @current_user.address_book.known_user(
|
|
|
|
params[:user_id],
|
|
|
|
include_context: @admin_context,
|
|
|
|
conversation_id: params[:from_conversation_id])
|
|
|
|
recipients << known if known
|
2013-07-16 06:52:25 +08:00
|
|
|
elsif params[:context] || params[:search]
|
|
|
|
collections = []
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
exclude_users, exclude_contexts = AddressBook.partition_recipients(exclude)
|
2013-07-16 06:52:25 +08:00
|
|
|
|
|
|
|
if types[:context]
|
|
|
|
collections << ['contexts', search_messageable_contexts(
|
|
|
|
:search => params[:search],
|
|
|
|
:context => params[:context],
|
|
|
|
:synthetic_contexts => params[:synthetic_contexts],
|
|
|
|
:include_inactive => params[:include_inactive],
|
2013-12-07 06:11:44 +08:00
|
|
|
:messageable_only => params[:messageable_only],
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
:exclude_ids => exclude_contexts,
|
2013-07-16 06:52:25 +08:00
|
|
|
:search_all_contexts => params[:search_all_contexts],
|
|
|
|
:types => types[:context]
|
|
|
|
)]
|
|
|
|
end
|
|
|
|
|
|
|
|
if types[:user] && !@skip_users
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
collections << ['participants', @current_user.address_book.search_users(
|
|
|
|
search: params[:search],
|
|
|
|
exclude_ids: exclude_users,
|
|
|
|
context: params[:context],
|
|
|
|
is_admin: @admin_context.present?,
|
|
|
|
weak_checks: params[:skip_visibility_checks]
|
2013-07-16 06:52:25 +08:00
|
|
|
)]
|
|
|
|
end
|
|
|
|
|
|
|
|
recipients = BookmarkedCollection.concat(*collections)
|
|
|
|
recipients = Api.paginate(recipients, self, api_v1_search_recipients_url)
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
2013-02-27 01:30:06 +08:00
|
|
|
|
2013-07-16 06:52:25 +08:00
|
|
|
render :json => conversation_recipients_json(recipients, @current_user, session)
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-09-26 03:01:06 +08:00
|
|
|
# @API List all courses
|
|
|
|
# List all courses visible in the public index
|
|
|
|
#
|
|
|
|
# @argument search [String]
|
|
|
|
# Search terms used for matching users/courses/groups (e.g. "bob smith"). If
|
|
|
|
# multiple terms are given (separated via whitespace), only results matching
|
|
|
|
# all terms will be returned.
|
|
|
|
#
|
|
|
|
# @argument public_only [Optional, Boolean]
|
|
|
|
# Only return courses with public content. Defaults to false.
|
|
|
|
#
|
|
|
|
# @argument open_enrollment_only [Optional, Boolean]
|
|
|
|
# Only return courses that allow self enrollment. Defaults to false.
|
|
|
|
#
|
|
|
|
def all_courses
|
2015-07-02 03:05:25 +08:00
|
|
|
@courses = Course.where(root_account_id: @domain_root_account)
|
2013-09-26 03:01:06 +08:00
|
|
|
.where(indexed: true)
|
|
|
|
.where(workflow_state: 'available')
|
|
|
|
.order('created_at')
|
|
|
|
@search = params[:search]
|
|
|
|
if @search.present?
|
2016-08-02 23:54:11 +08:00
|
|
|
@courses = @courses.where(@courses.wildcard('name', @search.to_s))
|
2013-09-26 03:01:06 +08:00
|
|
|
end
|
|
|
|
@public_only = params[:public_only]
|
|
|
|
if @public_only
|
|
|
|
@courses = @courses.where(is_public: true)
|
|
|
|
end
|
|
|
|
@open_enrollment_only = params[:open_enrollment_only]
|
|
|
|
if @open_enrollment_only
|
|
|
|
@courses = @courses.where(open_enrollment: true)
|
|
|
|
end
|
|
|
|
pagination_args = {}
|
|
|
|
pagination_args[:per_page] = 12 unless request.format == :json
|
2016-10-12 03:41:22 +08:00
|
|
|
base_url = api_request? ? api_v1_search_all_courses_url : '/search/all_courses/'
|
|
|
|
ret = Api.paginate(@courses, self, base_url, pagination_args, {enhanced_return: true})
|
2013-09-26 03:01:06 +08:00
|
|
|
@courses = ret[:collection]
|
|
|
|
|
|
|
|
if request.format == :json
|
|
|
|
return render :json => @courses.as_json
|
|
|
|
end
|
|
|
|
|
|
|
|
@prevPage = ret[:hash][:prev]
|
|
|
|
@nextPage = ret[:hash][:next]
|
|
|
|
@contentHTML = render_to_string(partial: "all_courses_inner")
|
|
|
|
|
|
|
|
if request.xhr?
|
2016-06-18 06:01:26 +08:00
|
|
|
set_no_cache_headers
|
2013-09-26 03:01:06 +08:00
|
|
|
return render :text => @contentHTML
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-16 23:27:28 +08:00
|
|
|
private
|
|
|
|
|
2013-02-27 01:30:06 +08:00
|
|
|
# stupid bookmarker that instantiates the whole collection and then
|
|
|
|
# "bookmarks" subsets of the collection by index. will want to improve this
|
|
|
|
# eventually, but for now it's no worse than the old way, and lets us compose
|
|
|
|
# the messageable contexts and messageable users for pagination.
|
|
|
|
class ContextBookmarker
|
|
|
|
def initialize(collection)
|
|
|
|
@collection = collection
|
|
|
|
end
|
|
|
|
|
|
|
|
def bookmark_for(item)
|
|
|
|
@collection.index(item)
|
|
|
|
end
|
|
|
|
|
|
|
|
def validate(bookmark)
|
2017-01-26 05:43:18 +08:00
|
|
|
bookmark.is_a?(Integer)
|
2013-02-27 01:30:06 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.wrap(collection)
|
|
|
|
BookmarkedCollection.build(self.new(collection)) do |pager|
|
|
|
|
page_start = pager.current_bookmark ? pager.current_bookmark + 1 : 0
|
|
|
|
page_end = page_start + pager.per_page
|
|
|
|
pager.replace collection[page_start, page_end]
|
|
|
|
pager.has_more! if collection.size > page_end
|
|
|
|
pager
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def search_messageable_contexts(options={})
|
|
|
|
ContextBookmarker.wrap(matching_contexts(options))
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def matching_contexts(options)
|
|
|
|
context_name = options[:context]
|
|
|
|
avatar_url = avatar_url_for_group(blank_fallback)
|
|
|
|
terms = options[:search].to_s.downcase.strip.split(/\s+/)
|
|
|
|
exclude = options[:exclude_ids] || []
|
|
|
|
|
|
|
|
result = []
|
|
|
|
if context_name.nil?
|
|
|
|
result = if terms.blank?
|
|
|
|
courses = @contexts[:courses].values
|
2015-07-10 21:55:36 +08:00
|
|
|
group_ids = @current_user.current_groups.shard(@current_user).pluck(:id)
|
2012-04-16 23:27:28 +08:00
|
|
|
groups = @contexts[:groups].slice(*group_ids).values
|
|
|
|
courses + groups
|
|
|
|
else
|
|
|
|
@contexts.values_at(*options[:types].map{|t|t.to_s.pluralize.to_sym}).compact.map(&:values).flatten
|
|
|
|
end
|
|
|
|
elsif options[:synthetic_contexts]
|
|
|
|
if context_name =~ /\Acourse_(\d+)(_(groups|sections))?\z/ && (course = @contexts[:courses][$1.to_i]) && messageable_context_states[course[:state]]
|
2013-02-07 08:40:16 +08:00
|
|
|
sections = @contexts[:sections].values.select{ |section| section[:parent] == {:course => course[:id]} }
|
|
|
|
groups = @contexts[:groups].values.select{ |group| group[:parent] == {:course => course[:id]} }
|
2012-04-16 23:27:28 +08:00
|
|
|
case context_name
|
|
|
|
when /\Acourse_\d+\z/
|
|
|
|
if terms.present? || options[:search_all_contexts] # search all groups and sections (and users)
|
|
|
|
result = sections + groups
|
|
|
|
else # otherwise we show synthetic contexts
|
|
|
|
result = synthetic_contexts_for(course, context_name)
|
2013-12-07 06:11:44 +08:00
|
|
|
found_custom_sections = sections.any? { |s| s[:id] != course[:default_section_id] }
|
|
|
|
result << {:id => "#{context_name}_sections", :name => t(:course_sections, "Course Sections"), :item_count => sections.size, :type => :context} if found_custom_sections
|
2012-04-16 23:27:28 +08:00
|
|
|
result << {:id => "#{context_name}_groups", :name => t(:student_groups, "Student Groups"), :item_count => groups.size, :type => :context} if groups.size > 0
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
when /\Acourse_\d+_groups\z/
|
|
|
|
@skip_users = true # whether searching or just enumerating, we just want groups
|
|
|
|
result = groups
|
|
|
|
when /\Acourse_\d+_sections\z/
|
|
|
|
@skip_users = true # ditto
|
|
|
|
result = sections
|
|
|
|
end
|
|
|
|
elsif context_name =~ /\Asection_(\d+)\z/ && (section = @contexts[:sections][$1.to_i]) && messageable_context_states[section[:state]]
|
|
|
|
if terms.present? # we'll just search the users
|
|
|
|
result = []
|
|
|
|
else
|
2013-02-07 08:40:16 +08:00
|
|
|
return synthetic_contexts_for(course_for_section(section), context_name)
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-27 01:30:06 +08:00
|
|
|
result = if options[:search].present?
|
2012-04-16 23:27:28 +08:00
|
|
|
result.sort_by{ |context|
|
|
|
|
[
|
|
|
|
context_state_ranks[context[:state]],
|
|
|
|
context_type_ranks[context[:type]],
|
2013-09-27 05:15:55 +08:00
|
|
|
Canvas::ICU.collation_key(context[:name]),
|
2013-02-27 01:30:06 +08:00
|
|
|
context[:id]
|
2012-04-16 23:27:28 +08:00
|
|
|
]
|
|
|
|
}
|
|
|
|
else
|
2013-02-27 01:30:06 +08:00
|
|
|
result.sort_by{ |context|
|
|
|
|
[
|
2013-09-27 05:15:55 +08:00
|
|
|
Canvas::ICU.collation_key(context[:name]),
|
2013-02-27 01:30:06 +08:00
|
|
|
context[:id]
|
|
|
|
]
|
|
|
|
}
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
2013-02-27 01:30:06 +08:00
|
|
|
|
2012-04-16 23:27:28 +08:00
|
|
|
result = result.reject{ |context| context[:state] == :inactive } unless options[:include_inactive]
|
|
|
|
result = result.map{ |context|
|
2013-02-27 01:30:06 +08:00
|
|
|
asset_string = "#{context[:type]}_#{context[:id]}"
|
2012-04-16 23:27:28 +08:00
|
|
|
ret = {
|
2013-02-27 01:30:06 +08:00
|
|
|
:id => asset_string,
|
2012-04-16 23:27:28 +08:00
|
|
|
:name => context[:name],
|
|
|
|
:avatar_url => avatar_url,
|
|
|
|
:type => :context,
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
:user_count => @current_user.address_book.count_in_context(asset_string),
|
2013-02-07 08:40:16 +08:00
|
|
|
:permissions => context[:permissions] || {}
|
2012-04-16 23:27:28 +08:00
|
|
|
}
|
2013-02-07 08:40:16 +08:00
|
|
|
if context[:type] == :section
|
|
|
|
# TODO: have load_all_contexts actually return section-level
|
|
|
|
# permissions. but before we do that, sections will need to grant many
|
|
|
|
# more permission (possibly inherited from the course, like
|
|
|
|
# :send_messages_all)
|
|
|
|
ret[:permissions] = course_for_section(context)[:permissions]
|
2013-12-07 06:11:44 +08:00
|
|
|
elsif context[:type] == :group && context[:parent]
|
2014-01-24 08:26:55 +08:00
|
|
|
course = course_for_group(context)
|
|
|
|
# People have groups in unpublished courses that they use for messaging.
|
|
|
|
# We should really train them to use subaccount-level groups.
|
|
|
|
ret[:permissions] = course ? course[:permissions] : {send_messages: true}
|
2013-02-07 08:40:16 +08:00
|
|
|
end
|
2012-04-16 23:27:28 +08:00
|
|
|
ret[:context_name] = context[:context_name] if context[:context_name] && context_name.nil?
|
|
|
|
ret
|
|
|
|
}
|
2013-12-07 06:11:44 +08:00
|
|
|
result = result.select{ |context| context[:permissions].include? :send_messages } if options[:messageable_only]
|
2012-04-16 23:27:28 +08:00
|
|
|
|
|
|
|
result.reject!{ |context| terms.any?{ |part| !context[:name].downcase.include?(part) } } if terms.present?
|
|
|
|
result.reject!{ |context| exclude.include?(context[:id]) }
|
2013-02-27 01:30:06 +08:00
|
|
|
result
|
2012-04-16 23:27:28 +08:00
|
|
|
end
|
|
|
|
|
2013-02-07 08:40:16 +08:00
|
|
|
def course_for_section(section)
|
|
|
|
@contexts[:courses][section[:parent][:course]]
|
|
|
|
end
|
|
|
|
|
2013-12-07 06:11:44 +08:00
|
|
|
def course_for_group(group)
|
|
|
|
course_for_section(group)
|
|
|
|
end
|
|
|
|
|
2012-04-16 23:27:28 +08:00
|
|
|
def synthetic_contexts_for(course, context)
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
# context is a string identifying a subset of the course
|
2012-04-16 23:27:28 +08:00
|
|
|
@skip_users = true
|
|
|
|
# TODO: move the aggregation entirely into the DB. we only select a little
|
|
|
|
# bit of data per user, but this still isn't ideal
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
users = @current_user.address_book.known_in_context(context)
|
2012-04-16 23:27:28 +08:00
|
|
|
enrollment_counts = {:all => users.size}
|
|
|
|
users.each do |user|
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
common_courses = @current_user.address_book.common_courses(user)
|
2016-10-04 05:44:46 +08:00
|
|
|
next unless common_courses.key?(course[:id])
|
AddressBook facade
fixes CNVS-29824
refs CNVS-29862, CNVS-29867
test-plan:
check for regressions around:
* accessing profile#show endpoint, with profiles enabled, is
authorized if and only if current user knows profile owner.
* eportfolios#show, with profiles enabled, includes link to owner's
profile if and only if current user knows owner.
* conversations:
- filtering of individual recipients when creating a conversation
- expansion of context recipients when creating a conversation
- restriction of individual recipients to those known via course
when creating a conversation in a course
- including as an admin over that course
- filtering of individual recipients when adding a message to an
existing conversation allows existing recipients
* searching/browsing address book (conversation creation, link
observer to students, due date overrides, etc.):
- when loading info about single user (user_id parameter), including
combinations of conversation_id and context parameters, returns
user data if and only if the target is known to current user under
those parameters
- users matched:
- excludes already listed users
- restricts to users known via specified context, if any
- including as an admin over that context
- finds users with weak associations (e.g. invited student that
hasn't logged in for first time yet) when linking observers
- doesn't find users with weak associations otherwise
- contexts matched:
- limited to those known to current user
- have count of known users
- have counts of known users in synthetic contexts
* data returned for known users in various API calls include common
course and groups between current user and returned user
Change-Id: I66bca0921b298be8d529a713fa982a6dfdcbcc8e
Reviewed-on: https://gerrit.instructure.com/84045
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Jacob Fugal <jacob@instructure.com>
2016-06-17 00:37:56 +08:00
|
|
|
roles = common_courses[course[:id]].uniq
|
|
|
|
roles.each do |role|
|
2012-04-16 23:27:28 +08:00
|
|
|
enrollment_counts[role] ||= 0
|
|
|
|
enrollment_counts[role] += 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
avatar_url = avatar_url_for_group(blank_fallback)
|
|
|
|
result = []
|
2013-02-07 08:40:16 +08:00
|
|
|
synthetic_context = {:avatar_url => avatar_url, :type => :context, :permissions => course[:permissions]}
|
|
|
|
result << synthetic_context.merge({:id => "#{context}_teachers", :name => t(:enrollments_teachers, "Teachers"), :user_count => enrollment_counts['TeacherEnrollment']}) if enrollment_counts['TeacherEnrollment'].to_i > 0
|
|
|
|
result << synthetic_context.merge({:id => "#{context}_tas", :name => t(:enrollments_tas, "Teaching Assistants"), :user_count => enrollment_counts['TaEnrollment']}) if enrollment_counts['TaEnrollment'].to_i > 0
|
|
|
|
result << synthetic_context.merge({:id => "#{context}_students", :name => t(:enrollments_students, "Students"), :user_count => enrollment_counts['StudentEnrollment']}) if enrollment_counts['StudentEnrollment'].to_i > 0
|
|
|
|
result << synthetic_context.merge({:id => "#{context}_observers", :name => t(:enrollments_observers, "Observers"), :user_count => enrollment_counts['ObserverEnrollment']}) if enrollment_counts['ObserverEnrollment'].to_i > 0
|
2012-04-16 23:27:28 +08:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2012-08-04 15:39:04 +08:00
|
|
|
def get_admin_search_context(asset_string)
|
|
|
|
return unless asset_string
|
|
|
|
return unless asset_string =~ (/\A((\w+)_(\d+))/)
|
|
|
|
asset_string = $1
|
|
|
|
asset_type = $2.to_sym
|
2014-09-25 08:15:21 +08:00
|
|
|
asset_id = $3
|
|
|
|
context = nil
|
|
|
|
case asset_type
|
|
|
|
when :course, :group
|
|
|
|
return unless context = Context.find_by_asset_string(asset_string)
|
|
|
|
when :section
|
|
|
|
return unless context = CourseSection.find(asset_id)
|
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
return unless context.grants_right?(@current_user, :read_as_admin)
|
2012-08-04 15:39:04 +08:00
|
|
|
@admin_context = context
|
|
|
|
end
|
|
|
|
|
2012-04-16 23:27:28 +08:00
|
|
|
def context_state_ranks
|
|
|
|
{:active => 0, :recently_active => 1, :inactive => 2}
|
|
|
|
end
|
|
|
|
|
|
|
|
def context_type_ranks
|
|
|
|
{:course => 0, :section => 1, :group => 2}
|
|
|
|
end
|
|
|
|
|
|
|
|
def messageable_context_states
|
|
|
|
{:active => true, :recently_active => true, :inactive => false}
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|