X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d0c85cb89af65eff3e629fa3d475a289e289cff7..8deda2012ea7e25610f2cbd0a271e131d9364503:/apps/workbench/app/controllers/application_controller.rb diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb index 20dea38dcd..3270cfb376 100644 --- a/apps/workbench/app/controllers/application_controller.rb +++ b/apps/workbench/app/controllers/application_controller.rb @@ -11,13 +11,14 @@ class ApplicationController < ActionController::Base around_filter :set_thread_api_token # Methods that don't require login should # skip_around_filter :require_thread_api_token - around_filter :require_thread_api_token, except: [:report_issue_popup, :report_issue] + ERROR_ACTIONS + around_filter :require_thread_api_token, except: ERROR_ACTIONS + before_filter :set_cache_buster before_filter :accept_uuid_as_id_param, except: ERROR_ACTIONS - before_filter :check_user_agreements, except: [:report_issue_popup, :report_issue] + 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 + before_filter :find_object_by_uuid, except: [:create, :index, :choose] + ERROR_ACTIONS theme :select_theme begin @@ -31,6 +32,12 @@ class ApplicationController < ActionController::Base with: :render_exception) end + def set_cache_buster + response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" + end + def unprocessable(message=nil) @errors ||= [] @@ -98,8 +105,28 @@ class ApplicationController < ActionController::Base end end + # params[:order]: + # + # The order can be left empty to allow it to default. + # Or it can be a comma separated list of real database column names, one per model. + # Column names should always be qualified by a table name and a direction is optional, defaulting to asc + # (e.g. "collections.name" or "collections.name desc"). + # If a column name is specified, that table will be sorted by that column. + # If there are objects from different models that will be shown (such as in Jobs and Pipelines tab), + # then a sort column name can optionally be specified for each model, passed as an comma-separated list (e.g. "jobs.script, pipeline_instances.name") + # Currently only one sort column name and direction can be specified for each model. def load_filters_and_paging_params - @order = params[:order] || 'created_at desc' + if params[:order].blank? + @order = 'created_at desc' + elsif params[:order].is_a? Array + @order = params[:order] + else + begin + @order = JSON.load(params[:order]) + rescue + @order = params[:order].split(',') + end + end @order = [@order] unless @order.is_a? Array @limit ||= 200 @@ -128,6 +155,8 @@ class ApplicationController < ActionController::Base end end end + # After this, params[:filters] can be trusted to be an array of arrays: + params[:filters] = filters @filters += filters end end @@ -135,11 +164,23 @@ class ApplicationController < ActionController::Base def find_objects_for_index @objects ||= model_class @objects = @objects.filter(@filters).limit(@limit).offset(@offset) + @objects.fetch_multiple_pages(false) end def render_index respond_to do |f| - f.json { render json: @objects } + f.json { + if params[:partial] + @next_page_href = next_page_href(partial: params[:partial], filters: @filters.to_json) + render json: { + content: render_to_string(partial: "show_#{params[:partial]}", + formats: [:html]), + next_page_href: @next_page_href + } + else + render json: @objects + end + } f.html { if params[:tab_pane] render_pane params[:tab_pane] @@ -202,10 +243,16 @@ class ApplicationController < ActionController::Base 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(action: :show, id: @object) } + @object.textile_attributes.each do |textile_attr| + extra_attrs.merge!({ "#{textile_attr}Textile" => view_context.render_markup(@object.attributes[textile_attr]) }) + end + render json: @object.attributes.merge(extra_attrs) + end f.html { if params['tab_pane'] - render_pane params['tab_pane'] + render_pane(if params['tab_pane'].is_a? Hash then params['tab_pane']["name"] else params['tab_pane'] end) elsif request.method.in? ['GET', 'HEAD'] render else @@ -218,10 +265,10 @@ class ApplicationController < ActionController::Base def choose params[:limit] ||= 40 - find_objects_for_index if !@objects respond_to do |f| if params[:partial] f.json { + find_objects_for_index if !@objects render json: { content: render_to_string(partial: "choose_rows.html", formats: [:html]), @@ -230,6 +277,7 @@ class ApplicationController < ActionController::Base } end f.js { + find_objects_for_index if !@objects render partial: 'choose', locals: {multiple: params[:multiple]} } end @@ -363,12 +411,17 @@ class ApplicationController < ActionController::Base false # For convenience to return from callbacks end - def using_specific_api_token(api_token) + def using_specific_api_token(api_token, opts={}) start_values = {} [:arvados_api_token, :user].each do |key| start_values[key] = Thread.current[key] end - load_api_token(api_token) + if opts.fetch(:load_user, true) + load_api_token(api_token) + else + Thread.current[:arvados_api_token] = api_token + Thread.current[:user] = nil + end begin yield ensure @@ -683,6 +736,50 @@ class ApplicationController < ActionController::Base end.reverse end + helper_method :running_pipelines + def running_pipelines + pi = PipelineInstance.order(["started_at asc", "created_at asc"]).filter([["state", "in", ["RunningOnServer", "RunningOnClient"]]]) + jobs = {} + pi.each do |pl| + pl.components.each do |k,v| + if v.is_a? Hash and v[:job] + jobs[v[:job][:uuid]] = {} + end + end + end + + if jobs.keys.any? + Job.filter([["uuid", "in", jobs.keys]]).each do |j| + jobs[j[:uuid]] = j + end + + pi.each do |pl| + pl.components.each do |k,v| + if v.is_a? Hash and v[:job] + v[:job] = jobs[v[:job][:uuid]] + end + end + end + end + + pi + end + + helper_method :finished_pipelines + def finished_pipelines lim + PipelineInstance.limit(lim).order(["finished_at desc"]).filter([["state", "in", ["Complete", "Failed", "Paused"]], ["finished_at", "!=", nil]]) + end + + helper_method :recent_collections + def recent_collections lim + c = Collection.limit(lim).order(["modified_at desc"]).filter([["owner_uuid", "is_a", "arvados#group"]]) + own = {} + Group.filter([["uuid", "in", c.map(&:owner_uuid)]]).each do |g| + own[g[:uuid]] = g + end + {collections: c, owners: own} + end + helper_method :my_project_tree def my_project_tree build_project_trees @@ -730,7 +827,7 @@ class ApplicationController < ActionController::Base @my_project_tree = sorted_paths.call buildtree.call(children_of, 'me') @shared_project_tree = - sorted_paths.call({'Shared with me' => + sorted_paths.call({'Projects shared with me' => buildtree.call(children_of, false)}) end @@ -750,6 +847,12 @@ class ApplicationController < ActionController::Base crumbs = [] current = @name_link || @object while current + # Halt if a group ownership loop is detected. API should refuse + # to produce this state, but it could still arise from a race + # condition when group ownership changes between our find() + # queries. + break if crumbs.collect(&:uuid).include? current.uuid + if current.is_a?(Group) and current.group_class == 'project' crumbs.prepend current end