Merge remote-tracking branch 'origin/master' into 2044-share-button
authorPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 22 May 2014 20:30:34 +0000 (16:30 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 22 May 2014 20:30:34 +0000 (16:30 -0400)
1  2 
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/views/collections/_sharing_button.html.erb
apps/workbench/app/views/collections/_sharing_popup.html.erb
apps/workbench/app/views/collections/_show_files.html.erb
apps/workbench/app/views/collections/show.html.erb
apps/workbench/config/routes.rb
services/api/app/models/arvados_model.rb

index 509b9f4cb2f37d6fd7deb5edb1a80cb1b74483b0,3b4943f5889a1910815417157c351aa5f1ad7704..3e981e10d8b94de9e64c6b83029fc42495ebb55c
@@@ -110,99 -120,44 +120,76 @@@ class CollectionsController < Applicati
      self.response_body = file_enumerator opts
    end
  
-     ApiClientAuthorization.where(filters: [['scopes', '=', ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/collections/#{@object.uuid}/"]]])
++  def sharing_scopes
++    ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/keep_services"]
++  end
++
 +  def search_scopes
++    ApiClientAuthorization.where(filters: [['scopes', '=', sharing_scopes]])
 +  end
 +
    def show
      return super if !@object
-     @provenance = []
-     @output2job = {}
-     @output2colorindex = {}
-     @sourcedata = {params[:uuid] => {uuid: params[:uuid]}}
-     @protected = {}
-     @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
-     colorindex = -1
-     any_hope_left = true
-     while any_hope_left
-       any_hope_left = false
-       Job.where(output: @sourcedata.keys).sort_by { |a| a.finished_at || a.created_at }.reverse.each do |job|
-         if !@output2colorindex[job.output]
-           any_hope_left = true
-           @output2colorindex[job.output] = (colorindex += 1) % 10
-           @provenance << {job: job, output: job.output}
-           @sourcedata.delete job.output
-           @output2job[job.output] = job
-           job.dependencies.each do |new_source_data|
-             unless @output2colorindex[new_source_data]
-               @sourcedata[new_source_data] = {uuid: new_source_data}
-             end
-           end
-         end
-       end
-     end
-     Link.where(head_uuid: @sourcedata.keys | @output2job.keys).each do |link|
-       if link.link_class == 'resources' and link.name == 'wants'
-         @protected[link.head_uuid] = true
-         if link.tail_uuid == current_user.uuid
-           @is_persistent = true
-         end
+     if current_user
+       jobs_with = lambda do |conds|
+         Job.limit(RELATION_LIMIT).where(conds)
+           .results.sort_by { |j| j.finished_at || j.created_at }
        end
+       @output_of = jobs_with.call(output: @object.uuid)
+       @log_of = jobs_with.call(log: @object.uuid)
+       folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+         .where(head_uuid: @object.uuid, link_class: 'name').results
+       folder_hash = Group.where(uuid: folder_links.map(&:tail_uuid)).to_hash
+       @folders = folder_links.map { |link| folder_hash[link.tail_uuid] }
+       @permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+         .where(head_uuid: @object.uuid, link_class: 'permission',
+                name: 'can_read').results
+       @logs = Log.limit(RELATION_LIMIT).order("created_at DESC")
+         .where(object_uuid: @object.uuid).results
+       @is_persistent = Link.limit(1)
+         .where(head_uuid: @object.uuid, tail_uuid: current_user.uuid,
+                link_class: 'resources', name: 'wants')
+         .results.any?
++      @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
      end
-     Link.where(tail_uuid: @sourcedata.keys).each do |link|
-       if link.link_class == 'data_origin'
-         @sourcedata[link.tail_uuid][:data_origins] ||= []
-         @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_uuid]
-       end
-     end
-     Collection.where(uuid: @sourcedata.keys).each do |collection|
-       if @sourcedata[collection.uuid]
-         @sourcedata[collection.uuid][:collection] = collection
-       end
-     end
-     Collection.where(uuid: @object.uuid).each do |u|
-       @prov_svg = ProvenanceHelper::create_provenance_graph(u.provenance, "provenance_svg",
-                                                             {:request => request,
-                                                               :direction => :bottom_up,
-                                                               :combine_jobs => :script_only}) rescue nil
-       @used_by_svg = ProvenanceHelper::create_provenance_graph(u.used_by, "used_by_svg",
-                                                                {:request => request,
-                                                                  :direction => :top_down,
-                                                                  :combine_jobs => :script_only,
-                                                                  :pdata_only => true}) rescue nil
-     end
+     @prov_svg = ProvenanceHelper::create_provenance_graph(@object.provenance, "provenance_svg",
+                                                           {:request => request,
+                                                             :direction => :bottom_up,
+                                                             :combine_jobs => :script_only}) rescue nil
+     @used_by_svg = ProvenanceHelper::create_provenance_graph(@object.used_by, "used_by_svg",
+                                                              {:request => request,
+                                                                :direction => :top_down,
+                                                                :combine_jobs => :script_only,
+                                                                :pdata_only => true}) rescue nil
    end
  
-     a = ApiClientAuthorization.create(scopes: ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/collections/#{@object.uuid}/"])
 +  def sharing_popup
 +    @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
 +    respond_to do |format|
 +      format.html
 +      format.js
 +    end
 +  end
 +
 +  def share
++    a = ApiClientAuthorization.create(scopes: sharing_scopes)
 +    @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
 +    render 'sharing_popup'
 +  end
 +
 +  def unshare
 +    @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
 +    @search_sharing.each do |s|
 +      s.destroy
 +    end
 +    @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
 +    render 'sharing_popup'
 +  end
 +
    protected
  
-   def find_usable_token
-     # Iterate over every token available to make it the current token and
+   def find_usable_token(token_list)
+     # Iterate over every given token to make it the current token and
      # yield the given block.
      # If the block succeeds, return the token it used.
      # Otherwise, render an error response based on the most specific
index b4f59f70155c01d598c520d9874be83a5f0d5fc1,0000000000000000000000000000000000000000..36952d69d041430dd3ea7af734461921a6145104
mode 100644,000000..100644
--- /dev/null
@@@ -1,8 -1,0 +1,8 @@@
- <%= link_to linktext, sharing_popup_collection_url(id: @object.uuid),  {class: "btn-xs #{btnstyle}", :remote => true, 'data-toggle' =>  "modal", 'data-target' => '#collection-sharing-modal-window'}  %>
 +<% if @search_sharing.any? %>
 +  <% linktext = "Shared" %>
 +  <% btnstyle = "btn-success" %>
 +<% else %>
 +  <% linktext = "Share" %>
 +  <% btnstyle = "btn-info" %>
 +<% end %>
++<%= link_to linktext, sharing_popup_collection_url(id: @object.uuid),  {class: "btn #{btnstyle}", :remote => true, 'data-toggle' =>  "modal", 'data-target' => '#collection-sharing-modal-window'}  %>
index 719f8fb4a60c009f066a2dadc9f98082143a96d1,0000000000000000000000000000000000000000..f7f05558371992f8ed7df72388bb8fba67bf29b9
mode 100644,000000..100644
--- /dev/null
@@@ -1,37 -1,0 +1,37 @@@
-           <% link = collection_url + "?reader_tokens[]=#{@search_sharing.first.api_token}" %>
 +
 +<div class="modal-dialog">
 +  <div class="modal-content">
 +    <div class="modal-header">
 +      <button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">&times;</button>
 +      <h4 class="modal-title">Sharing</h4>
 +    </div>
 +    <div class="modal-body">
 +      <div id="sharing-text" style="text-align: center; word-wrap: break-word">
 +        <% if @search_sharing.any? %>
 +          Use this link to share this collection:<br>
 +          <big>
++          <% link = collections_url + "/download/#{@object.uuid}/#{@search_sharing.first.api_token}" %>
 +          <%= link_to link, link %>
 +          </big>
 +        <% else %>
 +          Not shared.
 +        <% end %>
 +      </div>
 +      <div style="text-align: center; padding-top: 1em">
 +      <% if @search_sharing and @search_sharing.any? %>
 +        <%= link_to "Unshare", unshare_collection_url, {
 +            class: 'btn btn-success',
 +              remote: true,
 +            method: 'post'
 +            } %>
 +      <% else %>
 +        <%= link_to "Share", share_collection_url, {
 +              class: 'btn btn-info',
 +              remote: true,
 +              method: 'post'
 +            } %>
 +      <% end %>
 +      </div>
 +    </div>
 +  </div>
 +</div>
index 28c3396e4fb7f565cafcf2088b0b5a1dd3fed9a8,c5c12792ce6890371a2c6ac3d99914ae94fd7787..74e02f79fe0c87a18a56b99dfa6c5ab3e0c645ff
@@@ -10,12 -3,8 +3,9 @@@
    <div class="col-md-6"></div>
    <div class="col-md-6">
      <div class="pull-right">
-       <span id="sharing-button">
-         <%= render partial: 'sharing_button' %>
-       </span>
 -      Collection storage status:
 +      <span style="padding-left: 1em">Collection storage status:</span>
        <%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
 +
      </div>
    </div>
  </div>
                  :class => 'persistent-selection',
                  :friendly_type => "File",
                  :friendly_name => "#{@object.uuid}/#{file_path}",
-                 :href => "#{url_for controller: 'collections', action: 'show', id: @object.uuid }/#{file_path}",
-                 :title => "Click to add this item to your selection list"
+                 :href => url_for(controller: 'collections', action: 'show_file',
+                                  uuid: @object.uuid, file: file_path),
+                 :title => "Include #{file_path} in your selections",
                } %>
-         </td>
-         <td>
-           <%= file[0] %>
-         </td>
-       <td>
-         <%= link_to (if CollectionsHelper::is_image file[1]
-                        image_tag "#{url_for @object}/#{file_path}", class: "file-list-inline-image"
-                      else
-                        file[1]
-                      end),
-             {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'inline'},
-             {title: file_path} %>
-       </td>
-         <td style="text-align:right">
-           <%= raw(human_readable_bytes_html(file[2])) %>
-         </td>
-         <td>
-           <div style="display:inline-block">
-             <%= link_to raw('<i class="glyphicon glyphicon-download-alt"></i>'), {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'attachment'}, {class: 'btn btn-info btn-sm', title: 'Download'} %>
-           </div>
-         </td>
-       </tr>
-     <% end; end %>
-   </tbody>
- </table>
+           <%= link_to(raw('<i class="fa fa-search"></i>'),
+                       link_params.merge(disposition: 'inline'),
+                       {title: "View #{file_path}", class: "btn btn-info btn-sm"}) %>
+           <%= link_to(raw('<i class="fa fa-download"></i>'),
+                       link_params.merge(disposition: 'attachment'),
+                       {title: "Download #{file_path}", class: "btn btn-info btn-sm"}) %>
+         </div>
+       <% if CollectionsHelper::is_image(filename) %>
+         <div class="collection_files_name"><i class="fa fa-fw fa-bar-chart-o"></i> <%= filename %></div>
+        </div>
+         <div class="collection_files_inline">
+           <%= link_to(image_tag("#{url_for @object}/#{file_path}"),
+                       link_params.merge(disposition: 'inline'),
+                       {title: file_path}) %>
+         </div>
+       <% else %>
+         <div class="collection_files_name"><i class="fa fa-fw fa-file"></i> <%= filename %></div>
+        </div>
+       <% end %>
+       </li>
+     <% end  # if file or directory %>
+   <% end  # file_tree.each %>
+   <%= raw(dirstack.map { |_| "</ul>" }.join("</li>")) %>
+ <% end  # if file_tree %>
 +
 +<% content_for :footer_html do %>
 +<div id="collection-sharing-modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"></div>
 +<% end %>
index 0000000000000000000000000000000000000000,9fc67ac4cdd4744c56e6767637c10820d4b7217c..c26b74c65a3d77bc000638730af8211f0d2ba40e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,105 +1,111 @@@
+ <div class="row row-fill-height">
+   <div class="col-md-6">
+     <div class="panel panel-info">
+       <div class="panel-heading">
+       <h3 class="panel-title">
+           <% default_name = "Collection #{@object.uuid}" %>
+         <% name_html = render_editable_attribute @object, 'name', nil, {data: {emptytext: default_name}} %>
+           <%= (/\S/.match(name_html)) ? name_html : default_name %>
+       </h3>
+       </div>
+       <div class="panel-body">
+         <img src="/favicon.ico" class="pull-right" alt="" style="opacity: 0.3"/>
+         <% if not (@output_of.andand.any? or @log_of.andand.any?) %>
+           <p><i>No source information available.</i></p>
+         <% end %>
+         <% if @output_of.andand.any? %>
+           <p>Output of jobs:<br />
+           <%= render_arvados_object_list_start(@output_of, 'Show all jobs',
+                 jobs_path(filter: [['output', '=', @object.uuid]].to_json)) do |job| %>
+           <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+           <% end %>
+           </p>
+         <% end %>
+         <% if @log_of.andand.any? %>
+           <p>Log of jobs:<br />
+           <%= render_arvados_object_list_start(@log_of, 'Show all jobs',
+                 jobs_path(filter: [['log', '=', @object.uuid]].to_json)) do |job| %>
+           <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+           <% end %>
+           </p>
+         <% end %>
+       </div>
+     </div>
+   </div>
+   <div class="col-md-3">
+     <div class="panel panel-default">
+       <div class="panel-heading">
+       <h3 class="panel-title">
+         Activity
+       </h3>
+       </div>
+       <div class="panel-body smaller-text">
+         <!--
+       <input type="text" class="form-control" placeholder="Search"/>
+         -->
+       <div style="height:0.5em;"></div>
+         <% if not @logs.andand.any? %>
+           <p>
+             Created: <%= @object.created_at.to_s(:long) %>
+           </p>
+           <p>
+             Last modified: <%= @object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object @object.modified_by_user_uuid, friendly_name: true %>
+           </p>
+         <% else %>
+           <%= render_arvados_object_list_start(@logs, 'Show all activity',
+                 logs_path(filters: [['object_uuid','=',@object.uuid]].to_json)) do |log| %>
+           <p>
+           <%= time_ago_in_words(log.event_at) %> ago: <%= log.summary %>
+             <% if log.object_uuid %>
+             <%= link_to_if_arvados_object log.object_uuid, link_text: raw('<i class="fa fa-hand-o-right"></i>') %>
+             <% end %>
+           </p>
+           <% end %>
+         <% end %>
+       </div>
+     </div>
+   </div>
+   <div class="col-md-3">
+     <div class="panel panel-default">
+       <div class="panel-heading">
+       <h3 class="panel-title">
+         Sharing and permissions
+       </h3>
+       </div>
+       <div class="panel-body">
+         <!--
+       <input type="text" class="form-control" placeholder="Search"/>
+         -->
++
++        <div id="sharing-button" style="text-align: center">
++          <%= render partial: 'sharing_button' %>
++        </div>
++
+       <div style="height:0.5em;"></div>
+         <% if @folders.andand.any? %>
+           <p>Included in folders:<br />
+           <%= render_arvados_object_list_start(@folders, 'Show all folders',
+                 links_path(filter: [['head_uuid', '=', @object.uuid],
+                                     ['link_class', '=', 'name']].to_json)) do |folder| %>
+           <%= link_to_if_arvados_object(folder, friendly_name: true) %><br />
+           <% end %>
+           </p>
+         <% end %>
+         <% if @permissions.andand.any? %>
+           <p>Readable by:<br />
+           <%= render_arvados_object_list_start(@permissions, 'Show all permissions',
+                 links_path(filter: [['head_uuid', '=', @object.uuid],
+                                     ['link_class', '=', 'permission']].to_json)) do |link| %>
+           <%= link_to_if_arvados_object(link.tail_uuid, friendly_name: true) %><br />
+           <% end %>
+           </p>
+         <% end %>
++
+       </div>
+     </div>
+   </div>
+ </div>
+ <%= render file: 'application/show.html.erb' %>
index 43eb7ae6288045d88296387bc5ffbcc2e258bd23,c12cc989d71cd182ac16003909c67e21cecfa04d..6e3d66b86b252a0339b89561c5b8ac5f2e04c974
@@@ -39,13 -40,13 +40,16 @@@ ArvadosWorkbench::Application.routes.dr
      get 'compare', on: :collection
    end
    resources :links
-   match '/collections/graph' => 'collections#graph'
+   get '/collections/graph' => 'collections#graph'
    resources :collections do
      post 'set_persistent', on: :member
 +    get 'sharing_popup', :on => :member
 +    post 'share', :on => :member
 +    post 'unshare', :on => :member
    end
+   get('/collections/download/:uuid/:reader_token/*file' => 'collections#show_file',
+       format: false)
+   get '/collections/download/:uuid/:reader_token' => 'collections#show_file_links'
    get '/collections/:uuid/*file' => 'collections#show_file', :format => false
    resources :folders do
      match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
index 290e1564786f0920db6e7771df4a1b5d6e4331b6,bfd228e5e0441725676dd3c720cd62149e555fca..adff09d53c45de9ebf03593418723b822b7f81bc
@@@ -187,26 -187,15 +187,17 @@@ class ArvadosModel < ActiveRecord::Bas
  
    def ensure_owner_uuid_is_permitted
      raise PermissionDeniedError if !current_user
-     if self.respond_to? :owner_uuid=
+     if respond_to? :owner_uuid=
        self.owner_uuid ||= current_user.uuid
-       if self.owner_uuid_changed?
-         if current_user.uuid == self.owner_uuid or
-             current_user.can? write: self.owner_uuid
-           # current_user is, or has :write permission on, the new owner
-         else
-           logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
-           raise PermissionDeniedError
-         end
-       end
+     end
+     if self.owner_uuid_changed?
 -      if current_user.uuid == self.owner_uuid or
 +      if new_record?
 +        return true
-       elsif current_user.uuid == self.owner_uuid_was or
-           current_user.uuid == self.uuid or
-           current_user.can? write: self.owner_uuid_was
-         # current user is, or has :write permission on, the previous owner
-         return true
++      elsif current_user.uuid == self.owner_uuid or
+           current_user.can? write: self.owner_uuid
+         # current_user is, or has :write permission on, the new owner
        else
 -        logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
 +        logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{self.uuid} but does not have permission to write #{self.owner_uuid_was}"
          raise PermissionDeniedError
        end
      end