# Methods that don't require login should
# skip_around_filter :require_thread_api_token
around_filter :require_thread_api_token, except: ERROR_ACTIONS
+ before_filter :accept_uuid_as_id_param, except: ERROR_ACTIONS
before_filter :check_user_agreements, except: ERROR_ACTIONS
+ before_filter :check_user_profile, except: [:update_profile] + ERROR_ACTIONS
before_filter :check_user_notifications, except: ERROR_ACTIONS
+ before_filter :load_filters_and_paging_params, except: ERROR_ACTIONS
before_filter :find_object_by_uuid, except: [:index, :choose] + ERROR_ACTIONS
theme :select_theme
render_error status: 422
end
- def render_error(opts)
- opts = {status: 500}.merge opts
+ def render_error(opts={})
+ opts[:status] ||= 500
respond_to do |f|
# json must come before html here, so it gets used as the
# default format when js is requested by the client. This lets
# ajax:error callback parse the response correctly, even though
# the browser can't.
f.json { render opts.merge(json: {success: false, errors: @errors}) }
- f.html { render opts.merge(controller: 'application', action: 'error') }
+ f.html { render({action: 'error'}.merge(opts)) }
end
end
def render_exception(e)
logger.error e.inspect
logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace
- if @object.andand.errors.andand.full_messages.andand.any?
+ err_opts = {status: 422}
+ if e.is_a?(ArvadosApiClient::ApiError)
+ err_opts.merge!(action: 'api_error', locals: {api_error: e})
+ @errors = e.api_response[:errors]
+ elsif @object.andand.errors.andand.full_messages.andand.any?
@errors = @object.errors.full_messages
else
@errors = [e.to_s]
end
- if e.is_a? ArvadosApiClient::NotLoggedInException
- self.render_error status: 422
- else
- set_thread_api_token do
- self.render_error status: 422
- end
+ # If the user has an active session, and the API server is available,
+ # make user information available on the error page.
+ begin
+ load_api_token(session[:arvados_api_token])
+ rescue ArvadosApiClient::ApiError
+ load_api_token(nil)
end
+ # Preload projects trees for the template. If that fails, set empty
+ # trees so error page rendering can proceed. (It's easier to rescue the
+ # exception here than in a template.)
+ begin
+ build_project_trees
+ rescue ArvadosApiClient::ApiError
+ @my_project_tree ||= []
+ @shared_project_tree ||= []
+ end
+ render_error(err_opts)
end
def render_not_found(e=ActionController::RoutingError.new("Path not found"))
logger.error e.inspect
@errors = ["Path not found"]
set_thread_api_token do
- self.render_error status: 404
+ self.render_error(action: '404', status: 404)
end
end
- def find_objects_for_index
+ def load_filters_and_paging_params
+ @order = params[:order] || 'created_at desc'
+ @order = [@order] unless @order.is_a? Array
+
@limit ||= 200
if params[:limit]
@limit = params[:limit].to_i
filters = params[:filters]
if filters.is_a? String
filters = Oj.load filters
+ elsif filters.is_a? Array
+ filters = filters.collect do |filter|
+ if filter.is_a? String
+ # Accept filters[]=["foo","=","bar"]
+ Oj.load filter
+ else
+ # Accept filters=[["foo","=","bar"]]
+ filter
+ end
+ end
end
@filters += filters
end
+ end
+ def find_objects_for_index
@objects ||= model_class
@objects = @objects.filter(@filters).limit(@limit).offset(@offset)
end
respond_to do |f|
f.json { render json: @objects }
f.html {
- if params['tab_pane']
- comparable = self.respond_to? :compare
- render(partial: 'show_' + params['tab_pane'].downcase,
- locals: { comparable: comparable, objects: @objects })
+ if params[:tab_pane]
+ render_pane params[:tab_pane]
else
render
end
end
end
+ helper_method :render_pane
+ def render_pane tab_pane, opts={}
+ render_opts = {
+ partial: 'show_' + tab_pane.downcase,
+ locals: {
+ comparable: self.respond_to?(:compare),
+ objects: @objects,
+ tab_pane: tab_pane
+ }.merge(opts[:locals] || {})
+ }
+ if opts[:to_string]
+ render_to_string render_opts
+ else
+ render render_opts
+ end
+ end
+
def index
find_objects_for_index if !@objects
render_index
end
helper_method :next_page_offset
- def next_page_offset
- if @objects.respond_to?(:result_offset) and
- @objects.respond_to?(:result_limit) and
- @objects.respond_to?(:items_available)
- next_offset = @objects.result_offset + @objects.result_limit
- if next_offset < @objects.items_available
+ def next_page_offset objects=nil
+ if !objects
+ objects = @objects
+ end
+ if objects.respond_to?(:result_offset) and
+ objects.respond_to?(:result_limit) and
+ objects.respond_to?(:items_available)
+ next_offset = objects.result_offset + objects.result_limit
+ if next_offset < objects.items_available
next_offset
else
nil
end
end
+ helper_method :next_page_href
+ def next_page_href with_params={}
+ if next_page_offset
+ url_for with_params.merge(offset: next_page_offset)
+ end
+ end
+
def show
if !@object
return render_not_found("object not found")
f.json { render json: @object.attributes.merge(href: url_for(@object)) }
f.html {
if params['tab_pane']
- comparable = self.respond_to? :compare
- render(partial: 'show_' + params['tab_pane'].downcase,
- locals: { comparable: comparable, objects: @objects })
+ render_pane params['tab_pane']
elsif request.method.in? ['GET', 'HEAD']
render
else
def choose
params[:limit] ||= 40
- if !@objects
- if params[:project_uuid] and !params[:project_uuid].empty?
- # We want the chooser to show objects of the controllers's model_class
- # type within a specific project specified by project_uuid, so fetch the
- # project and request the contents of the project filtered on the
- # controllers's model_class kind.
- @objects = Group.find(params[:project_uuid]).contents({:filters => [['uuid', 'is_a', "arvados\##{ArvadosApiClient.class_kind(model_class)}"]]})
- end
- find_objects_for_index if !@objects
- end
+ find_objects_for_index if !@objects
respond_to do |f|
if params[:partial]
f.json {
render json: {
content: render_to_string(partial: "choose_rows.html",
- formats: [:html],
- locals: {
- multiple: params[:multiple]
- }),
- next_page_href: @next_page_href
+ formats: [:html]),
+ next_page_href: next_page_href(partial: params[:partial])
}
}
end
if @object.name and @object.name != ''
@object.name = "Copy of #{@object.name}"
else
- @object.name = "Copy of unnamed #{@object.class_for_display.downcase}"
+ @object.name = ""
end
end
@object.save!
end
def current_user
- return Thread.current[:user] if Thread.current[:user]
-
- if Thread.current[:arvados_api_token]
- if session[:user]
- if session[:user][:is_active] != true
- Thread.current[:user] = User.current
- else
- Thread.current[:user] = User.new(session[:user])
- end
- else
- Thread.current[:user] = User.current
- end
- else
- logger.error "No API token in Thread"
- return nil
- end
+ Thread.current[:user]
end
def model_class
[:arvados_api_token, :user].each do |key|
start_values[key] = Thread.current[key]
end
- Thread.current[:arvados_api_token] = api_token
- Thread.current[:user] = nil
+ load_api_token(api_token)
begin
yield
ensure
end
end
- def find_object_by_uuid
+
+ def accept_uuid_as_id_param
if params[:id] and params[:id].match /\D/
params[:uuid] = params.delete :id
end
+ end
+
+ def find_object_by_uuid
begin
if not model_class
@object = nil
else
@object = model_class.find(params[:uuid])
end
- rescue ArvadosApiClient::NotFoundException => error
+ rescue ArvadosApiClient::NotFoundException, RuntimeError => error
+ if error.is_a?(RuntimeError) and (error.message !~ /^argument to find\(/)
+ raise
+ end
render_not_found(error)
return false
end
end
def thread_clear
- Thread.current[:arvados_api_token] = nil
- Thread.current[:user] = nil
+ load_api_token(nil)
Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/)
yield
Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/)
end
+ # Set up the thread with the given API token and associated user object.
+ def load_api_token(new_token)
+ Thread.current[:arvados_api_token] = new_token
+ if new_token.nil?
+ Thread.current[:user] = nil
+ else
+ Thread.current[:user] = User.current
+ end
+ end
+
+ # If there's a valid api_token parameter, set up the session with that
+ # user's information. Return true if the method redirects the request
+ # (usually a post-login redirect); false otherwise.
+ def setup_user_session
+ return false unless params[:api_token]
+ Thread.current[:arvados_api_token] = params[:api_token]
+ begin
+ user = User.current
+ rescue ArvadosApiClient::NotLoggedInException
+ false # We may redirect to login, or not, based on the current action.
+ else
+ session[:arvados_api_token] = params[:api_token]
+
+ if !request.format.json? and request.method.in? ['GET', 'HEAD']
+ # Repeat this request with api_token in the (new) session
+ # cookie instead of the query string. This prevents API
+ # tokens from appearing in (and being inadvisedly copied
+ # and pasted from) browser Location bars.
+ redirect_to strip_token_from_path(request.fullpath)
+ true
+ else
+ false
+ end
+ ensure
+ Thread.current[:arvados_api_token] = nil
+ end
+ end
+
# Save the session API token in thread-local storage, and yield.
# This method also takes care of session setup if the request
# provides a valid api_token parameter.
# If a token is unavailable or expired, the block is still run, with
# a nil token.
def set_thread_api_token
- # If an API token has already been found, pass it through.
if Thread.current[:arvados_api_token]
- yield
+ yield # An API token has already been found - pass it through.
return
+ elsif setup_user_session
+ return # A new session was set up and received a response.
end
begin
- # If there's a valid api_token parameter, use it to set up the session.
- if (Thread.current[:arvados_api_token] = params[:api_token]) and
- verify_api_token
- session[:arvados_api_token] = params[:api_token]
- u = User.current
- session[:user] = {
- uuid: u.uuid,
- email: u.email,
- first_name: u.first_name,
- last_name: u.last_name,
- is_active: u.is_active,
- is_admin: u.is_admin,
- prefs: u.prefs
- }
- if !request.format.json? and request.method.in? ['GET', 'HEAD']
- # Repeat this request with api_token in the (new) session
- # cookie instead of the query string. This prevents API
- # tokens from appearing in (and being inadvisedly copied
- # and pasted from) browser Location bars.
- redirect_to strip_token_from_path(request.fullpath)
- return
- end
- end
-
- # With setup done, handle the request using the session token.
- Thread.current[:arvados_api_token] = session[:arvados_api_token]
- begin
+ load_api_token(session[:arvados_api_token])
+ yield
+ rescue ArvadosApiClient::NotLoggedInException
+ # If we got this error with a token, it must've expired.
+ # Retry the request without a token.
+ unless Thread.current[:arvados_api_token].nil?
+ load_api_token(nil)
yield
- rescue ArvadosApiClient::NotLoggedInException
- # If we got this error with a token, it must've expired.
- # Retry the request without a token.
- unless Thread.current[:arvados_api_token].nil?
- Thread.current[:arvados_api_token] = nil
- yield
- end
end
ensure
# Remove token in case this Thread is used for anything else.
- Thread.current[:arvados_api_token] = nil
+ load_api_token(nil)
end
end
end
end
- def verify_api_token
- begin
- Link.where(uuid: 'just-verifying-my-api-token')
- true
- rescue ArvadosApiClient::NotLoggedInException
- false
- end
- end
-
def ensure_current_user_is_admin
unless current_user and current_user.is_admin
@errors = ['Permission denied']
true
end
+ def check_user_profile
+ if request.method.downcase != 'get' || params[:partial] ||
+ params[:tab_pane] || params[:action_method] ||
+ params[:action] == 'setup_popup'
+ return true
+ end
+
+ if missing_required_profile?
+ render 'users/profile'
+ end
+ true
+ end
+
+ helper_method :missing_required_profile?
+ def missing_required_profile?
+ missing_required = false
+
+ profile_config = Rails.configuration.user_profile_form_fields
+ if current_user && profile_config
+ current_user_profile = current_user.prefs[:profile]
+ profile_config.kind_of?(Array) && profile_config.andand.each do |entry|
+ if entry['required']
+ if !current_user_profile ||
+ !current_user_profile[entry['key'].to_sym] ||
+ current_user_profile[entry['key'].to_sym].empty?
+ missing_required = true
+ break
+ end
+ end
+ end
+ end
+
+ missing_required
+ end
+
def select_theme
return Rails.configuration.arvados_theme
end
helper_method :all_projects
def all_projects
@all_projects ||= Group.
- filter([['group_class','in',['project','folder']]]).order('name')
+ filter([['group_class','=','project']]).order('name')
end
helper_method :my_projects
(Job.limit(10) |
PipelineInstance.limit(10)).
sort_by do |x|
- x.finished_at || x.started_at || x.created_at rescue x.created_at
- end
+ (x.finished_at || x.started_at rescue nil) || x.modified_at || x.created_at
+ end.reverse
end
helper_method :my_project_tree
crumbs = []
current = @name_link || @object
while current
- if current.is_a?(Group) and current.group_class.in?(['project','folder'])
+ if current.is_a?(Group) and current.group_class == 'project'
crumbs.prepend current
end
if current.is_a? Link
helper_method :current_project_uuid
def current_project_uuid
- if @object.is_a? Group and @object.group_class.in?(['project','folder'])
+ if @object.is_a? Group and @object.group_class == 'project'
@object.uuid
elsif @name_link.andand.tail_uuid
@name_link.tail_uuid
@objects_for
end
+ def wiselinks_layout
+ 'body'
+ end
end