Merge branch '8183-projects-dropdown' into 8286-fav-projects
authorradhika <radhika@curoverse.com>
Mon, 15 Feb 2016 17:35:02 +0000 (12:35 -0500)
committerradhika <radhika@curoverse.com>
Mon, 15 Feb 2016 17:35:02 +0000 (12:35 -0500)
Conflicts:
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/views/application/_projects_tree_menu.html.erb
apps/workbench/test/controllers/projects_controller_test.rb

1  2 
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/views/application/_projects_tree_menu.html.erb
apps/workbench/test/controllers/projects_controller_test.rb

index 61c57ae29f523b66b16b080293a45b7abdb368b8,9438da3fb699eb741fa352d3df848a11d7b870ad..5094556d64e69b924c4016444d17a7ce434fedee
@@@ -94,9 -94,7 +94,10 @@@ class ApplicationController < ActionCon
          # Fall back to the default-setting code later.
        end
      end
 +    @starred_projects ||= []
+     @my_wanted_projects_tree ||= []
 +    @my_project_tree ||= []
 +    @shared_project_tree ||= []
      render_error(err_opts)
    end
  
      {collections: c, owners: own}
    end
  
 +  helper_method :my_starred_projects
 +  def my_starred_projects user
 +    return if @starred_projects
 +    links = Link.filter([['tail_uuid', '=', user.uuid],
 +                         ['link_class', '=', 'star'],
 +                         ['head_uuid', 'is_a', 'arvados#group']]).select(%w(head_uuid))
 +    uuids =links.collect { |x| x.head_uuid }
 +    starred_projects = Group.filter([['uuid', 'in', uuids]]).order('name')
 +    @starred_projects = starred_projects.results
 +  end
 +
+   # If there are more than 200 projects that are readable by the user,
+   # build the tree using only the top 200+ projects owned by the user,
+   # from the top three levels.
+   # That is: get toplevel projects under home, get subprojects of
+   # these projects, and so on until we hit the limit.
+   def my_wanted_projects user, page_size=100
+     return @my_wanted_projects if @my_wanted_projects
+     from_top = []
+     uuids = [user.uuid]
+     depth = 0
+     @too_many_projects = false
+     @reached_level_limit = false
+     while from_top.size <= page_size*2
+       current_level = Group.filter([['group_class','=','project'],
+                                     ['owner_uuid', 'in', uuids]])
+                       .order('name').limit(page_size*2)
+       break if current_level.results.size == 0
+       @too_many_projects = true if current_level.items_available > current_level.results.size
+       from_top.concat current_level.results
+       uuids = current_level.results.collect { |x| x.uuid }
+       depth += 1
+       if depth >= 3
+         @reached_level_limit = true
+         break
+       end
+     end
+     @my_wanted_projects = from_top
+   end
+   helper_method :my_wanted_projects_tree
+   def my_wanted_projects_tree user, page_size=100
+     build_my_wanted_projects_tree user, page_size
+     [@my_wanted_projects_tree, @too_many_projects, @reached_level_limit]
+   end
+   def build_my_wanted_projects_tree user, page_size=100
+     return @my_wanted_projects_tree if @my_wanted_projects_tree
+     parent_of = {user.uuid => 'me'}
+     my_wanted_projects(user, page_size).each do |ob|
+       parent_of[ob.uuid] = ob.owner_uuid
+     end
+     children_of = {false => [], 'me' => [user]}
+     my_wanted_projects(user, page_size).each do |ob|
+       if ob.owner_uuid != user.uuid and
+           not parent_of.has_key? ob.owner_uuid
+         parent_of[ob.uuid] = false
+       end
+       children_of[parent_of[ob.uuid]] ||= []
+       children_of[parent_of[ob.uuid]] << ob
+     end
+     buildtree = lambda do |children_of, root_uuid=false|
+       tree = {}
+       children_of[root_uuid].andand.each do |ob|
+         tree[ob] = buildtree.call(children_of, ob.uuid)
+       end
+       tree
+     end
+     sorted_paths = lambda do |tree, depth=0|
+       paths = []
+       tree.keys.sort_by { |ob|
+         ob.is_a?(String) ? ob : ob.friendly_link_name
+       }.each do |ob|
+         paths << {object: ob, depth: depth}
+         paths += sorted_paths.call tree[ob], depth+1
+       end
+       paths
+     end
+     @my_wanted_projects_tree =
+       sorted_paths.call buildtree.call(children_of, 'me')
+   end
    helper_method :my_project_tree
    def my_project_tree
      build_project_trees
index 221d9a28a0e5aef7f0030deb606ef76b473c3447,69f97e404aab589eb3575c59068841b51a3cf8a2..6e116dd48e0b9740f7af36fe86c03d456718db79
@@@ -1,15 -1,26 +1,42 @@@
-   My projects
 +<li role="presentation" class="dropdown-header">
