[ARVADOS] updated: 50efff371741949e377f8f9c11b724b1981f373a

git at public.curoverse.com git at public.curoverse.com
Fri Oct 31 14:38:07 EDT 2014


Summary of changes:
 .../workbench/app/assets/javascripts/filterable.js |  51 ++++++++
 .../app/assets/javascripts/selection.js.erb        |   2 +-
 .../app/controllers/projects_controller.rb         |  35 +++++-
 apps/workbench/app/models/pipeline_instance.rb     |   6 +-
 .../app/views/collections/_show_files.html.erb     |  59 +++++----
 .../projects/_show_jobs_and_pipelines.html.erb     |   1 +
 .../app/views/projects/_show_tab_contents.html.erb |   2 +-
 apps/workbench/config/application.yml.example      |   9 ++
 .../workbench/test/integration/collections_test.rb | 133 ++++++++++++++++++---
 apps/workbench/test/integration/projects_test.rb   |   2 +-
 apps/workbench/test/unit/pipeline_instance_test.rb |  42 +++++--
 .../controllers/arvados/v1/groups_controller.rb    |   2 +-
 services/api/config/application.yml.example        |   1 +
 services/api/test/fixtures/groups.yml              |   4 +-
 services/api/test/fixtures/jobs.yml                |  10 +-
 services/api/test/fixtures/pipeline_instances.yml  |   4 +-
 services/fuse/tests/test_mount.py                  |   2 +
 services/nodemanager/arvnodeman/jobqueue.py        |   2 +-
 services/nodemanager/tests/test_computenode.py     |   3 +-
 services/nodemanager/tests/test_jobqueue.py        |   5 +
 20 files changed, 298 insertions(+), 77 deletions(-)

       via  50efff371741949e377f8f9c11b724b1981f373a (commit)
       via  540680b267cb67d5128fbf9fc2666bdf864a0801 (commit)
       via  fd0ca9211847806adb2e97c0ae78e9312ef89ca1 (commit)
       via  e2fe6c0e5c1c62a37e03519590c04a5186a2cc9b (commit)
       via  c105f3566ef117227dae65396425618c586d9e10 (commit)
       via  78fc6d80639659e30e4f15562c382c002ed6e1ef (commit)
       via  7d8d1b78a10a7045d7ef2367a45c07b023272548 (commit)
       via  9ee4a125d1796dd7b71ef1b9f255133e5cf0becc (commit)
       via  5560cdf37024ba98bb0367a65bc2176c1577496b (commit)
       via  c19bb2a3554f8bcb17cadd9c133b6fb260e70513 (commit)
       via  89ba1124c9fd6d3167c50059dc8adea64e6147b5 (commit)
       via  2d244defaaa4f5f663a5ac11cf507d7203f704e3 (commit)
       via  fb2ee4033abfae93e8f9a9af367569e7f4fa3793 (commit)
       via  cbc29982e30fd776c194c47dc584710ff1b340c4 (commit)
       via  97f3db9cb084efce35ef6a24c25d14308785a49a (commit)
       via  e1020f4ad01b6f583a9d3c2bf6d146cd9e0d9331 (commit)
       via  337a0bdb43f518fbf716ed5ecd66eb5e569b8fdf (commit)
       via  c330d58918b7c631307ec11bd584824bf0da7f47 (commit)
       via  3a47eb915b3865bea6d2e6536c7bc1f4a1afc8b4 (commit)
       via  4e148ec9b5db231b22f8d9bd04527023fdbc23ba (commit)
       via  2c16f4fbf9408bb758a0f54cae4058dccef4c2a5 (commit)
       via  c1efd42bfbb5bc7ff9ac275643ee7a0c4e44012b (commit)
       via  1bde97d8aa4d670c25015284e97281e1af163dda (commit)
       via  f8dab15791a8f19839f9db529a820b41f10440ae (commit)
      from  1edad4ad7a8c239b36b6f10565ee36cbeab67ddb (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 50efff371741949e377f8f9c11b724b1981f373a
Author: Tim Pierce <twp at curoverse.com>
Date:   Thu Oct 30 10:22:02 2014 -0400

    4088: use filterable.js to filter on client side
    
    Implemented client-side filtering via filterable.js:
    * Designated filename rows as "filterable"
    * Designated filename pattern input field as "filterable-control"
    * Added "Select all" and "Unselect all" buttons with matching
      select_all_files() and unselect_all_files() actions
    * Limit selection actions (compare, move, copy etc) to visible page
      elements only.
    * Added integration tests "Filtering collection files by regexp" and
      "Creating collection from list of filtered files".

diff --git a/apps/workbench/app/assets/javascripts/selection.js.erb b/apps/workbench/app/assets/javascripts/selection.js.erb
index 59e6425..ca4dce3 100644
--- a/apps/workbench/app/assets/javascripts/selection.js.erb
+++ b/apps/workbench/app/assets/javascripts/selection.js.erb
@@ -183,7 +183,7 @@ function dispatch_selection_action() {
     }
     $(this).
         closest('.selection-action-container').
-        find(':checkbox:checked').
+        find(':checkbox:checked:visible').
         each(function() {
             data.push({name: param_name, value: $(this).val()});
         });
diff --git a/apps/workbench/app/views/collections/_show_files.html.erb b/apps/workbench/app/views/collections/_show_files.html.erb
index 9cd77b0..6f26bda 100644
--- a/apps/workbench/app/views/collections/_show_files.html.erb
+++ b/apps/workbench/app/views/collections/_show_files.html.erb
@@ -1,3 +1,24 @@
+<script>
+// The "each" loop in select_all_files() and unselect_all_files()
+// is needed because .trigger("change") does not play well with clippy.
+// Once clippy has been retired, we should be able to compress this
+// into .filter(":visible").prop("checked", true).trigger("change").
+//
+function select_all_files() {
+  $("#collection_files :checkbox").filter(":visible").each(
+    function() {
+      $(this).prop("checked", true).trigger("change");
+    });
+}
+
+function unselect_all_files() {
+  $("#collection_files :checkbox").filter(":visible").each(
+    function() {
+      $(this).prop("checked", false).trigger("change");
+    });
+}
+</script>
+
 <div class="selection-action-container" style="padding-left: 1em">
   <% if !defined? no_checkboxes or !no_checkboxes %>
   <div class="row">
@@ -15,50 +36,24 @@
                   'data-toggle' => 'dropdown'
             %></li>
         </ul>
+	<button id="select-all" type="button" class="btn btn-default" onClick="select_all_files()">Select all</button>
+	<button id="unselect-all" type="button" class="btn btn-default" onClick="unselect_all_files()">Unselect all</button>
       </div>
     </div>
-    <div class="pull-right col-lg-3">
-      <%= form_tag collection_path(@object.uuid), {method: 'get'} do %>
-        <div class="input-group">
-          <input class="form-control" id="file_regex" name="file_regex" placeholder="regular expression" value="<%= params[:file_regex] %>" type="text"/>
-          <span class="input-group-btn">
-            <button id="file_regex_submit" type="submit" class="btn btn-primary" autofocus>Filter</button>
-          </span>
-        </div>
-      <% end %>
+    <div class="pull-right">
+      <input class="form-control filterable-control" data-filterable-target="ul#collection_files" id="file_regex" name="file_regex" placeholder="filename filter" type="text"/>
     </div>
   </div>
   <p/>
   <% end %>
 
-<% file_regex = nil %>
-<% if params[:file_regex] %>
-  <% begin %>
-    <% file_regex = Regexp.new(params[:file_regex]) %>
-  <% rescue RegexpError %>
-    <% # If the pattern is not a valid regex, quote it %>
-    <% # (i.e. use it as a simple substring search) %>
-    <div class="alert alert-info">
-      <p>The search term <code><%= params[:file_regex] %></code> could not be parsed as a regular expression.</p>
-      <p>Searching for files named <code><%= params[:file_regex] %></code> instead.</p>
-    </div>
-    <% file_regex = Regexp.new(Regexp.quote(params[:file_regex])) %>
-  <% end %>
-<% end %>
-
 <% file_tree = @object.andand.files_tree %>
 <% if file_tree.nil? or file_tree.empty? %>
   <p>This collection is empty.</p>
 <% else %>
   <ul id="collection_files" class="collection_files">
   <% dirstack = [file_tree.first.first] %>
-  <% file_tree.reject { |(dirname, filename, size)|
-       # Eliminate any files that don't match file_regex
-       # (or accept all files if no file_regex was given)
-       size and file_regex and !file_regex.match(filename)
-       }
-       .take(10000)
-       .each_with_index do |(dirname, filename, size), index| %>
+  <% file_tree.take(10000).each_with_index do |(dirname, filename, size), index| %>
     <% file_path = CollectionsHelper::file_path([dirname, filename]) %>
     <% while dirstack.any? and (dirstack.last != dirname) %>
       <% dirstack.pop %></ul></li>
@@ -73,7 +68,7 @@
     <% else %>
       <% link_params = {controller: 'collections', action: 'show_file',
                         uuid: @object.portable_data_hash, file: file_path, size: size} %>
-       <div class="collection_files_row">
+       <div class="collection_files_row filterable">
         <div class="collection_files_buttons pull-right">
           <%= raw(human_readable_bytes_html(size)) %>
           <% disable_search = (Rails.configuration.filename_suffixes_with_view_icon.include? file_path.split('.')[-1]) ? false : true %>
diff --git a/apps/workbench/test/integration/collections_test.rb b/apps/workbench/test/integration/collections_test.rb
index 3abbf6f..68ab0ab 100644
--- a/apps/workbench/test/integration/collections_test.rb
+++ b/apps/workbench/test/integration/collections_test.rb
@@ -202,26 +202,26 @@ class CollectionsTest < ActionDispatch::IntegrationTest
   end
 
   test "Filtering collection files by regexp" do
+    headless = Headless.new
+    headless.start
+    Capybara.current_driver = :selenium
     col = api_fixture('collections', 'multilevel_collection_1')
     visit page_with_token('active', "/collections/#{col['uuid']}")
 
-    # Test when only some files match the regex
+    # Filter file list to some but not all files in the collection
     page.find_field('file_regex').set('file[12]')
-    find('button#file_regex_submit').click
     assert page.has_text?("file1")
     assert page.has_text?("file2")
     assert page.has_no_text?("file3")
 
-    # Test all files matching the regex
-    page.find_field('file_regex').set('file[123]')
-    find('button#file_regex_submit').click
+    # Filter file list with a regex matching all files
+    page.find_field('file_regex').set('.*')
     assert page.has_text?("file1")
     assert page.has_text?("file2")
     assert page.has_text?("file3")
 
-    # Test no files matching the regex
+    # Filter file list to a regex matching no files
     page.find_field('file_regex').set('file9')
-    find('button#file_regex_submit').click
     assert page.has_no_text?("file1")
     assert page.has_no_text?("file2")
     assert page.has_no_text?("file3")
@@ -230,13 +230,118 @@ class CollectionsTest < ActionDispatch::IntegrationTest
     assert page.has_text?("multilevel_collection_1")
     assert page.has_text?(col['portable_data_hash'])
 
-    # Syntactically invalid regex
-    # Page loads, but does not match any files
+    # Set filename filter to a syntactically invalid regex
+    # Page loads, but stops filtering after the last valid regex parse
     page.find_field('file_regex').set('file[2')
-    find('button#file_regex_submit').click
-    assert page.has_text?('could not be parsed as a regular expression')
-    assert page.has_no_text?("file1")
-    assert page.has_no_text?("file2")
-    assert page.has_no_text?("file3")
+    assert page.has_text?("multilevel_collection_1")
+    assert page.has_text?(col['portable_data_hash'])
+    assert page.has_text?("file1")
+    assert page.has_text?("file2")
+    assert page.has_text?("file3")
+
+    # Test the "Select all" button
+
+    # Note: calling .set('') on a Selenium element is not sufficient
+    # to reset the field for this test, as it does not send any key
+    # events to the browser. To clear the field, we must instead send
+    # a backspace character.
+    # See https://selenium.googlecode.com/svn/trunk/docs/api/rb/Selenium/WebDriver/Element.html#clear-instance_method
+    page.find_field('file_regex').set("\b") # backspace
+    find('button#select-all').click
+    page.all('input[type=checkbox]').each do |checkbox|
+      assert checkbox.checked?
+    end
+
+    # Test the "Unselect all" button
+    page.find_field('file_regex').set("\b") # backspace
+    find('button#unselect-all').click
+    page.all('input[type=checkbox]').each do |checkbox|
+      refute checkbox.checked?
+    end
+
+    # Filter files, then "select all", then unfilter
+    page.find_field('file_regex').set("\b") # backspace
+    find('button#unselect-all').click
+    page.find_field('file_regex').set('file[12]')
+    find('button#select-all').click
+    page.find_field('file_regex').set("\b") # backspace
+
+    # all "file1" and "file2" checkboxes must be selected
+    # all "file3" checkboxes must be clear
+    assert page.has_selector?('[value*="file1"]')
+    page.all('[value*="file1"]').each do |checkbox|
+      assert checkbox.checked?, 'checkboxes for file1 should be selected after filtering'
+    end
+    assert page.has_selector?('[value*="file2"]')
+    page.all('[value*="file2"]').each do |checkbox|
+      assert checkbox.checked?, 'checkboxes for file2 should be selected after filtering'
+    end
+    assert page.has_selector?('[value*="file3"]')
+    page.all('[value*="file3"]').each do |checkbox|
+      refute checkbox.checked?, 'checkboxes for file3 should be clear after filtering'
+    end
+
+    # Select all files, then filter, then "unselect all", then unfilter
+    page.find_field('file_regex').set("\b") # backspace
+    find('button#select-all').click
+    page.find_field('file_regex').set('file[12]')
+    find('button#unselect-all').click
+    page.find_field('file_regex').set("\b") # backspace
+
+    # all "file1" and "file2" checkboxes must be clear
+    # all "file3" checkboxes must be selected
+    assert page.has_selector?('[value*="file1"]')
+    page.all('[value*="file1"]').each do |checkbox|
+      refute checkbox.checked?, 'checkboxes for file1 should be clear after filtering'
+    end
+    assert page.has_selector?('[value*="file2"]')
+    page.all('[value*="file2"]').each do |checkbox|
+      refute checkbox.checked?, 'checkboxes for file2 should be clear after filtering'
+    end
+    assert page.has_selector?('[value*="file3"]')
+    page.all('[value*="file3"]').each do |checkbox|
+      assert checkbox.checked?, 'checkboxes for file3 should be selected after filtering'
+    end
+  end
+
+  test "Creating collection from list of filtered files" do
+    headless = Headless.new
+    headless.start
+    Capybara.current_driver = :selenium
+
+    col = api_fixture('collections', 'collection_with_files_in_subdir')
+    visit page_with_token('user1_with_load', "/collections/#{col['uuid']}")
+    assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not found'
+    assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not found'
+    assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not found'
+    assert page.has_text?('file1_in_subdir4'), 'expected file1_in_subdir4 not found'
+    assert page.has_text?('file2_in_subdir4'), 'expected file2_in_subdir4 not found'
+
+    # Select all files but then filter them to files in subdir1, subdir2 or subdir3
+    find('button#select-all').click
+    page.find_field('file_regex').set('_in_subdir[123]')
+    assert page.has_text?('file_in_subdir1'), 'expected file_in_subdir1 not in filtered files'
+    assert page.has_text?('file1_in_subdir3'), 'expected file1_in_subdir3 not in filtered files'
+    assert page.has_text?('file2_in_subdir3'), 'expected file2_in_subdir3 not in filtered files'
+    refute page.has_text?('file1_in_subdir4'), 'file1_in_subdir4 found in filtered files'
+    refute page.has_text?('file2_in_subdir4'), 'file2_in_subdir4 found in filtered files'
+
+    # Create a new collection
+    click_button 'Selection...'
+    within('.selection-action-container') do
+      click_link 'Create new collection with selected files'
+    end
+
+    # now in the newly created collection page
+    assert page.has_text?('Content hash:'), 'not on new collection page'
+    refute page.has_text?(col['uuid']), 'new collection page has old collection uuid'
+    refute page.has_text?(col['portable_data_hash']), 'new collection page has old portable_data_hash'
+
+    # must have files in subdir1 and subdir3 but not subdir4
+    assert page.has_text?('file_in_subdir1'), 'file_in_subdir1 missing from new collection'
+    assert page.has_text?('file1_in_subdir3'), 'file1_in_subdir3 missing from new collection'
+    assert page.has_text?('file2_in_subdir3'), 'file2_in_subdir3 missing from new collection'
+    refute page.has_text?('file1_in_subdir4'), 'file1_in_subdir4 found in new collection'
+    refute page.has_text?('file2_in_subdir4'), 'file2_in_subdir4 found in new collection'
   end
 end

commit 540680b267cb67d5128fbf9fc2666bdf864a0801
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Oct 30 13:43:34 2014 -0400

    4088: Add usage docs to filterable.js

diff --git a/apps/workbench/app/assets/javascripts/filterable.js b/apps/workbench/app/assets/javascripts/filterable.js
index d14551c..8ac1953 100644
--- a/apps/workbench/app/assets/javascripts/filterable.js
+++ b/apps/workbench/app/assets/javascripts/filterable.js
@@ -1,3 +1,54 @@
+// filterable.js shows/hides content when the user operates
+// search/select widgets. For "infinite scroll" content, it passes the
+// filters to the server and retrieves new content. For other content,
+// it filters the existing DOM elements using jQuery show/hide.
+//
+// Usage:
+//
+// 1. Add the "filterable" class to each filterable content item.
+// Typically, each item is a 'tr' or a 'div class="row"'.
+//
+// <div id="results">
+//   <div class="filterable row">First row</div>
+//   <div class="filterable row">Second row</div>
+// </div>
+//
+// 2. Add the "filterable-control" class to each search/select widget.
+// Also add a data-filterable-target attribute with a jQuery selector
+// for an ancestor of the filterable items, i.e., the container in
+// which this widget should apply filtering.
+//
+// <input class="filterable-control" data-filterable-target="#results"
+//        type="text" />
+//
+// Supported widgets:
+//
+// <input type="text" ... />
+//
+// The input value is used as a regular expression. Rows with content
+// matching the regular expression are shown.
+//
+// <select ... data-filterable-attribute="data-example-attr">
+//  <option value="foo">Foo</option>
+//  <option value="">Show all</option>
+// </select>
+//
+// When the user selects the "Foo" option, rows with
+// data-example-attr="foo" are shown, and all others are hidden. When
+// the user selects the "Show all" option, all rows are shown.
+//
+// Notes:
+//
+// When multiple filterable-control widgets operate on the same
+// data-filterable-target, items must pass _all_ filters in order to
+// be shown.
+//
+// If one data-filterable-target is the parent of another
+// data-filterable-target, results are undefined. Don't do this.
+//
+// Combining "select" filterable-controls with infinite-scroll is not
+// yet supported.
+
 $(document).
     on('paste keyup input', 'input[type=text].filterable-control', function() {
         var $target = $($(this).attr('data-filterable-target'));

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list