gem 'rails', '~> 4.1.0'
gem 'minitest', '>= 5.0.0'
-gem 'arvados', '>= 0.1.20140917180103'
+gem 'arvados', '>= 0.1.20141114230720'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
addressable (2.3.6)
andand (1.3.3)
arel (5.0.1.20140414130214)
- arvados (0.1.20140917180103)
+ arvados (0.1.20141114230720)
activesupport (>= 3.2.13)
- andand
- google-api-client (~> 0.6.3)
- json (>= 1.7.7)
+ andand (~> 1.3, >= 1.3.3)
+ google-api-client (~> 0.6.3, >= 0.6.3)
+ json (~> 1.7, >= 1.7.7)
jwt (>= 0.1.5, < 1.0.0)
autoparse (0.3.3)
addressable (>= 2.3.1)
json (1.8.1)
jwt (0.1.13)
multi_json (>= 1.5)
- launchy (2.4.2)
+ launchy (2.4.3)
addressable (~> 2.3)
less (2.4.0)
commonjs (~> 0.2.7)
DEPENDENCIES
RedCloth
andand
- arvados (>= 0.1.20140917180103)
+ arvados (>= 0.1.20141114230720)
bootstrap-sass (~> 3.1.0)
bootstrap-tab-history-rails
bootstrap-x-editable-rails
// Combining "select" filterable-controls with infinite-scroll is not
// yet supported.
+function updateFilterableQueryNow($target) {
+ var newquery = $target.data('filterable-query-new');
+ var params = $target.data('infinite-content-params-filterable') || {};
+ params.filters = [['any', 'ilike', '%' + newquery + '%']];
+ $target.data('infinite-content-params-filterable', params);
+ $target.data('filterable-query', newquery);
+}
+
$(document).
- on('paste keyup input', 'input[type=text].filterable-control', function() {
+ on('ready ajax:success', function() {
+ // Copy any initial input values into
+ // data-filterable-query[-new].
+ $('input[type=text].filterable-control').each(function() {
+ var $this = $(this);
+ var $target = $($this.attr('data-filterable-target'));
+ if ($target.data('filterable-query-new') === undefined) {
+ $target.data('filterable-query', $this.val());
+ $target.data('filterable-query-new', $this.val());
+ updateFilterableQueryNow($target);
+ }
+ });
+ $('[data-infinite-scroller]').on('refresh-content', '[data-filterable-query]', function(e) {
+ // If some other event causes a refresh-content event while there
+ // is a new query waiting to cooloff, we should use the new query
+ // right away -- otherwise we'd launch an extra ajax request that
+ // would have to be reloaded as soon as the cooloff period ends.
+ if (this != e.target)
+ return;
+ if ($(this).data('filterable-query') == $(this).data('filterable-query-new'))
+ return;
+ updateFilterableQueryNow($(this));
+ });
+ }).
+ on('paste keyup input', 'input[type=text].filterable-control', function(e) {
+ if (this != e.target) return;
var $target = $($(this).attr('data-filterable-target'));
var currentquery = $target.data('filterable-query');
if (currentquery === undefined) currentquery = '';
// in the next 1/4 second (like type or erase
// characters in the search box), hide the stale
// content and ask the server for new results.
- var newquery = $target.data('filterable-query-new');
- var params = $target.data('infinite-content-params-filterable') || {};
- params.filters = [['any', 'ilike', '%' + newquery + '%']];
- $target.data('infinite-content-params-filterable', params);
- $target.data('filterable-query', newquery);
+ updateFilterableQueryNow($target);
$target.trigger('refresh-content');
}, 250));
} else {
function maybe_load_more_content(event) {
- var scroller = this; // element with scroll bars
- var $container; // element that receives new content
- var src; // url for retrieving content
+ var scroller = this;
+ var $container = $(event.data.container);
+ var src; // url for retrieving content
var scrollHeight;
var spinner, colspan;
var serial = Date.now();
>
scrollHeight - 50)
{
- $container = $(event.data.container);
if (!$container.attr('data-infinite-content-href0')) {
// Remember the first page source url, so we can refresh
// from page 1 later.
function run_pipeline_button_state() {
- var a = $('a.editable.required.editable-empty,input.form-control.required[value=]');
+ var a = $('a.editable.required.editable-empty,input.form-control.required[value=""]');
if (a.length > 0) {
$(".run-pipeline-button").addClass("disabled");
}
def find_objects_for_index
@objects ||= model_class
@objects = @objects.filter(@filters).limit(@limit).offset(@offset)
+ @objects.fetch_multiple_pages(false)
end
def render_index
if params[:partial]
@next_page_href = next_page_href(partial: params[:partial], filters: @filters.to_json)
render json: {
- content: render_to_string(partial: "show_#{params[:partial]}", formats: [:html]),
- next_page_href: @next_page_href
-
+ content: render_to_string(partial: "show_#{params[:partial]}",
+ formats: [:html]),
+ next_page_href: @next_page_href
}
else
render json: @objects
if !objects
objects = @objects
end
+ # result_limit and result_offset won't work until we call #results
+ objects.results
if objects.respond_to?(:result_offset) and
objects.respond_to?(:result_limit) and
objects.respond_to?(:items_available)
if params[:partial]
f.json {
find_objects_for_index if !@objects
- @objects.fetch_multiple_pages(false)
render json: {
content: render_to_string(partial: "choose_rows.html",
formats: [:html]),
%w(Compare Graph)
end
- def index
- if params[:search].andand.length.andand > 0
- @select ||= PipelineInstance.columns.map(&:name)
- base_search = PipelineInstance.select(@select)
- @objects = base_search.where(any: ['contains', params[:search]]).
- uniq { |pi| pi.uuid }
- end
-
- @limit = 20
- super
- end
-
protected
def for_comparison v
if v.is_a? Hash or v.is_a? Array
end
end
+ def load_filters_and_paging_params
+ params[:limit] = 20
+ super
+ end
+
def find_objects_by_uuid
@objects = model_class.where(uuid: params[:uuids])
end
-
end
include ArvadosApiClientHelper
include Enumerable
+ attr_reader :resource_class
+
def initialize resource_class=nil
@resource_class = resource_class
@fetch_multiple_pages = true
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h4 class="modal-title"><%= params[:title] || "Choose #{@objects.first.andand.class_for_display}" %></h4>
+ <h4 class="modal-title"><%= params[:title] || "Choose #{@objects.resource_class.andand.class_for_display}" %></h4>
</div>
<div class="modal-body">
}
</style>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
-<%= piwik_tracking_tag %>
+ <%= piwik_tracking_tag if (PiwikAnalytics.configuration.url != 'localhost' rescue false) %>
</head>
<body>
<%= render template: 'layouts/body' %>
<div class="input-group">
<input type="text" class="form-control filterable-control recent-pipeline-instances-filterable-control"
placeholder="Search pipeline instances"
- data-filterable-target="#recent-pipeline-instances"/>
+ data-filterable-target="#recent-pipeline-instances"
+ <%# Just for the double-load test in FilterableInfiniteScrollTest: %>
+ value="<%= params[:search] %>"
+ />
</div>
<%= form_tag({action: 'compare', controller: params[:controller], method: 'get'}, {method: 'get', id: 'compare', class: 'pull-right small-form-margin'}) do |f| %>
end
end
- test "component rendering copes with unexpeceted components format" do
+ test "component rendering copes with unexpected components format" do
get(:show,
{id: api_fixture("pipeline_instances")["components_is_jobspec"]["uuid"]},
session_for(:active))
--- /dev/null
+require 'integration_helper'
+
+class FilterableInfiniteScrollTest < ActionDispatch::IntegrationTest
+ setup do
+ headless = Headless.new
+ headless.start
+ Capybara.current_driver = :selenium
+ end
+
+ # Chrome remembers what you had in the text field when you hit
+ # "back". Here, we simulate the same effect by sending an otherwise
+ # unused ?search=foo param to pre-populate the search field.
+ test 'no double-load if text input has a value at page load time' do
+ visit page_with_token('admin', '/pipeline_instances')
+ assert_text 'pipeline_2'
+ visit page_with_token('admin', '/pipeline_instances?search=pipeline_1')
+ # Horrible hack to ensure the search results can't load correctly
+ # on the second attempt.
+ assert_selector '#recent-pipeline-instances'
+ assert page.evaluate_script('$("#recent-pipeline-instances[data-infinite-content-href0]").attr("data-infinite-content-href0","/give-me-an-error").length == 1')
+ # Wait for the first page of results to appear.
+ assert_text 'pipeline_1'
+ # Make sure the results are filtered.
+ assert_no_text 'pipeline_2'
+ # Make sure pipeline_2 didn't disappear merely because the results
+ # were replaced with an error message.
+ assert_text 'pipeline_1'
+ end
+end
s.files = ["lib/arvados.rb", "lib/arvados/keep.rb"]
s.required_ruby_version = '>= 2.1.0'
s.add_dependency('google-api-client', '~> 0.6.3', '>= 0.6.3')
- s.add_dependency('activesupport', '~> 3.2', '>= 3.2.13')
+ s.add_dependency('activesupport', '>= 3.2.13')
s.add_dependency('json', '~> 1.7', '>= 1.7.7')
s.add_dependency('andand', '~> 1.3', '>= 1.3.3')
s.add_runtime_dependency('jwt', '>= 0.1.5', '< 1.0.0')