++  My favorite projects
 +</li>
 +<li>
 +  <%= project_link_to.call({object: current_user, depth: 0}) do %>
 +    <span style="padding-left: 0">Home</span>
 +  <% end %>
 +</li>
 +<% (my_starred_projects current_user).each do |pnode| %>
 +  <li>
 +    <%= project_link_to.call({object: pnode, depth: 0}) do%>
 +      <span style="padding-left: 0em"></span><%= pnode[:name] %>
 +    <% end %>
 +  </li>
 +<% end %>
++
+ <li role="presentation" class="dropdown-header">
+   My projects
+ </li>
+ <li>
+   <%= project_link_to.call({object: current_user, depth: 0}) do %>
+     <span style="padding-left: 0">Home</span>
+   <% end %>
+ </li>
+ <% my_tree = my_wanted_projects_tree current_user %>
+ <% my_tree[0].each do |pnode| %>
+   <% next if pnode[:object].class != Group %>
+   <li>
+     <%= project_link_to.call pnode do %>
+       <span style="padding-left: <%= pnode[:depth] %>em"></span><%= pnode[:object].name %>
+     <% end %>
+   </li>
+ <% end %>
+ <% if my_tree[1] or my_tree[0].size > 200 %>
+ <li role="presentation" class="dropdown-header">
+   Some projects have been omitted.
+ </li>
+ <% elsif my_tree[2] %>
+ <li role="presentation" class="dropdown-header">
+   Showing top three levels of your projects.
+ </li>
+ <% end %>
index 643aaae6c92a772161561c3d305c647fe4f5d0d9,6bdab55ddf20d302f3369883bc930872a5045024..58914a84ac87b5b0949f07d634a826226a2b64af
@@@ -419,53 -419,32 +419,82 @@@ class ProjectsControllerTest < ActionCo
      assert_select "#projects-menu + ul li.divider ~ li a[href=/projects/#{project_uuid}]"
    end
  
+   [
+     ["active", 5, ["aproject", "asubproject"], "anonymously_accessible_project"],
+     ["user1_with_load", 2, ["project_with_10_collections"], "project_with_2_pipelines_and_60_jobs"],
+     ["admin", 5, ["anonymously_accessible_project", "subproject_in_anonymous_accessible_project"], "aproject"],
+   ].each do |user, page_size, tree_segment, unexpected|
+     test "build my projects tree for #{user} user and verify #{unexpected} is omitted" do
+       use_token user
+       ctrl = ProjectsController.new
+       current_user = User.find(api_fixture('users')[user]['uuid'])
+       my_tree = ctrl.send :my_wanted_projects_tree, current_user, page_size
+       tree_segment_at_depth_1 = api_fixture('groups')[tree_segment[0]]
+       tree_segment_at_depth_2 = api_fixture('groups')[tree_segment[1]] if tree_segment[1]
+       tree_nodes = {}
+       my_tree[0].each do |x|
+         tree_nodes[x[:object]['uuid']] = x[:depth]
+       end
+       assert_equal(1, tree_nodes[tree_segment_at_depth_1['uuid']])
+       assert_equal(2, tree_nodes[tree_segment_at_depth_2['uuid']]) if tree_segment[1]
+       unexpected_project = api_fixture('groups')[unexpected]
+       assert_nil(tree_nodes[unexpected_project['uuid']])
+     end
+   end
++
 +  [
 +    ["active", 1],
 +    ["project_viewer", 1],
 +    ["admin", 0],
 +  ].each do |user, size|
 +    test "starred projects for #{user}" do
 +      use_token user
 +      ctrl = ProjectsController.new
 +      current_user = User.find(api_fixture('users')[user]['uuid'])
 +      my_starred_project = ctrl.send :my_starred_projects, current_user
 +      assert_equal(size, my_starred_project.andand.size)
 +
 +      ctrl2 = ProjectsController.new
 +      current_user = User.find(api_fixture('users')[user]['uuid'])
 +      my_starred_project = ctrl2.send :my_starred_projects, current_user
 +      assert_equal(size, my_starred_project.andand.size)
 +    end
 +  end
 +
 +  test "unshare project and verify that it is no longer included in shared user's starred projects" do
 +    # remove sharing link
 +    use_token :system_user
 +    Link.find(api_fixture('links')['share_starred_project_with_project_viewer']['uuid']).destroy
 +
 +    # verify that project is no longer included in starred projects
 +    use_token :project_viewer
 +    current_user = User.find(api_fixture('users')['project_viewer']['uuid'])
 +    ctrl = ProjectsController.new
 +    my_starred_project = ctrl.send :my_starred_projects, current_user
 +    assert_equal(0, my_starred_project.andand.size)
 +
 +    # share it again
 +    @controller = LinksController.new
 +    post :create, {
 +      link: {
 +        link_class: 'permission',
 +        name: 'can_read',
 +        head_uuid: api_fixture('groups')['starred_and_shared_active_user_project']['uuid'],
 +        tail_uuid: api_fixture('users')['project_viewer']['uuid'],
 +      },
 +      format: :json
 +    }, session_for(:system_user)
 +
 +    # verify that the project is again included in starred projects
 +    use_token :project_viewer
 +    ctrl = ProjectsController.new
 +    my_starred_project = ctrl.send :my_starred_projects, current_user
 +    assert_equal(1, my_starred_project.andand.size)
 +  end
  end