on('ajax:complete ready', function() {
// See http://getbootstrap.com/javascript/#buttons
$('.btn').button();
- });
-
- $(document).
+ }).
on('ready ajax:complete', function() {
$('[data-toggle~=tooltip]').tooltip({container:'body'});
+ }).
+ on('ready ajax:complete', function() {
+ // This makes the dialog close on Esc key, obviously.
+ $('.modal').attr('tabindex', '-1')
});
HeaderRowFixer = function(selector) {
data('q', q).
trigger('refresh');
}).on('refresh', '.filterable-container', function() {
+ var $container = $(this);
var q = $(this).data('q');
var filters = $(this).data('filters');
$('.filterable', this).hide().filter(function() {
}
return pass;
}).show();
+
+ // Show/hide each section heading depending on whether any
+ // content rows are visible in that section.
+ $('.row[data-section-heading]', this).each(function(){
+ $(this).toggle($('.row.filterable[data-section-name="' +
+ $(this).attr('data-section-name') +
+ '"]:visible').length > 0);
+ });
+
+ // Load more content if the last result is showing.
$('.infinite-scroller').add(window).trigger('scroll');
}).on('change', 'select.filterable-control', function() {
var val = $(this).val();
var src; // url for retrieving content
var scrollHeight;
var spinner, colspan;
+ var serial = Date.now();
scrollHeight = scroller.scrollHeight || $('body')[0].scrollHeight;
var num_scrollers = $(window).data("arv-num-scrollers");
if ($(scroller).scrollTop() + $(scroller).height()
{
for (var i = 0; i < num_scrollers; i++) {
$container = $($(this).data('infinite-container'+i));
- src = $container.attr('data-infinite-content-href');
+ if (!$(container).attr('data-infinite-content-href0')) {
+ // Remember the first page source url, so we can refresh
+ // from page 1 later.
+ $(container).attr('data-infinite-content-href0',
+ $(container).attr('data-infinite-content-href'));
+ }
+ src = $(container).attr('data-infinite-content-href');
if (!src || !$container.is(':visible'))
- continue;
+ // Finished
+ return;
// Don't start another request until this one finishes
$container.attr('data-infinite-content-href', null);
spinner +
'</td></tr>');
}
- $container.append(spinner);
+ $(container).find(".spinner").detach();
+ $(container).append(spinner);
+ $(container).attr('data-infinite-serial', serial);
$.ajax(src,
{dataType: 'json',
type: 'GET',
- data: {},
- context: {container: $container, src: src}}).
- always(function() {
- $(this.container).find(".spinner").detach();
- }).
+ data: ($(container).data('infinite-content-params') || {}),
+ context: {container: container, src: src, serial: serial}}).
fail(function(jqxhr, status, error) {
+ var $faildiv;
+ if ($(this.container).attr('data-infinite-serial') != this.serial) {
+ // A newer request is already in progress.
+ return;
+ }
if (jqxhr.readyState == 0 || jqxhr.status == 0) {
message = "Cancelled."
} else if (jqxhr.responseJSON && jqxhr.responseJSON.errors) {
} else {
message = "Request failed.";
}
- // TODO: report this to the user.
+ // TODO: report the message to the user.
console.log(message);
- $(this.container).attr('data-infinite-content-href', this.src);
+ $faildiv = $('<div />').
+ attr('data-infinite-content-href', this.src).
+ addClass('infinite-retry').
+ append('<span class="fa fa-warning" /> Oops, request failed. <button class="btn btn-xs btn-primary">Retry</button>');
+ $(this.container).find('div.spinner').replaceWith($faildiv);
}).
done(function(data, status, jqxhr) {
+ if ($(this.container).attr('data-infinite-serial') != this.serial) {
+ // A newer request is already in progress.
+ return;
+ }
+ $(this.container).find(".spinner").detach();
$(this.container).append(data.content);
$(this.container).attr('data-infinite-content-href', data.next_page_href);
});
}
$(document).
+ on('click', 'div.infinite-retry button', function() {
+ var $retry_div = $(this).closest('.infinite-retry');
+ var $scroller = $(this).closest('.infinite-scroller')
+ $scroller.attr('data-infinite-content-href',
+ $retry_div.attr('data-infinite-content-href'));
+ $retry_div.replaceWith('<div class="spinner spinner-32px spinner-h-center" />');
+ $scroller.trigger('scroll');
+ }).
+ on('refresh-content', '[data-infinite-scroller]', function() {
+ // Clear all rows, reset source href to initial state, and
+ // (if the container is visible) start loading content.
+ var first_page_href = $(this).attr('data-infinite-content-href0');
+ if (!first_page_href)
+ first_page_href = $(this).attr('data-infinite-content-href');
+ $(this).
+ html('').
+ attr('data-infinite-content-href', first_page_href);
+ $('.infinite-scroller').
+ trigger('scroll');
+ }).
on('ready ajax:complete', function() {
var num_scrollers = 0;
$('[data-infinite-scroller]').each(function() {
$(document).on('click', '.selectable', function() {
var any;
var $this = $(this);
- if (!$this.hasClass('multiple')) {
- $this.closest('.selectable-container').
+ var $container = $(this).closest('.selectable-container');
+ if (!$container.hasClass('multiple')) {
+ $container.
find('.selectable').
removeClass('active');
}
$this.toggleClass('active');
- any = ($this.
- closest('.selectable-container').
+ any = ($container.
find('.selectable.active').length > 0)
$this.
closest('.modal').
prop('disabled', !any);
if ($this.hasClass('active')) {
+ var no_preview_available = '<div class="spinner-h-center spinner-v-center"><center>(No preview available)</center></div>';
+ if (!$this.attr('data-preview-href')) {
+ $(".modal-dialog-preview-pane").html(no_preview_available);
+ return;
+ }
$(".modal-dialog-preview-pane").html('<div class="spinner spinner-32px spinner-h-center spinner-v-center"></div>');
$.ajax($this.attr('data-preview-href'),
{dataType: "html"}).
- done(function(data, status, jqxhr) {
+ done(function(data, status, jqxhr) {
$(".modal-dialog-preview-pane").html(data);
}).
fail(function(data, status, jqxhr) {
- $(".modal-dialog-preview-pane").text('Preview load failed.');
+ $(".modal-dialog-preview-pane").html(no_preview_available);
});
}
$(document).trigger(event_name!=null ? event_name : 'page-refresh',
[data, status, jqxhr, this.action_data]);
});
+}).on('click', '.chooser-show-project', function() {
+ var params = {};
+ $(this).attr('href', '#'); // Skip normal click handler
+ if ($(this).attr('data-project-uuid')) {
+ params = {'filters[]': JSON.stringify(['owner_uuid',
+ '=',
+ $(this).attr('data-project-uuid')])};
+ }
+ $($(this).closest('[data-filterable-target]').attr('data-filterable-target')).
+ data('infinite-content-params', params).
+ trigger('refresh-content');
});
$(document).on('page-refresh', function(event, data, status, jqxhr, action_data) {
window.location.reload();
margin-bottom: -15px;
}
+.infinite-scroller .fa-warning {
+ color: #800;
+}
+
.inline-progress-container div.progress {
margin-bottom: 0;
}
background: #428bca;
color: #fff;
}
+.selectable-container > .row.class-separator {
+ background: #ddd;
+}
ArvadosBase::resource_class_for_uuid(params[:uuid])
end
+ def show
+ @object = model_class.find(params[:uuid])
+ if @object.is_a? Link and
+ @object.link_class == 'name' and
+ ArvadosBase::resource_class_for_uuid(@object.head_uuid) == Collection
+ redirect_to collection_path(id: @object.uuid)
+ else
+ redirect_to @object
+ end
+ end
+
def post
params.keys.collect(&:to_sym).each do |param|
if @@exposed_actions[param]
around_filter :require_thread_api_token, except: ERROR_ACTIONS
before_filter :check_user_agreements, except: ERROR_ACTIONS
before_filter :check_user_notifications, except: ERROR_ACTIONS
+ before_filter :load_filters_and_paging_params, except: ERROR_ACTIONS
before_filter :find_object_by_uuid, except: [:index, :choose] + ERROR_ACTIONS
theme :select_theme
end
end
- def find_objects_for_index
+ def load_filters_and_paging_params
@limit ||= 200
if params[:limit]
@limit = params[:limit].to_i
filters = params[:filters]
if filters.is_a? String
filters = Oj.load filters
+ elsif filters.is_a? Array
+ filters = filters.collect do |filter|
+ if filter.is_a? String
+ # Accept filters[]=["foo","=","bar"]
+ Oj.load filter
+ else
+ # Accept filters=[["foo","=","bar"]]
+ filter
+ end
+ end
end
@filters += filters
end
+ end
+ def find_objects_for_index
@objects ||= model_class
@objects = @objects.filter(@filters).limit(@limit).offset(@offset)
end
end
end
+ helper_method :next_page_href
+ def next_page_href with_params={}
+ if next_page_offset
+ url_for with_params.merge(offset: next_page_offset)
+ end
+ end
+
def show
if !@object
return render_not_found("object not found")
def choose
params[:limit] ||= 40
- if !@objects
- if params[:project_uuid] and !params[:project_uuid].empty?
- # We want the chooser to show objects of the controllers's model_class
- # type within a specific project specified by project_uuid, so fetch the
- # project and request the contents of the project filtered on the
- # controllers's model_class kind.
- @objects = Group.find(params[:project_uuid]).contents({:filters => [['uuid', 'is_a', "arvados\##{ArvadosApiClient.class_kind(model_class)}"]]})
- end
- find_objects_for_index if !@objects
- end
+ find_objects_for_index if !@objects
respond_to do |f|
if params[:partial]
f.json {
render json: {
content: render_to_string(partial: "choose_rows.html",
- formats: [:html],
- locals: {
- multiple: params[:multiple]
- }),
- next_page_href: @next_page_href
+ formats: [:html]),
+ next_page_href: next_page_href(partial: params[:partial])
}
}
end
def choose
params[:limit] ||= 40
- filter = [['link_class','=','name'],
- ['head_uuid','is_a','arvados#collection']]
-
- if params[:project_uuid] and !params[:project_uuid].empty?
- filter << ['tail_uuid', '=', params[:project_uuid]]
- end
-
- @objects = Link.filter(filter)
+ @filters += [['link_class','=','name'],
+ ['head_uuid','is_a','arvados#collection']]
+ @objects = Link
find_objects_for_index
- @next_page_href = (next_page_offset and
- url_for(offset: next_page_offset, partial: true))
+
@name_links = @objects
@objects = Collection.
--- /dev/null
+class SearchController < ApplicationController
+ def find_objects_for_index
+ @objects = Group.contents(limit: @limit, offset: @offset, filters: @filters)
+ super
+ end
+
+ def next_page_href with_params={}
+ super with_params.merge(last_object_class: @objects.last.class.to_s)
+ end
+end
RESOURCE_CLASS_ICONS.fetch(class_name, default)
end
end
+
+ def chooser_preview_url_for object
+ case object.class.to_s
+ when 'Collection'
+ polymorphic_path(object, tab_pane: 'chooser_preview')
+ else
+ nil
+ end
+ end
end
true
end
+ def self.contents params={}
+ res = arvados_api_client.api self, "/contents", {
+ _method: 'GET'
+ }.merge(params)
+ ret = ArvadosResourceList.new
+ ret.results = arvados_api_client.unpack_api_response(res)
+ ret
+ end
+
def contents params={}
res = arvados_api_client.api self.class, "/#{self.uuid}/contents", {
_method: 'GET'
</div>
<div class="modal-body">
- <nav class="navbar navbar-default breadcrumbs" role="navigation">
- <ul class="nav navbar-nav navbar-left">
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="projects-menu">
- Projects
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu">
- <%= render partial: "projects_tree_menu", locals: {
- :project_link_to => Proc.new do |pnode, &block|
- link_to "#", {"class" => "chooser-show-project", "data-project-uuid" => pnode[:object].uuid }, &block
- end,
- :top_button => Proc.new do %>
- <% link_to "#", {"class" => "chooser-show-project btn btn-xs btn-default pull-right" } do %>
- All <%= controller.model_class.class_for_display.pluralize.downcase %>
- <% end %>
+ <div class="input-group">
+ <input type="text" class="form-control filterable-control focus-on-display" placeholder="Search" data-filterable-target=".modal.arv-choose .selectable-container"/>
+ <% if params[:by_project] != false %>
+ <div class="input-group-btn" data-filterable-target=".modal.arv-choose .selectable-container">
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+ Filter by project <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" role="menu">
+ <li>
+ <%= link_to '#', class: 'chooser-show-project' do %>
+ Remove filter (search all projects)
<% end %>
- <% } %>
- </ul>
- </li>
- <li class="nav-separator">
- <i class="fa fa-lg fa-angle-double-right"></i>
- </li>
- <li><p class="navbar-text" id="chooser-breadcrumb">All <%= controller.model_class.class_for_display.pluralize.downcase %></span></li>
- </ul>
- <div class="navbar-form navbar-right">
- <input type="text" class="form-control filterable-control" placeholder="Search" data-filterable-target=".modal.arv-choose .selectable-container"/>
- </div>
- </nav>
+ </li>
+ <li class="divider" />
+ <%= render partial: "projects_tree_menu", locals: {
+ :project_link_to => Proc.new do |pnode, &block|
+ link_to "#", {"class" => "chooser-show-project", "data-project-uuid" => pnode[:object].uuid }, &block
+ end,
+ :top_button => nil
+ } %>
+ </ul>
+ </div>
+ <% end %>
+ </div>
+ <div style="height: 1em" />
<% preview_pane = (params[:preview_pane] != "false")
pane_col_class = preview_pane ? "col-sm-6" : "" %>
<div class="row" style="height: 20em">
- <div class="<%= pane_col_class %> container-fluid arv-filterable-list selectable-container"
- style="height: 100%; overflow-y: scroll"
- data-infinite-scroller="#choose-scroll"
- id="choose-scroll"
- data-infinite-content-href="<%= @next_page_href %>">
- <%= render partial: 'choose_rows', locals: {multiple: multiple} %>
+ <div class="col-sm-6 container arv-filterable-list selectable-container <%= 'multiple' if multiple %>"
+ style="height: 100%; overflow-y: scroll"
+ data-infinite-scroller="#choose-scroll"
+ id="choose-scroll"
+ data-infinite-content-href="<%= next_page_href partial: true %>"
+ data-infinite-content-href0="<%= url_for partial: true %>">
+ <%= render partial: 'choose_rows' %>
+ </div>
+ <div class="col-sm-6 modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
</div>
- <% if preview_pane %>
- <div class="<%= pane_col_class %> modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
- </div>
- <% end %>
</div>
<div class="modal-footer">
attr('data-action-href', '<%= j params[:action_href] %>').
attr('data-method', '<%= j params[:action_method] %>').
data('action-data', <%= raw params[:action_data] %>);
-$(".chooser-show-project").on("click", function() {
- $("#choose-scroll").html("<div class=\"spinner spinner-32px spinner-h-center\"></div>");
- $(".modal-dialog-preview-pane").html('');
- var t = $(this);
- var d = {
- partial: true,
- multiple: <%= multiple || "false" %>
- };
- if (t.attr("data-project-uuid") != null) {
- d.project_uuid = t.attr("data-project-uuid");
- }
- $.ajax('<%=j url_for %>', {
- dataType: "json",
- type: "GET",
- data: d
- }).done(function(data, status, jqxhr) {
- $("#chooser-breadcrumb").text(t.text());
- $("#choose-scroll").html(data.content);
- $("#choose-scroll").prop("data-infinite-content-href", "next_page_href");
- });
-});
+$('body > .modal-container .modal .focus-on-display').focus();
<li role="presentation" class="dropdown-header">
- <%= top_button.call %>
+ <%= top_button.andand.call %>
My projects
</li>
<% my_project_tree.each do |pnode| %>
<% @name_links.each do |name_link| %>
<% if (object = get_object(name_link.head_uuid)) %>
- <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= name_link.uuid %>"
- data-preview-href="<%= url_for object %>?tab_pane=chooser_preview"
+ <div class="row filterable selectable" data-object-uuid="<%= name_link.uuid %>"
+ data-preview-href="<%= chooser_preview_url_for object %>"
style="margin-left: 1em; border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: #DDDDDD">
<i class="fa fa-fw fa-archive"></i>
<%= name_link.name %>
<% icon_class = fa_icon_class_for_class(Group) %>
<% @objects.each do |object| %>
- <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+ <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>">
<div class="col-sm-12" style="overflow-x:hidden">
<i class="fa fa-fw <%= icon_class %>"></i>
<%= object.name %>
</li>
-->
+ <li>
+ <%= link_to(url_for(
+ action: 'choose',
+ controller: 'search',
+ title: 'Search',
+ action_name: 'Show',
+ action_href: url_for(controller: :actions, action: :show),
+ action_method: 'get',
+ action_data: {selection_param: 'uuid', success: 'redirect-to-created-object'}.to_json),
+ { class: "", remote: true, method: 'get' }) do %>
+ <i class="fa fa-fw fa-search"></i> Search
+ <% end %>
+ </li>
+
<li class="dropdown notification-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
<span class="badge badge-alert notification-count"><%= @notification_count %></span>
<% @objects.each do |object| %>
- <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= url_for object %>?tab_pane=chooser_preview">
+ <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= url_for object %>?tab_pane=chooser_preview">
<div class="col-sm-12" style="overflow-x:hidden">
<i class="fa fa-fw fa-gear"></i>
<%= object.name %>
--- /dev/null
+<% current_class = params[:last_object_class] %>
+<% @objects.each do |object| %>
+ <% icon_class = fa_icon_class_for_class(object.class) %>
+ <% if object.class.to_s != current_class %>
+ <% current_class = object.class.to_s %>
+ <div class="row class-separator" data-section-heading="true" data-section-name="<%= object.class.to_s %>">
+ <div class="col-sm-12">
+ <%= object.class_for_display.pluralize.downcase %>
+ </div>
+ </div>
+ <% end %>
+ <div class="row filterable selectable" data-section-name="<%= object.class.to_s %>" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= chooser_preview_url_for object %>">
+ <div class="col-sm-12" style="overflow-x:hidden; white-space: nowrap">
+ <i class="fa fa-fw <%= icon_class %>"></i>
+ <% if object.respond_to?(:name) and object.name and object.name.length > 0 %>
+ <%= object.name %>
+ <% else %>
+ unnamed - <span class="arvados-uuid"><%= object.uuid %></span>
+ <% end %>
+ </div>
+ </div>
+<% end %>
<% icon_class = fa_icon_class_for_class(User) %>
<% @objects.each do |object| %>
- <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+ <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>">
<div class="col-sm-12" style="overflow-x:hidden">
<i class="fa fa-fw <%= icon_class %>"></i>
<%= object.full_name %>
get 'choose', on: :collection
post 'share_with', on: :member
end
+ resources :search do
+ get 'choose', :on => :collection
+ end
post 'actions' => 'actions#post'
+ get 'actions' => 'actions#show'
get 'websockets' => 'websocket#index'
root :to => 'projects#index'
--- /dev/null
+require 'test_helper'
+
+class SearchControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class SearchHelperTest < ActionView::TestCase
+end
all(find_spec + ' a').collect { |tag|
if tag[:href].nil? or tag[:href].empty? or (tag.text !~ text_regexp)
nil
+ elsif tag[:'data-remote']
+ # these don't necessarily work with format=html
+ nil
else
url = URI(tag[:href])
url.host.nil? ? url.path : nil
except: [:index, :create] + ERROR_ACTIONS)
before_filter :load_limit_offset_order_params, only: [:index, :contents]
before_filter :load_where_param, only: [:index, :contents]
- before_filter :load_filters_param, only: [:index, :contents]
+ before_filter :load_filters_param, only: [:index, :contents, :choose]
before_filter :find_objects_for_index, :only => :index
before_filter :reload_object_before_update, :only => :update
before_filter(:render_404_if_no_object,
})
end
+ def render_404_if_no_object
+ if params[:action] == 'contents' and !params[:uuid]
+ # OK!
+ @object = nil
+ else
+ super
+ end
+ end
+
def contents
+ # Set @objects:
+ load_searchable_objects(owner_uuid: @object.andand.uuid, include_linked: params[:include_linked])
+
+ sql = 'link_class=? and head_uuid in (?)'
+ sql_params = ['name', @objects.collect(&:uuid)]
+ if @object
+ sql += ' and tail_uuid=?'
+ sql_params << @object.uuid
+ end
+ @links = Link.where sql, *sql_params
+ @object_list = {
+ :kind => "arvados#objectList",
+ :etag => "",
+ :self_link => "",
+ :links => @links.as_api_response(nil),
+ :offset => @offset,
+ :limit => @limit,
+ :items_available => @items_available,
+ :items => @objects.as_api_response(nil)
+ }
+ render json: @object_list
+ end
+
+ protected
+
+ def load_searchable_objects opts
all_objects = []
- all_available = 0
+ @items_available = 0
# Trick apply_where_limit_order_params into applying suitable
# per-table values. *_all are the real ones we'll apply to the
Collection,
Human, Specimen, Trait].each do |klass|
@objects = klass.readable_by(*@read_users)
- cond_sql = "#{klass.table_name}.owner_uuid = ?"
- cond_params = [@object.uuid]
- if params[:include_linked]
- cond_sql += " OR #{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'} AND links.tail_uuid=#{klass.sanitize @object.uuid})"
+ conds = []
+ cond_params = []
+ if opts[:owner_uuid]
+ conds << "#{klass.table_name}.owner_uuid = ?"
+ cond_params << opts[:owner_uuid]
+ end
+ if opts[:include_linked]
+ conds << "#{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'} AND links.tail_uuid=#{klass.sanitize @object.uuid})"
+ end
+ if conds.any?
+ cond_sql = '(' + conds.join(') OR (') + ')'
+ @objects = @objects.where(cond_sql, *cond_params)
end
- @objects = @objects.where(cond_sql, *cond_params).order("#{klass.table_name}.uuid")
+ @objects = @objects.order("#{klass.table_name}.uuid")
@limit = limit_all - all_objects.count
apply_where_limit_order_params
- items_available = @objects.
+ klass_items_available = @objects.
except(:limit).except(:offset).
count(:id, distinct: true)
- all_available += items_available
- @offset = [@offset - items_available, 0].max
+ @items_available += klass_items_available
+ @offset = [@offset - klass_items_available, 0].max
all_objects += @objects.to_a
end
- @objects = all_objects || []
- @links = Link.where('link_class=? and tail_uuid=?'\
- ' and head_uuid in (?)',
- 'name',
- @object.uuid,
- @objects.collect(&:uuid))
- @object_list = {
- :kind => "arvados#objectList",
- :etag => "",
- :self_link => "",
- :links => @links.as_api_response(nil),
- :offset => offset_all,
- :limit => limit_all,
- :items_available => all_available,
- :items => @objects.as_api_response(nil)
- }
- render json: @object_list
+
+ @objects = all_objects
+ @limit = limit_all
+ @offset = offset_all
end
end
get 'used_by', on: :member
end
resources :groups do
+ get 'contents', on: :collection
get 'contents', on: :member
end
resources :humans
check_project_contents_response
end
+ test 'list objects across multiple projects' do
+ authorize_with :project_viewer
+ get :contents, {
+ format: :json,
+ include_linked: false,
+ filters: [['uuid', 'is_a', 'arvados#specimen']]
+ }
+ assert_response :success
+ found_uuids = json_response['items'].collect { |i| i['uuid'] }
+ [[:in_aproject, true],
+ [:in_asubproject, true],
+ [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+ if should_find
+ assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+ else
+ refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+ end
+ end
+ end
+
# Even though the project_viewer tests go through other controllers,
# I'm putting them here so they're easy to find alongside the other
# project tests.