Merge branch 'master' into 3354-render-textile
authorPhil Hodgson <bitbucket@philhodgson.net>
Wed, 20 Aug 2014 13:04:54 +0000 (09:04 -0400)
committerPhil Hodgson <bitbucket@philhodgson.net>
Wed, 20 Aug 2014 13:04:54 +0000 (09:04 -0400)
Conflicts (resolved):
apps/workbench/app/controllers/application_controller.rb

1  2 
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/helpers/application_helper.rb

index a673421ca1bd7b3ee8fb08ef6d84ccb652d7f6fe,5c99c15abc62544f79c4bca6f148da051f8e59a8..d2f4ed44b240990b5c611c34d0103a478924c7f4
@@@ -14,6 -14,7 +14,7 @@@ class ApplicationController < ActionCon
    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: 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
      else
        @errors = [e.to_s]
      end
-     # If the user has an active session, and the API server is available,
-     # make user information available on the error page.
+     # Make user information available on the error page, falling back to the
+     # session cache if the API server is unavailable.
      begin
        load_api_token(session[:arvados_api_token])
      rescue ArvadosApiClient::ApiError
-       load_api_token(nil)
+       unless session[:user].nil?
+         begin
+           Thread.current[:user] = User.new(session[:user])
+         rescue ArvadosApiClient::ApiError
+           # This can happen if User's columns are unavailable.  Nothing to do.
+         end
+       end
      end
-     # Preload projects trees for the template.  If that fails, set empty
+     # Preload projects trees for the template.  If that's not doable, 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 ||= []
+     unless current_user.nil?
+       begin
+         build_project_trees
+       rescue ArvadosApiClient::ApiError
+         # Fall back to the default-setting code later.
+       end
      end
+     @my_project_tree ||= []
+     @shared_project_tree ||= []
      render_error(err_opts)
    end
  
@@@ -89,6 -99,9 +99,9 @@@
    end
  
    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
      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
        return render_not_found("object not found")
      end
      respond_to do |f|
 -      f.json { render json: @object.attributes.merge(href: url_for(action: :show, id: @object)) }
 +      f.json do
-         extra_attrs = { href: url_for(@object) }
++        extra_attrs = { href: url_for(action: :show, id: @object) }
 +        @object.textile_attributes.each do |textile_attr|
 +          extra_attrs.merge!({ "#{textile_attr}Textile" => view_context.render_content_from_database(@object.attributes[textile_attr]) })
 +        end
 +        render json: @object.attributes.merge(extra_attrs)
 +      end
        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
      @object ||= model_class.new @new_resource_attrs, params["options"]
      if @object.save
        respond_to do |f|
-         f.json { render json: @object.attributes.merge(href: url_for(@object)) }
+         f.json { render json: @object.attributes.merge(href: url_for(action: :show, id: @object)) }
          f.html {
            redirect_to @object
          }
      Thread.current[:arvados_api_token] = new_token
      if new_token.nil?
        Thread.current[:user] = nil
-     elsif (new_token == session[:arvados_api_token]) and
-         session[:user].andand[:is_active]
-       Thread.current[:user] = User.new(session[:user])
      else
        Thread.current[:user] = User.current
      end
        false  # We may redirect to login, or not, based on the current action.
      else
        session[:arvados_api_token] = params[:api_token]
+       # If we later have trouble contacting the API server, we still want
+       # to be able to render basic user information in the UI--see
+       # render_exception above.  We store that in the session here.  This is
+       # not intended to be used as a general-purpose cache.  See #2891.
        session[:user] = {
          uuid: user.uuid,
          email: user.email,
          is_admin: user.is_admin,
          prefs: user.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
      end
    end
  
-   # Reroute this request if an API token is unavailable.
+   # Redirect to login/welcome if client provided expired API token (or none at all)
    def require_thread_api_token
      if Thread.current[:arvados_api_token]
        yield
        session.delete :arvados_api_token
        redirect_to_login
      else
-       render 'users/welcome'
+       redirect_to welcome_users_path(return_to: request.fullpath)
      end
    end
  
      end
    end
  
+   helper_method :unsigned_user_agreements
+   def unsigned_user_agreements
+     @signed_ua_uuids ||= UserAgreement.signatures.map &:head_uuid
+     @unsigned_user_agreements ||= UserAgreement.all.map do |ua|
+       if not @signed_ua_uuids.index ua.uuid
+         Collection.find(ua.uuid)
+       end
+     end.compact
+   end
    def check_user_agreements
      if current_user && !current_user.is_active
        if not current_user.is_invited
-         return render 'users/inactive'
+         return redirect_to inactive_users_path(return_to: request.fullpath)
        end
-       signatures = UserAgreement.signatures
-       @signed_ua_uuids = UserAgreement.signatures.map &:head_uuid
-       @required_user_agreements = UserAgreement.all.map do |ua|
-         if not @signed_ua_uuids.index ua.uuid
-           Collection.find(ua.uuid)
-         end
-       end.compact
-       if @required_user_agreements.empty?
+       if unsigned_user_agreements.empty?
          # No agreements to sign. Perhaps we just need to ask?
          current_user.activate
          if !current_user.is_active
          end
        end
        if !current_user.is_active
-         render 'user_agreements/index'
+         redirect_to user_agreements_path(return_to: request.fullpath)
        end
      end
      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?
+       redirect_to profile_user_path(current_user.uuid, return_to: request.fullpath)
+     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
      }
    }
  
