3531: Refactor project tab infinite-scroll. Sort jobs and pipelines
[arvados.git] / apps / workbench / app / controllers / projects_controller.rb
index 91d6e8a71374dead223a9cbff57b0c12ea85acf4..ea3103d9a8d75f1ec7de67ea9db721f711657984 100644 (file)
@@ -3,12 +3,41 @@ class ProjectsController < ApplicationController
     Group
   end
 
+  def find_object_by_uuid
+    if current_user and params[:uuid] == current_user.uuid
+      @object = current_user.dup
+      @object.uuid = current_user.uuid
+      class << @object
+        def name
+          'Home'
+        end
+        def description
+          ''
+        end
+        def attribute_editable? attr, *args
+          case attr
+          when 'description', 'name'
+            false
+          else
+            super
+          end
+        end
+      end
+    else
+      super
+    end
+  end
+
   def index_pane_list
     %w(Projects)
   end
 
   def show_pane_list
-    %w(Contents Permissions Advanced)
+    if @user_is_manager
+      %w(Data_collections Jobs_and_pipelines Pipeline_templates Subprojects Other_objects Sharing Advanced)
+    else
+      %w(Data_collections Jobs_and_pipelines Pipeline_templates Subprojects Other_objects Advanced)
+    end
   end
 
   def remove_item
@@ -48,6 +77,32 @@ class ProjectsController < ApplicationController
     end
   end
 
+  def move_items
+    target_uuid = params['target']
+    uuids_to_add = session[:selected_move_items]
+
+    uuids_to_add.
+      collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
+      uniq.
+      each do |resource_class|
+      resource_class.filter([['uuid','in',uuids_to_add]]).each do |dst|
+        if resource_class == Collection
+          dst = Link.new(owner_uuid: target_uuid,
+                         tail_uuid: target_uuid,
+                         head_uuid: dst.uuid,
+                         link_class: 'name',
+                         name: target_uuid)
+        else
+          dst.owner_uuid = target_uuid
+          dst.tail_uuid = target_uuid if dst.class == Link
+        end
+        dst.save!
+      end
+    end
+    session[:selected_move_items] = nil
+    redirect_to @object
+  end
+
   def destroy
     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
                                   ['tail_uuid','=',@object.uuid]])).any?
@@ -73,50 +128,77 @@ class ProjectsController < ApplicationController
     super
   end
 
+  def load_contents_objects kinds=[]
+    @limit = 5
+    kind_filters = @filters.select do |attr,op,val|
+      op == 'is_a' and val.is_a? Array and val.count > 1
+    end
+    if /^created_at/ =~ @order[0] and kind_filters.count == 1
+      # If filtering on multiple types and sorting by date: Get the
+      # first page of each type, sort the entire set, truncate to one
+      # page, and use the last item on this page as a filter for
+      # retrieving the next page. Ideally the API would do this for
+      # us, but it doesn't (yet).
+      @objects = []
+      kind_filters.each do |attr,op,val|
+        (val.is_a?(Array) ? val : [val]).each do |type|
+          @objects += @object.contents(order: @order,
+                                       limit: @limit,
+                                       include_linked: true,
+                                       filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
+                                       offset: @offset)
+        end
+      end
+      @objects = @objects.to_a.sort_by(&:created_at).reverse[0..@limit-1]
+      @next_page_filters = @filters.reject do |attr,op,val|
+        attr == 'created_at' and op == '<'
+      end
+      if @objects.any?
+        @next_page_filters += [['created_at', '<', @objects.last.created_at]]
+        @next_page_href = url_for(partial: :contents_rows,
+                                  filters: @next_page_filters.to_json)
+      else
+        @next_page_href = nil
+      end
+    else
+      @objects = @object.contents(order: @order,
+                                  limit: @limit,
+                                  include_linked: true,
+                                  filters: @filters,
+                                  offset: @offset)
+      @next_page_href = next_page_href(partial: :contents_rows)
+    end
+  end
+
   def show
     if !@object
       return render_not_found("object not found")
     end
-    @objects = @object.contents(limit: 50,
-                                include_linked: true,
-                                offset: params[:offset] || 0)
-    @share_links = Link.filter([['head_uuid', '=', @object.uuid],
-                                ['link_class', '=', 'permission']])
-    @logs = Log.limit(10).filter([['object_uuid', '=', @object.uuid]])
 
-    @objects_and_names = []
-    @objects.each do |object|
-      if !(name_links = @objects.links_for(object, 'name')).empty?
-        name_links.each do |name_link|
-          @objects_and_names << [object, name_link]
-        end
-      elsif object.respond_to? :name
-        @objects_and_names << [object, object]
-      else
-        @objects_and_names << [object,
-                               Link.new(owner_uuid: @object.uuid,
-                                        tail_uuid: @object.uuid,
-                                        head_uuid: object.uuid,
-                                        link_class: "name",
-                                        name: "")]
+    @user_is_manager = false
+    @share_links = []
+    if @object.uuid != current_user.uuid
+      begin
+        @share_links = Link.permissions_for(@object)
+        @user_is_manager = true
+      rescue ArvadosApiClient::AccessForbiddenException,
+        ArvadosApiClient::NotFoundException
       end
     end
+
     if params[:partial]
+      load_contents_objects
       respond_to do |f|
         f.json {
           render json: {
             content: render_to_string(partial: 'show_contents_rows.html',
-                                      formats: [:html],
-                                      locals: {
-                                        objects_and_names: @objects_and_names,
-                                        project: @object
-                                      }),
-            next_page_href: (next_page_offset and
-                             url_for(offset: next_page_offset, partial: true))
+                                      formats: [:html]),
+            next_page_href: @next_page_href
           }
         }
       end
     else
+      @objects = []
       super
     end
   end
@@ -131,4 +213,61 @@ class ProjectsController < ApplicationController
     @updates = params['project']
     super
   end
+
+  helper_method :get_objects_and_names
+  def get_objects_and_names(objects=nil)
+    objects = @objects if objects.nil?
+    objects_and_names = []
+    objects.each do |object|
+      if objects.respond_to? :links_for and
+          !(name_links = objects.links_for(object, 'name')).empty?
+        name_links.each do |name_link|
+          objects_and_names << [object, name_link]
+        end
+      elsif object.respond_to? :name
+        objects_and_names << [object, object]
+      else
+        objects_and_names << [object,
+                               Link.new(owner_uuid: @object.uuid,
+                                        tail_uuid: @object.uuid,
+                                        head_uuid: object.uuid,
+                                        link_class: "name",
+                                        name: "")]
+      end
+    end
+    objects_and_names
+  end
+
+  def share_with
+    if not params[:uuids].andand.any?
+      @errors = ["No user/group UUIDs specified to share with."]
+      return render_error(status: 422)
+    end
+    results = {"success" => [], "errors" => []}
+    params[:uuids].each do |shared_uuid|
+      begin
+        Link.create(tail_uuid: shared_uuid, link_class: "permission",
+                    name: "can_read", head_uuid: @object.uuid)
+      rescue ArvadosApiClient::ApiError => error
+        error_list = error.api_response.andand[:errors]
+        if error_list.andand.any?
+          results["errors"] += error_list.map { |e| "#{shared_uuid}: #{e}" }
+        else
+          error_code = error.api_status || "Bad status"
+          results["errors"] << "#{shared_uuid}: #{error_code} response"
+        end
+      else
+        results["success"] << shared_uuid
+      end
+    end
+    if results["errors"].empty?
+      results.delete("errors")
+      status = 200
+    else
+      status = 422
+    end
+    respond_to do |f|
+      f.json { render(json: results, status: status) }
+    end
+  end
 end