end
expose_action :combine_selected_files_into_collection do
- link_uuids, coll_ids = params["selection"].partition do |sel_s|
- ArvadosBase::resource_class_for_uuid(sel_s) == Link
- end
-
- unless link_uuids.empty?
- Link.select([:head_uuid]).where(uuid: link_uuids).each do |link|
- if ArvadosBase::resource_class_for_uuid(link.head_uuid) == Collection
- coll_ids << link.head_uuid
- end
- end
- end
-
- uuids = []
- pdhs = []
- source_paths = Hash.new { |hash, key| hash[key] = [] }
- coll_ids.each do |coll_id|
- if m = CollectionsHelper.match(coll_id)
- key = m[1] + m[2]
- pdhs << key
- source_paths[key] << m[4]
- elsif m = CollectionsHelper.match_uuid_with_optional_filepath(coll_id)
- key = m[1]
- uuids << key
- source_paths[key] << m[4]
- end
- end
-
- unless pdhs.empty?
- Collection.where(portable_data_hash: pdhs.uniq).
- select([:uuid, :portable_data_hash]).each do |coll|
- unless source_paths[coll.portable_data_hash].empty?
- uuids << coll.uuid
- source_paths[coll.uuid] = source_paths.delete(coll.portable_data_hash)
- end
- end
- end
+ uuids, source_paths = selected_collection_files params
new_coll = Arv::Collection.new
Collection.where(uuid: uuids.uniq).
end
end
+ # helper method to get the names of collection files selected
+ helper_method :selected_collection_files
+ def selected_collection_files params
+ link_uuids, coll_ids = params["selection"].partition do |sel_s|
+ ArvadosBase::resource_class_for_uuid(sel_s) == Link
+ end
+
+ unless link_uuids.empty?
+ Link.select([:head_uuid]).where(uuid: link_uuids).each do |link|
+ if ArvadosBase::resource_class_for_uuid(link.head_uuid) == Collection
+ coll_ids << link.head_uuid
+ end
+ end
+ end
+
+ uuids = []
+ pdhs = []
+ source_paths = Hash.new { |hash, key| hash[key] = [] }
+ coll_ids.each do |coll_id|
+ if m = CollectionsHelper.match(coll_id)
+ key = m[1] + m[2]
+ pdhs << key
+ source_paths[key] << m[4]
+ elsif m = CollectionsHelper.match_uuid_with_optional_filepath(coll_id)
+ key = m[1]
+ uuids << key
+ source_paths[key] << m[4]
+ end
+ end
+
+ unless pdhs.empty?
+ Collection.where(portable_data_hash: pdhs.uniq).
+ select([:uuid, :portable_data_hash]).each do |coll|
+ unless source_paths[coll.portable_data_hash].empty?
+ uuids << coll.uuid
+ source_paths[coll.uuid] = source_paths.delete(coll.portable_data_hash)
+ end
+ end
+ end
+
+ [uuids, source_paths]
+ end
+
def wiselinks_layout
'body'
end
require "arvados/keep"
+require "arvados/collection"
require "uri"
class CollectionsController < ApplicationController
sharing_popup
end
+ def remove_selected_files
+ uuids, source_paths = selected_collection_files params
+
+ arv_coll = Arv::Collection.new(@object.manifest_text)
+ source_paths[uuids[0]].each do |p|
+ arv_coll.rm "."+p
+ end
+
+ if @object.update_attributes manifest_text: arv_coll.manifest_text
+ show
+ else
+ self.render_error status: 422
+ end
+ end
+
+ def update
+ updated_attr = params[:collection].each.select {|a| a[0].andand.start_with? 'rename-file-path:'}
+
+ if updated_attr.size > 0
+ # Is it file rename?
+ file_path = updated_attr[0][0].split('rename-file-path:')[-1]
+
+ new_file_path = updated_attr[0][1]
+ if new_file_path.start_with?('./')
+ # looks good
+ elsif new_file_path.start_with?('/')
+ new_file_path = '.' + new_file_path
+ else
+ new_file_path = './' + new_file_path
+ end
+
+ arv_coll = Arv::Collection.new(@object.manifest_text)
+ arv_coll.rename "./"+file_path, new_file_path
+
+ if @object.update_attributes manifest_text: arv_coll.manifest_text
+ show
+ else
+ self.render_error status: 422
+ end
+ else
+ # Not a file rename; use default
+ super
+ end
+ end
+
protected
def find_usable_token(token_list)
"data-placement" => "bottom",
"data-type" => input_type,
"data-title" => "Edit #{attr.to_s.gsub '_', ' '}",
- "data-name" => attr,
+ "data-name" => htmloptions['selection_name'] || attr,
"data-object-uuid" => object.uuid,
"data-toggle" => "manual",
- "data-value" => attrvalue,
+ "data-value" => htmloptions['data-value'] || attrvalue,
"id" => span_id,
:class => "editable #{is_textile?( object, attr ) ? 'editable-textile' : ''}"
}.merge(htmloptions).merge(ajax_options)
end
def editable_attributes
- %w(name description manifest_text)
+ %w(name description manifest_text filename)
end
def provenance
'data-selection-action' => 'combine-collections',
'data-toggle' => 'dropdown'
%></li>
+ <% if object.editable? %>
+ <li><%= link_to "Remove selected files", '#',
+ method: :post,
+ 'data-href' => url_for(controller: 'collections', action: :remove_selected_files),
+ 'data-selection-param-name' => 'selection[]',
+ 'data-selection-action' => 'remove-selected-files',
+ 'data-toggle' => 'dropdown'
+ %></li>
+ <% end %>
</ul>
</div>
<div class="btn-group btn-group-sm">
} %>
<span> </span>
<% end %>
+
+ <% if object.editable? %>
+ <%= link_to({controller: 'collections', action: 'remove_selected_files', id: object.uuid, selection: [object.portable_data_hash+'/'+file_path]}, method: :post, remote: true, data: {confirm: "Remove #{file_path}?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: "Remove #{file_path}") do %>
+ <i class="fa fa-fw fa-trash-o"></i>
+ <% end %>
+ <% end %>
<% if CollectionsHelper::is_image(filename) %>
- <i class="fa fa-fw fa-bar-chart-o"></i> <%= filename %></div>
+ <i class="fa fa-fw fa-bar-chart-o"></i>
+ <% if object.editable? %>
+ <%= render_editable_attribute object, 'filename', filename, {'data-value' => file_path, 'data-toggle' => 'manual', 'selection_path' => 'rename-file-path:'+file_path}, {tiptitle: 'Edit path of this file (name or directory or both). If you use the same path as another file, it may be removed.'} %>
+ <% else %>
+ <%= filename %>
+ <% end %>
+ </div>
<div class="collection_files_inline">
<%= link_to(image_tag("#{url_for object}/#{file_path}"),
link_params.merge(disposition: 'inline'),
</div>
</div>
<% else %>
- <i class="fa fa-fw fa-file" href="<%=object.uuid%>/<%=file_path%>" ></i> <%= filename %></div>
+ <% if object.editable? %>
+ <i class="fa fa-fw fa-file"></i><%= render_editable_attribute object, 'filename', filename, {'data-value' => file_path, 'data-toggle' => 'manual', 'selection_name' => 'rename-file-path:'+file_path}, {tiptitle: 'Edit path of this file (name or directory or both). If you use the same path as another file, it may be removed.'} %>
+ <% else %>
+ <i class="fa fa-fw fa-file" href="<%=object.uuid%>/<%=file_path%>" ></i> <%= filename %>
+ <% end %>
+ </div>
</div>
<% end %>
</li>
post 'share', :on => :member
post 'unshare', :on => :member
get 'choose', on: :collection
+ post 'remove_selected_files', on: :member
end
get('/collections/download/:uuid/:reader_token/*file' => 'collections#show_file',
format: false)
collection = api_fixture('collections')['foo_file']
get :show, {id: collection['uuid']}, session_for(:active)
assert_includes @response.body, collection['name']
- assert_match /href="#{collection['uuid']}\/foo" ><\/i> foo</, @response.body
+ assert_match /not authorized to manage collection sharing links/, @response.body
end
test "No Upload tab on non-writable collection" do
assert_equal "https://download.example/c=#{id.sub '+', '-'}/_/w%20a%20z?api_token=#{tok}", @response.redirect_url
end
end
+
+ test "remove selected files from collection" do
+ use_token :active
+
+ # create a new collection to test; using existing collections will cause other tests to fail,
+ # and resetting fixtures after each test makes it take almost 4 times to run this test file.
+ manifest_text = ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n./dir1 d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n"
+
+ collection = Collection.create(manifest_text: manifest_text)
+ assert_includes(collection['manifest_text'], "0:0:file1")
+
+ # now remove all files named 'file1' from the collection
+ post :remove_selected_files, {
+ id: collection['uuid'],
+ selection: ["#{collection['uuid']}/file1",
+ "#{collection['uuid']}/dir1/file1"],
+ format: :json
+ }, session_for(:active)
+ assert_response :success
+
+ # verify no 'file1' in the updated collection
+ collection = Collection.select([:uuid, :manifest_text]).where(uuid: collection['uuid']).first
+ assert_not_includes(collection['manifest_text'], "0:0:file1")
+ assert_includes(collection['manifest_text'], "0:0:file2") # but other files still exist
+ end
+
+ test "remove all files from a subdir of a collection" do
+ use_token :active
+
+ # create a new collection to test
+ manifest_text = ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n./dir1 d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n"
+
+ collection = Collection.create(manifest_text: manifest_text)
+ assert_includes(collection['manifest_text'], "0:0:file1")
+
+ # now remove all files from "dir1" subdir of the collection
+ post :remove_selected_files, {
+ id: collection['uuid'],
+ selection: ["#{collection['uuid']}/dir1/file1",
+ "#{collection['uuid']}/dir1/file2"],
+ format: :json
+ }, session_for(:active)
+ assert_response :success
+
+ # verify that "./dir1" no longer exists in this collection's manifest text
+ collection = Collection.select([:uuid, :manifest_text]).where(uuid: collection['uuid']).first
+ assert_match /. d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:file1 0:0:file2\n$/, collection['manifest_text']
+ assert_not_includes(collection['manifest_text'], 'dir1')
+ end
+
+ test "rename file in a collection" do
+ use_token :active
+
+ # create a new collection to test
+ manifest_text = ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n./dir1 d41d8cd98f00b204e9800998ecf8427e+0 0:0:dir1file1 0:0:dir1file2\n"
+
+ collection = Collection.create(manifest_text: manifest_text)
+ assert_includes(collection['manifest_text'], "0:0:file1")
+
+ # rename 'file1' as 'file1renamed' and verify
+ post :update, {
+ id: collection['uuid'],
+ collection: {
+ 'rename-file-path:file1' => 'file1renamed'
+ },
+ format: :json
+ }, session_for(:active)
+ assert_response :success
+
+ collection = Collection.select([:uuid, :manifest_text]).where(uuid: collection['uuid']).first
+ assert_match /. d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:file1renamed 0:0:file2\n.\/dir1 d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:dir1file1 0:0:dir1file2\n$/, collection['manifest_text']
+
+ # now rename 'file2' such that it is moved into 'dir1'
+ @test_counter = 0
+ post :update, {
+ id: collection['uuid'],
+ collection: {
+ 'rename-file-path:file2' => 'dir1/file2'
+ },
+ format: :json
+ }, session_for(:active)
+ assert_response :success
+
+ collection = Collection.select([:uuid, :manifest_text]).where(uuid: collection['uuid']).first
+ assert_match /. d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:file1renamed\n.\/dir1 d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:dir1file1 0:0:dir1file2 0:0:file2\n$/, collection['manifest_text']
+
+ # now rename 'dir1/dir1file1' such that it is moved into a new subdir
+ @test_counter = 0
+ post :update, {
+ id: collection['uuid'],
+ collection: {
+ 'rename-file-path:dir1/dir1file1' => 'dir2/dir3/dir1file1moved'
+ },
+ format: :json
+ }, session_for(:active)
+ assert_response :success
+
+ collection = Collection.select([:uuid, :manifest_text]).where(uuid: collection['uuid']).first
+ assert_match /. d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:file1renamed\n.\/dir1 d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:dir1file2 0:0:file2\n.\/dir2\/dir3 d41d8cd98f00b204e9800998ecf8427e\+0\+A(.*) 0:0:dir1file1moved\n$/, collection['manifest_text']
+ end
end
# Make sure we're not still on the old collection page.
refute_match(%r{/collections/#{col['uuid']}}, page.current_url)
end
+
+ test "remove a file from collection using checkbox and dropdown option" do
+ visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
+ assert(page.has_text?('file1'), 'file not found - file1')
+
+ # remove first file
+ input_files = page.all('input[type=checkbox]')
+ input_files[0].click
+
+ click_button 'Selection...'
+ within('.selection-action-container') do
+ click_link 'Remove selected files'
+ end
+
+ assert(page.has_no_text?('file1'), 'file found - file')
+ assert(page.has_text?('file2'), 'file not found - file2')
+ end
+
+ test "remove a file in collection using trash icon" do
+ need_selenium 'to confirm remove'
+
+ visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
+ assert(page.has_text?('file1'), 'file not found - file1')
+
+ first('.fa-trash-o').click
+ page.driver.browser.switch_to.alert.accept
+
+ assert(page.has_no_text?('file1'), 'file found - file')
+ assert(page.has_text?('file2'), 'file not found - file2')
+ end
+
+ test "rename a file in collection" do
+ visit page_with_token('active', '/collections/zzzzz-4zz18-a21ux3541sxa8sf')
+
+ within('.collection_files') do
+ first('.fa-pencil').click
+ find('.editable-input input').set('file1renamed')
+ find('.editable-submit').click
+ end
+
+ assert(page.has_text?('file1renamed'), 'file not found - file1renamed')
+ end
+
+ test "remove/rename file options not presented if user cannot update a collection" do
+ # visit a publicly accessible collection as 'spectator'
+ visit page_with_token('spectator', '/collections/zzzzz-4zz18-uukreo9rbgwsujr')
+
+ click_button 'Selection'
+ within('.selection-action-container') do
+ assert_selector 'li', text: 'Create new collection with selected files'
+ assert_no_selector 'li', text: 'Remove selected files'
+ end
+
+ within('.collection_files') do
+ assert(page.has_text?('GNU_General_Public_License'), 'file not found - GNU_General_Public_License')
+ assert_nil first('.fa-pencil')
+ assert_nil first('.fa-trash-o')
+ end
+ end
end
manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
name: collection_not_readable_by_active
+collection_to_remove_and_rename_files:
+ uuid: zzzzz-4zz18-a21ux3541sxa8sf
+ portable_data_hash: 80cf6dd2cf079dd13f272ec4245cb4a8+48
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ created_at: 2014-02-03T17:22:54Z
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+ modified_at: 2014-02-03T17:22:54Z
+ updated_at: 2014-02-03T17:22:54Z
+ manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n"
+ name: collection to remove and rename files
+
# Test Helper trims the rest of the file