-   #@@notification_tests.push lambda { |controller, current_user|
-   #  Job.limit(1).where(created_by: current_user.uuid).each do
-   #    return nil
-   #  end
-   #  return lambda { |view|
-   #    view.render partial: 'notifications/jobs_notification'
-   #  }
-   #}
    @@notification_tests.push lambda { |controller, current_user|
      Collection.limit(1).where(created_by: current_user.uuid).each do
        return nil
      @notification_count = 0
      @notifications = []
  
-     if current_user
+     if current_user.andand.is_active
        @showallalerts = false
        @@notification_tests.each do |t|
          a = t.call(self, current_user)
index f09e0fbabc1e70b3cea307e94d3b1feec7f0cffd,428c14f8282961f65e6ad69e9197794f62997d54..a219cd5cc3423820ea3c029e6c6c78e57ca24095
@@@ -12,7 -12,7 +12,7 @@@ module ApplicationHelpe
    end
  
    def render_content_from_database(markup)
 -    raw RedCloth.new(markup).to_html
 +    raw RedCloth.new(markup.to_s).to_html(:refs_arvados, :textile) if markup
    end
  
    def human_readable_bytes_html(n)
      if !object.attribute_editable?(attr, :ever) or
          (!object.editable? and
           !object.owner_uuid.in?(my_projects.collect(&:uuid)))
 -      return ((attrvalue && attrvalue.length > 0 && attrvalue) ||
 -              (attr == 'name' and object.andand.default_name) ||
 -              '(none)')
 +      if attrvalue && attrvalue.length > 0
 +        return render_textile_if_textile( object, attr, attrvalue )
 +      else
 +        return (attr == 'name' and object.andand.default_name) ||
 +                '(none)'
 +      end
      end
  
      input_type = 'text'
      end
  
      attrvalue = attrvalue.to_json if attrvalue.is_a? Hash or attrvalue.is_a? Array
 +    rendervalue = render_textile_if_textile( object, attr, attrvalue )
  
      ajax_options = {
        "data-pk" => {
      @unique_id ||= (Time.now.to_f*1000000).to_i
      span_id = object.uuid.to_s + '-' + attr.to_s + '-' + (@unique_id += 1).to_s
  
 -    span_tag = content_tag 'span', attrvalue.to_s, {
 +    span_tag = content_tag 'span', rendervalue, {
        "data-emptytext" => (object.andand.default_name || 'none'),
        "data-placement" => "bottom",
        "data-type" => input_type,
        "data-name" => attr,
        "data-object-uuid" => object.uuid,
        "data-toggle" => "manual",
 +      "data-value" => attrvalue,
        "id" => span_id,
 -      :class => "editable"
 +      :class => "editable #{is_textile?( object, attr ) ? 'editable-textile' : ''}"
      }.merge(htmloptions).merge(ajax_options)
      edit_button = raw('<a href="#" class="btn btn-xs btn-default btn-nodecorate" data-toggle="x-editable tooltip" data-toggle-selector="#' + span_id + '" data-placement="top" title="' + (htmloptions[:tiptitle] || 'edit') + '"><i class="fa fa-fw fa-pencil"></i></a>')
      if htmloptions[:btnplacement] == :left
        end
        modal_path = choose_collections_path \
        ({ title: chooser_title,
-          filters: [['tail_uuid', '=', object.owner_uuid]].to_json,
+          filters: [['owner_uuid', '=', object.owner_uuid]].to_json,
           action_name: 'OK',
           action_href: pipeline_instance_path(id: object.uuid),
           action_method: 'patch',
        nil
      end
    end
 +
 +private
 +  def is_textile?( object, attr )
 +    is_textile = object.textile_attributes.andand.include?(attr)
 +  end
 +
 +  def render_textile_if_textile( object, attr, attrvalue )
 +    is_textile?( object, attr ) ? render_content_from_database(attrvalue) : attrvalue
 +  end
  end