[ARVADOS] created: 420949e37a2903ab87f64f57278dfdc6a261a7f3
git at public.curoverse.com
git at public.curoverse.com
Tue Apr 29 00:38:46 EDT 2014
at 420949e37a2903ab87f64f57278dfdc6a261a7f3 (commit)
commit 420949e37a2903ab87f64f57278dfdc6a261a7f3
Merge: 2943d9c 34350a8
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Apr 29 00:38:22 2014 -0400
Merge branch '2640-folder-api' into 1970-folder-view
commit 34350a8b802a8c48b534673a712614d36a5b97ac
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Apr 29 00:37:53 2014 -0400
Skip UserAgreement in owned_items. They are just collections again.
diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 0f144d8..d9f01f5 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -88,7 +88,7 @@ class ApplicationController < ActionController::Base
# disappointed: when Rails reloads model classes, we get two
# distinct classes called Link which do not equal each
# other. But we can still rely on klass.to_s to be "Link".
- when 'ApiClientAuthorization'
+ when 'ApiClientAuthorization', 'UserAgreement'
# Do not want.
else
@objects = klass.readable_by(current_user)
commit 2943d9c3622e2c5bca081dc48fd5d8d148dac386
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Apr 29 00:37:01 2014 -0400
Add folders page, backed by groups.
diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 2e3a596..169f304 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -170,6 +170,10 @@ class ApplicationController < ActionController::Base
controller_name.classify.constantize
end
+ def model_class_for_display
+ model_class.to_s
+ end
+
def breadcrumb_page_name
(@breadcrumb_page_name ||
(@object.friendly_link_name if @object.respond_to? :friendly_link_name) ||
diff --git a/apps/workbench/app/controllers/groups_controller.rb b/apps/workbench/app/controllers/groups_controller.rb
index 672fe90..358cb2c 100644
--- a/apps/workbench/app/controllers/groups_controller.rb
+++ b/apps/workbench/app/controllers/groups_controller.rb
@@ -1,6 +1,14 @@
class GroupsController < ApplicationController
+ def model_class_for_display
+ params[:group_class] || super
+ end
+
def index
- @groups = Group.all
+ if params[:group_class]
+ @groups = Group.where(group_class: params[:group_class])
+ else
+ @groups = Group.all
+ end
@group_uuids = @groups.collect &:uuid
@links_from = Link.where link_class: 'permission', tail_uuid: @group_uuids
@links_to = Link.where link_class: 'permission', head_uuid: @group_uuids
diff --git a/apps/workbench/app/models/arvados_base.rb b/apps/workbench/app/models/arvados_base.rb
index 81732ba..45a4d8b 100644
--- a/apps/workbench/app/models/arvados_base.rb
+++ b/apps/workbench/app/models/arvados_base.rb
@@ -244,6 +244,10 @@ class ArvadosBase < ActiveRecord::Base
}
end
+ def class_for_display
+ self.class.to_s
+ end
+
def self.creatable?
current_user
end
diff --git a/apps/workbench/app/models/arvados_resource_list.rb b/apps/workbench/app/models/arvados_resource_list.rb
index a474b13..f6bcaae 100644
--- a/apps/workbench/app/models/arvados_resource_list.rb
+++ b/apps/workbench/app/models/arvados_resource_list.rb
@@ -90,6 +90,12 @@ class ArvadosResourceList
self
end
+ def collect
+ results.collect do |m|
+ yield m
+ end
+ end
+
def first
results.first
end
diff --git a/apps/workbench/app/views/application/_delete_object_button.html.erb b/apps/workbench/app/views/application/_delete_object_button.html.erb
index 67a3d06..52a568f 100644
--- a/apps/workbench/app/views/application/_delete_object_button.html.erb
+++ b/apps/workbench/app/views/application/_delete_object_button.html.erb
@@ -1,5 +1,5 @@
<% if object.editable? %>
- <%= link_to({action: 'destroy', id: object.uuid}, method: :delete, remote: true, data: {confirm: "You are about to delete #{object.class} #{object.uuid}.\n\nAre you sure?"}) do %>
+ <%= link_to({action: 'destroy', id: object.uuid}, method: :delete, remote: true, data: {confirm: "You are about to delete #{object.class_for_display} #{object.uuid}.\n\nAre you sure?"}) do %>
<i class="glyphicon glyphicon-trash"></i>
<% end %>
<% end %>
diff --git a/apps/workbench/app/views/application/_show_recent.html.erb b/apps/workbench/app/views/application/_show_recent.html.erb
index 04387ff..b02ce19 100644
--- a/apps/workbench/app/views/application/_show_recent.html.erb
+++ b/apps/workbench/app/views/application/_show_recent.html.erb
@@ -1,7 +1,7 @@
<% if @objects.empty? %>
<br/>
<p style="text-align: center">
- No <%= controller.model_class.to_s.pluralize.underscore.gsub '_', ' ' %> to display.
+ No <%= controller.model_class_for_display.pluralize.underscore.gsub '_', ' ' %> to display.
</p>
<% else %>
diff --git a/apps/workbench/app/views/application/index.html.erb b/apps/workbench/app/views/application/index.html.erb
index 3f31240..f6bd49c 100644
--- a/apps/workbench/app/views/application/index.html.erb
+++ b/apps/workbench/app/views/application/index.html.erb
@@ -12,7 +12,7 @@
'data-target' => '#user-setup-modal-window', return_to: request.url} %>
<div id="user-setup-modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"></div>
<% else %>
- <%= button_to "Add a new #{controller.model_class.to_s.underscore.gsub '_', ' '}",
+ <%= button_to "Add a new #{controller.model_class_for_display.underscore.gsub '_', ' '}",
{ action: 'create', return_to: request.url },
{ class: 'btn btn-primary pull-right' } %>
<% end %>
diff --git a/apps/workbench/app/views/groups/_show_recent.html.erb b/apps/workbench/app/views/groups/_show_recent.html.erb
index c709e89..83b31cc 100644
--- a/apps/workbench/app/views/groups/_show_recent.html.erb
+++ b/apps/workbench/app/views/groups/_show_recent.html.erb
@@ -4,7 +4,7 @@
<thead>
<tr class="contain-align-left">
<th>
- Group
+ <%= controller.model_class_for_display.capitalize %>
</th><th>
Owner
</th><th>
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index 04e5dfe..094cbc9 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -85,7 +85,7 @@
<li><a href="/pipeline_templates">
<i class="fa fa-lg fa-gears fa-fw"></i> Pipeline templates
</a></li>
- <li><a href="/groups">
+ <li><a href="/folders">
<i class="fa fa-lg fa-folder-o fa-fw"></i> Folders
</a></li>
<li class="dropdown">
@@ -138,7 +138,7 @@
<li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
<li>
<%= link_to(
- controller.model_class.to_s.pluralize.underscore.gsub('_', ' '),
+ controller.model_class_for_display.pluralize.underscore.gsub('_', ' '),
url_for({controller: params[:controller]})) %>
</li>
<% if params[:action] != 'index' %>
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index 9890ce4..4fb6578 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -42,6 +42,7 @@ ArvadosWorkbench::Application.routes.draw do
match '/collections/graph' => 'collections#graph'
resources :collections
get '/collections/:uuid/*file' => 'collections#show_file', :format => false
+ resources :folders, controller: :groups, group_class: 'folder'
post 'actions' => 'actions#post'
commit bd240259a9d95a4da53eb0ff8a3644d7acd7705d
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Apr 29 00:00:35 2014 -0400
Add sb-admin layout.
diff --git a/apps/workbench/app/assets/stylesheets/application.css.scss b/apps/workbench/app/assets/stylesheets/application.css.scss
index ff97da8..c918125 100644
--- a/apps/workbench/app/assets/stylesheets/application.css.scss
+++ b/apps/workbench/app/assets/stylesheets/application.css.scss
@@ -91,25 +91,6 @@ form.small-form-margin {
text-decoration: none;
text-shadow: 0 1px 0 #ffffff;
}
-/*.navbar .nav .dropdown .dropdown-menu li a {
- padding: 2px 20px;
-}*/
-
-ul.arvados-nav {
- list-style: none;
- padding-left: 0em;
- margin-left: 0em;
-}
-
-ul.arvados-nav li ul {
- list-style: none;
- padding-left: 0;
-}
-
-ul.arvados-nav li ul li {
- list-style: none;
- padding-left: 1em;
-}
.dax {
max-width: 10%;
@@ -151,20 +132,6 @@ span.removable-tag-container {
li.notification {
padding: 10px;
}
-.arvados-nav-container {
- top: 70px;
- height: calc(100% - 70px);
- overflow: auto;
- z-index: 2;
-}
-
-.arvados-nav-active {
- background: rgb(66, 139, 202);
-}
-
-.arvados-nav-active a, .arvados-nav-active a:hover {
- color: white;
-}
// See HeaderRowFixer in application.js
table.table-fixed-header-row {
@@ -189,3 +156,6 @@ table.table-fixed-header-row tbody {
overflow-y: auto;
}
+.row-fill-height, .row-fill-height>div[class*='col-'] {
+ display: flex;
+}
diff --git a/apps/workbench/app/assets/stylesheets/sb-admin.css.scss b/apps/workbench/app/assets/stylesheets/sb-admin.css.scss
new file mode 100644
index 0000000..e9b99d6
--- /dev/null
+++ b/apps/workbench/app/assets/stylesheets/sb-admin.css.scss
@@ -0,0 +1,165 @@
+/*
+Author: Start Bootstrap - http://startbootstrap.com
+'SB Admin' HTML Template by Start Bootstrap
+
+All Start Bootstrap themes are licensed under Apache 2.0.
+For more info and more free Bootstrap 3 HTML themes, visit http://startbootstrap.com!
+*/
+
+/* ATTN: This is mobile first CSS - to update 786px and up screen width use the media query near the bottom of the document! */
+
+/* Global Styles */
+
+body {
+ margin-top: 50px;
+}
+
+#wrapper {
+ padding-left: 0;
+}
+
+#page-wrapper {
+ width: 100%;
+ padding: 5px 15px;
+}
+
+/* Nav Messages */
+
+.messages-dropdown .dropdown-menu .message-preview .avatar,
+.messages-dropdown .dropdown-menu .message-preview .name,
+.messages-dropdown .dropdown-menu .message-preview .message,
+.messages-dropdown .dropdown-menu .message-preview .time {
+ display: block;
+}
+
+.messages-dropdown .dropdown-menu .message-preview .avatar {
+ float: left;
+ margin-right: 15px;
+}
+
+.messages-dropdown .dropdown-menu .message-preview .name {
+ font-weight: bold;
+}
+
+.messages-dropdown .dropdown-menu .message-preview .message {
+ font-size: 12px;
+}
+
+.messages-dropdown .dropdown-menu .message-preview .time {
+ font-size: 12px;
+}
+
+
+/* Nav Announcements */
+
+.announcement-heading {
+ font-size: 50px;
+ margin: 0;
+}
+
+.announcement-text {
+ margin: 0;
+}
+
+/* Table Headers */
+
+table.tablesorter thead {
+ cursor: pointer;
+}
+
+table.tablesorter thead tr th:hover {
+ background-color: #f5f5f5;
+}
+
+/* Flot Chart Containers */
+
+.flot-chart {
+ display: block;
+ height: 400px;
+}
+
+.flot-chart-content {
+ width: 100%;
+ height: 100%;
+}
+
+/* Edit Below to Customize Widths > 768px */
+ at media (min-width:768px) {
+
+ /* Wrappers */
+
+ #wrapper {
+ padding-left: 225px;
+ }
+
+ #page-wrapper {
+ padding: 15px 25px;
+ border-left: 1px solid #e7e7e7;
+ }
+
+ /* Side Nav */
+
+ .side-nav {
+ margin-left: -225px;
+ left: 225px;
+ width: 225px;
+ position: fixed;
+ top: 50px;
+ height: calc(100% - 50px);
+ border-radius: 0;
+ border: none;
+ background-color: #f8f8f8;
+ overflow-y: auto;
+ overflow-x: hidden; /* no left nav scroll bar */
+ }
+
+ /* Bootstrap Default Overrides - Customized Dropdowns for the Side Nav */
+
+ .side-nav>li.dropdown>ul.dropdown-menu {
+ position: relative;
+ min-width: 225px;
+ margin: 0;
+ padding: 0;
+ border: none;
+ border-radius: 0;
+ background-color: transparent;
+ box-shadow: none;
+ -webkit-box-shadow: none;
+ }
+
+ .side-nav>li.dropdown>ul.dropdown-menu>li>a {
+ color: #777777;
+ padding: 15px 15px 15px 25px;
+ }
+
+ .side-nav>li.dropdown>ul.dropdown-menu>li>a:hover,
+ .side-nav>li.dropdown>ul.dropdown-menu>li>a.active,
+ .side-nav>li.dropdown>ul.dropdown-menu>li>a:focus {
+ background-color: #f8ffff;
+ }
+
+ .side-nav>li>a {
+ width: 225px;
+ }
+
+ .navbar-default .navbar-nav.side-nav>li>a:hover,
+ .navbar-default .navbar-nav.side-nav>li>a:focus {
+ background-color: #f8ffff;
+ }
+
+ /* Nav Messages */
+
+ .messages-dropdown .dropdown-menu {
+ min-width: 300px;
+ }
+
+ .messages-dropdown .dropdown-menu li a {
+ white-space: normal;
+ }
+
+ .navbar-collapse {
+ padding-left: 15px !important;
+ padding-right: 15px !important;
+ }
+
+}
diff --git a/apps/workbench/app/views/groups/show.html.erb b/apps/workbench/app/views/groups/show.html.erb
index 41541f8..b6f970f 100644
--- a/apps/workbench/app/views/groups/show.html.erb
+++ b/apps/workbench/app/views/groups/show.html.erb
@@ -4,76 +4,80 @@
}
<% end %>
-<% content_for :above_left_nav do %>
-<div class="panel panel-info">
- <div class="panel-heading">
- <a class="btn btn-xs btn-info pull-right">
- Rename
- </a>
- <h3 class="panel-title">
- <%= @object.name %>
- </h3>
- </div>
- <div class="panel-body">
- <img src="/favicon.ico" class="pull-right" alt=""/>
- <p>
- This folder was created <%= @object.created_at %>. (This
- description defaults to something generic.)
- </p>
- <a href="#" class="btn btn-xs btn-info">Edit description</a>
- </div>
-</div>
-
-<div class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">
- Activity
- </h3>
- </div>
- <div class="panel-body">
- <input type="text" class="form-control" placeholder="Search"/>
- <div style="height:0.5em;"></div>
- <p>
- 11:12 - Some Subfolder added
- </p>
- <p>
- 10:06 - <%= @object.name %> - renamed from OldFolderName to <%= @object.name %>
- </p>
- <p>
- 10:01 - Test Dataset (4 GiB collection) added to <%= @object.name %> by <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
- </p>
+<div class="row row-fill-height">
+ <div class="col-md-6">
+ <div class="panel panel-info">
+ <div class="panel-heading">
+ <a class="btn btn-xs btn-info pull-right">
+ Rename
+ </a>
+ <h3 class="panel-title">
+ <%= @object.name %>
+ </h3>
+ </div>
+ <div class="panel-body">
+ <img src="/favicon.ico" class="pull-right" alt=""/>
+ <p>
+ This folder was created <%= @object.created_at %>. (This
+ description defaults to something generic.)
+ </p>
+ <a href="#" class="btn btn-xs btn-info">Edit description</a>
+ </div>
+ </div>
</div>
-</div>
-
-<div class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">
- Sharing and permissions
- </h3>
+ <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">
+ <input type="text" class="form-control" placeholder="Search"/>
+ <div style="height:0.5em;"></div>
+ <p>
+ 11:12 - Some Subfolder added
+ </p>
+ <p>
+ 10:06 - <%= @object.name %> - renamed from OldFolderName to <%= @object.name %>
+ </p>
+ <p>
+ 10:01 - Test Dataset (4 GiB collection) added to <%= @object.name %> by <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
+ </p>
+ </div>
+ </div>
</div>
- <div class="panel-body">
- <input type="text" class="form-control" placeholder="Search"/>
- <div style="height:0.5em;"></div>
- <table class="table table-condensed">
- <tbody>
- <tr>
- <td><%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %></td>
- <td>Owner</td>
- </tr>
- <tr>
- <td>Someone Else</td>
- <td>read only</td>
- </tr>
- <tr>
- <td>Someone Else</td>
- <td>read+write</td>
- </tr>
- </tbody>
- <thead><tr><th>User</th><th>Role</th></tr></thead>
- </table>
+ <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 style="height:0.5em;"></div>
+ <table class="table table-condensed">
+ <tbody>
+ <tr>
+ <td><%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %></td>
+ <td>Owner</td>
+ </tr>
+ <tr>
+ <td>Someone Else</td>
+ <td>read only</td>
+ </tr>
+ <tr>
+ <td>Someone Else</td>
+ <td>read+write</td>
+ </tr>
+ </tbody>
+ <thead><tr><th>User</th><th>Role</th></tr></thead>
+ </table>
+ </div>
+ </div>
</div>
</div>
-<% end %>
<div class="row">
<div class="card arvados-object">
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index ece2f2e..04e5dfe 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -32,34 +32,25 @@
padding-top: 70px; /* 70px to make the container go all the way to the bottom of the navbar */
}
- body > div.container-fluid > div.col-sm-9.col-sm-offset-3 {
- overflow: auto;
- }
-
@media (max-width: 979px) { body { padding-top: 0; } }
.navbar .nav li.nav-separator > span.glyphicon.glyphicon-arrow-right {
padding-top: 1.25em;
}
- @media (min-width: 768px) {
- .left-nav {
- position: fixed;
- }
- }
@media (max-width: 767px) {
.breadcrumbs {
display: none;
}
}
</style>
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
</head>
<body>
-
- <div class="navbar navbar-default navbar-fixed-top">
- <div class="container-fluid">
+ <div id="wrapper">
+ <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="navbar-header">
- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#workbench-navbar.navbar-collapse">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@@ -68,168 +59,185 @@
<a class="navbar-brand" href="/"><%= Rails.configuration.site_name rescue Rails.application.class.parent_name %></a>
</div>
- <div class="collapse navbar-collapse" id="workbench-navbar">
- <ul class="nav navbar-nav navbar-left breadcrumbs">
- <% if current_user %>
- <% if content_for?(:breadcrumbs) %>
- <%= yield(:breadcrumbs) %>
- <% else %>
- <li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
- <li>
- <%= link_to(
- controller.model_class.to_s.pluralize.underscore.gsub('_', ' '),
- url_for({controller: params[:controller]})) %>
- </li>
- <% if params[:action] != 'index' %>
- <li class="nav-separator">
- <span class="glyphicon glyphicon-arrow-right"></span>
+ <div class="collapse navbar-collapse">
+ <ul class="nav navbar-nav side-nav">
+ <% if current_user.andand.is_active %>
+
+ <li class="<%= 'arvados-nav-active' if params[:action] == 'home' %>">
+ <a href="/"><i class="fa fa-lg fa-dashboard fa-fw"></i> Dashboard</a>
</li>
- <li>
- <%= link_to_if_arvados_object @object %>
+
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-lg fa-hand-o-up fa-fw"></i> Help <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><%= link_to raw('<i class="fa fa-lg fa-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
+ <li><%= link_to raw('<i class="fa fa-lg fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
+ <li><%= link_to raw('<i class="fa fa-lg fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
+ </ul>
</li>
- <li style="padding: 14px 0 14px">
- <%= form_tag do |f| %>
- <%= render :partial => "selection_checkbox", :locals => {:object => @object} %>
- <% end %>
+
+ <li><a href="/collections">
+ <i class="fa fa-lg fa-briefcase fa-fw"></i> Collections (data files)
+ </a></li>
+ <li><a href="/pipeline_instances">
+ <i class="fa fa-lg fa-tasks fa-fw"></i> Pipeline instances
+ </a></li>
+ <li><a href="/pipeline_templates">
+ <i class="fa fa-lg fa-gears fa-fw"></i> Pipeline templates
+ </a></li>
+ <li><a href="/groups">
+ <i class="fa fa-lg fa-folder-o fa-fw"></i> Folders
+ </a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-lg fa-ellipsis-h fa-fw"></i> More <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="/humans">
+ <i class="fa fa-lg fa-male fa-fw"></i> Humans
+ </a></li>
+ <li><a href="/specimens">
+ <i class="fa fa-lg fa-flask fa-fw"></i> Specimens
+ </a></li>
+ <li><a href="/traits">
+ <i class="fa fa-lg fa-clipboard fa-fw"></i> Traits
+ </a></li>
+ <li><a href="/links">
+ <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
+ </a></li>
+ <li><a href="/repositories">
+ <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
+ </a></li>
+ <li><a href="/virtual_machines">
+ <i class="fa fa-lg fa-ellipsis-h fa-fw"></i> Virtual machines
+ </a></li>
+ <% if current_user.andand.is_admin %>
+ <li><a href="/users">
+ <i class="fa fa-lg fa-user fa-fw"></i> Users
+ </a></li>
+ <% end %>
+ <li><a href="/groups">
+ <i class="fa fa-lg fa-users fa-fw"></i> Groups
+ </a></li>
+ <li><a href="/nodes">
+ <i class="fa fa-lg fa-cogs fa-fw"></i> Compute nodes
+ </a></li>
+ <li><a href="/keep_disks">
+ <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
+ </a></li>
+ </ul>
</li>
- <% end %>
- <% end %>
- <% end %>
- </ul>
-
- <ul class="nav navbar-nav navbar-right">
-
- <li>
- <a><i class="rotating loading glyphicon glyphicon-refresh"></i></a>
- </li>
-
- <% if current_user %>
- <!-- XXX placeholder for this when search is implemented
- <li>
- <form class="navbar-form" role="search">
- <div class="input-group" style="width: 220px">
- <input type="text" class="form-control" placeholder="search">
- <span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>
- </div>
- </form>
- </li>
- -->
-
- <li class="dropdown notification-menu">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
- <span class="glyphicon glyphicon-paperclip"></span>
- <span class="badge" id="persistent-selection-count"></span>
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
- <%= form_tag '/actions' do %>
- <div id="selection-form-content"></div>
- <% end %>
- </ul>
- </li>
-
- <% if current_user.is_active %>
- <li class="dropdown notification-menu">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
- <span class="glyphicon glyphicon-envelope"></span>
- <span class="badge badge-alert notification-count"><%= @notification_count %></span>
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu">
- <% if (@notifications || []).length > 0 %>
- <% @notifications.each_with_index do |n, i| %>
- <% if i > 0 %><li class="divider"></li><% end %>
- <li class="notification"><%= n.call(self) %></li>
- <% end %>
- <% else %>
- <li class="notification empty">No notifications.</li>
<% end %>
</ul>
- </li>
- <% end %>
-
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="user-menu">
- <span class="glyphicon glyphicon-user"></span><span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu">
- <li role="presentation" class="dropdown-header"><%= current_user.email %></li>
- <% if current_user.is_active %>
- <li role="presentation" class="divider"></li>
- <li role="presentation"><a href="/authorized_keys" role="menuitem">Manage ssh keys</a></li>
- <li role="presentation"><a href="/api_client_authorizations" role="menuitem">Manage API tokens</a></li>
- <li role="presentation" class="divider"></li>
+
+
+
+ <ul class="nav navbar-nav navbar-left breadcrumbs">
+ <% if current_user %>
+ <% if content_for?(:breadcrumbs) %>
+ <%= yield(:breadcrumbs) %>
+ <% else %>
+ <li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
+ <li>
+ <%= link_to(
+ controller.model_class.to_s.pluralize.underscore.gsub('_', ' '),
+ url_for({controller: params[:controller]})) %>
+ </li>
+ <% if params[:action] != 'index' %>
+ <li class="nav-separator">
+ <span class="glyphicon glyphicon-arrow-right"></span>
+ </li>
+ <li>
+ <%= link_to_if_arvados_object @object %>
+ </li>
+ <li style="padding: 14px 0 14px">
+ <%= form_tag do |f| %>
+ <%= render :partial => "selection_checkbox", :locals => {:object => @object} %>
+ <% end %>
+ </li>
+ <% end %>
<% end %>
- <li role="presentation"><a href="<%= logout_path %>" role="menuitem">Log out</a></li>
- </ul>
- </li>
- <% else -%>
- <li><a href="<%= $arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
- <% end -%>
- </ul>
- </div><!-- /.navbar-collapse -->
- </div><!-- /.container-fluid -->
- </div>
+ <% end %>
+ </ul>
- <div class="container-fluid">
- <div class="col-sm-9 col-sm-offset-3">
- <div id="content" class="body-content">
- <%= yield %>
- </div>
- </div>
- <div class="col-sm-3 left-nav">
- <%= yield :above_left_nav %>
- <div class="arvados-nav-container">
- <% if current_user.andand.is_active %>
- <div class="well">
- <ul class="arvados-nav">
- <li class="<%= 'arvados-nav-active' if params[:action] == 'home' %>">
- <a href="/">Dashboard</a>
+ <ul class="nav navbar-nav navbar-right">
+
+ <li>
+ <a><i class="rotating loading glyphicon glyphicon-refresh"></i></a>
+ </li>
+
+ <% if current_user %>
+ <!-- XXX placeholder for this when search is implemented
+ <li>
+ <form class="navbar-form" role="search">
+ <div class="input-group" style="width: 220px">
+ <input type="text" class="form-control" placeholder="search">
+ <span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>
+ </div>
+ </form>
+ </li>
+ -->
+
+ <li class="dropdown notification-menu">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
+ <span class="glyphicon glyphicon-paperclip"></span>
+ <span class="badge" id="persistent-selection-count"></span>
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
+ <%= form_tag '/actions' do %>
+ <div id="selection-form-content"></div>
+ <% end %>
+ </ul>
</li>
- <% [['Data', [['collections', 'Collections (data files)'],
- ['humans'],
- ['traits'],
- ['specimens'],
- ['links']]],
- ['Activity', [['pipeline_instances', 'Recent pipeline instances'],
- ['jobs', 'Recent jobs']]],
- ['Compute', [['pipeline_templates'],
- ['repositories', 'Code repositories'],
- ['virtual_machines']]],
- ['System', [['users'],
- ['groups'],
- ['nodes', 'Compute nodes'],
- ['keep_disks']]]].each do |j| %>
- <li><%= j[0] %>
- <ul>
- <% j[1].each do |k| %>
- <% unless k[0] == 'users' and !current_user.andand.is_admin %>
- <li class="<%= 'arvados-nav-active' if (params[:controller] == k[0] && params[:action] != 'home') %>">
- <a href="/<%= k[0] %>">
- <%= if k[1] then k[1] else k[0].capitalize.gsub('_', ' ') end %>
- </a>
- </li>
+ <% if current_user.is_active %>
+ <li class="dropdown notification-menu">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
+ <span class="glyphicon glyphicon-envelope"></span>
+ <span class="badge badge-alert notification-count"><%= @notification_count %></span>
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <% if (@notifications || []).length > 0 %>
+ <% @notifications.each_with_index do |n, i| %>
+ <% if i > 0 %><li class="divider"></li><% end %>
+ <li class="notification"><%= n.call(self) %></li>
<% end %>
+ <% else %>
+ <li class="notification empty">No notifications.</li>
<% end %>
- </ul>
- </li>
+ </ul>
+ </li>
<% end %>
- <li>Help
- <ul>
- <li><%= link_to 'Tutorials and User guide', "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
- <li><%= link_to 'API Reference', "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
- <li><%= link_to 'SDK Reference', "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="user-menu">
+ <span class="glyphicon glyphicon-user"></span><span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li role="presentation" class="dropdown-header"><%= current_user.email %></li>
+ <% if current_user.is_active %>
+ <li role="presentation" class="divider"></li>
+ <li role="presentation"><a href="/authorized_keys" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage ssh keys</a></li>
+ <li role="presentation"><a href="/api_client_authorizations" role="menuitem"><i class="fa fa-ticket fa-fw"></i> Manage API tokens</a></li>
+ <li role="presentation" class="divider"></li>
+ <% end %>
+ <li role="presentation"><a href="<%= logout_path %>" role="menuitem"><i class="fa fa-sign-out fa-fw"></i> Log out</a></li>
</ul>
</li>
+ <% else %>
+ <li><a href="<%= $arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
+ <% end %>
</ul>
- </div>
- <% end %>
- </div>
- </div>
+ </div><!-- /.navbar-collapse -->
+ </nav>
+
+ <div id="page-wrapper">
+ <%= yield %>
+ </div>
</div>
+</div>
+
<%= yield :footer_html %>
<%= piwik_tracking_tag %>
<%= javascript_tag do %>
commit e35fb48f1485a92a64a30efe8b1c43a179b70260
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 28 19:30:35 2014 -0400
Show folder contents with editable names.
diff --git a/apps/workbench/app/assets/javascripts/editable.js b/apps/workbench/app/assets/javascripts/editable.js
index e6799bf..24da286 100644
--- a/apps/workbench/app/assets/javascripts/editable.js
+++ b/apps/workbench/app/assets/javascripts/editable.js
@@ -1,4 +1,4 @@
-$.fn.editable.defaults.ajaxOptions = {type: 'put', dataType: 'json'};
+$.fn.editable.defaults.ajaxOptions = {type: 'post', dataType: 'json'};
$.fn.editable.defaults.send = 'always';
// Default for editing is popup. I experimented with inline which is a little
@@ -13,8 +13,13 @@ $.fn.editable.defaults.params = function (params) {
var a = {};
var key = params.pk.key;
a.id = params.pk.id;
- a[key] = {};
+ a[key] = params.pk.defaults || {};
a[key][params.name] = params.value;
+ if (params.pk._method) {
+ a['_method'] = params.pk._method;
+ } else {
+ a['_method'] = 'put';
+ }
return a;
};
@@ -24,6 +29,13 @@ $.fn.editable.defaults.validate = function (value) {
}
}
+$(document).
+ on('ready ajax:complete', function() {
+ $('#editable-submit').click(function() {
+ console.log($(this));
+ });
+ });
+
$.fn.editabletypes.text.defaults.tpl = '<input type="text" name="editable-text">'
$.fn.editableform.buttons = '\
diff --git a/apps/workbench/app/assets/stylesheets/application.css.scss b/apps/workbench/app/assets/stylesheets/application.css.scss
index 455e4c0..ff97da8 100644
--- a/apps/workbench/app/assets/stylesheets/application.css.scss
+++ b/apps/workbench/app/assets/stylesheets/application.css.scss
@@ -44,6 +44,10 @@ table.table-justforlayout {
font-size: .8em;
color: #888;
}
+.arvados-uuid {
+ font-size: .8em;
+ font-family: monospace;
+}
table .data-size, .table .data-size {
text-align: right;
}
diff --git a/apps/workbench/app/assets/stylesheets/cards.css.scss b/apps/workbench/app/assets/stylesheets/cards.css.scss
new file mode 100644
index 0000000..c9560ad
--- /dev/null
+++ b/apps/workbench/app/assets/stylesheets/cards.css.scss
@@ -0,0 +1,85 @@
+.card {
+ padding-top: 20px;
+ margin: 10px 0 20px 0;
+ background-color: #ffffff;
+ border: 1px solid #d8d8d8;
+ border-top-width: 0;
+ border-bottom-width: 2px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.card.arvados-object {
+ position: relative;
+ display: inline-block;
+ width: 170px;
+ height: 175px;
+ padding-top: 0;
+ margin-left: 20px;
+ overflow: hidden;
+ vertical-align: top;
+}
+.card.arvados-object .card-top.green {
+ background-color: #53a93f;
+}
+.card.arvados-object .card-top.blue {
+ background-color: #427fed;
+}
+.card.arvados-object .card-top {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 170px;
+ height: 25px;
+ background-color: #ffffff;
+}
+.card.arvados-object .card-info {
+ position: absolute;
+ top: 25px;
+ display: inline-block;
+ width: 100%;
+ height: 101px;
+ overflow: hidden;
+ background: #ffffff;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.card.arvados-object .card-info .title {
+ display: block;
+ margin: 8px 14px 0 14px;
+ overflow: hidden;
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 18px;
+ color: #404040;
+}
+.card.arvados-object .card-info .desc {
+ display: block;
+ margin: 8px 14px 0 14px;
+ overflow: hidden;
+ font-size: 12px;
+ line-height: 16px;
+ color: #737373;
+ text-overflow: ellipsis;
+}
+.card.arvados-object .card-bottom {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ display: inline-block;
+ width: 100%;
+ padding: 10px 20px;
+ line-height: 29px;
+ text-align: center;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 41d5566..2e3a596 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -129,7 +129,9 @@ class ApplicationController < ActionController::Base
end
def create
- @object ||= model_class.new params[model_class.to_s.underscore.singularize]
+ new_resource_attrs = params[model_class.to_s.underscore.singularize].
+ reject { |k,v| k.to_s == 'uuid' }
+ @object ||= model_class.new new_resource_attrs
@object.save!
respond_to do |f|
diff --git a/apps/workbench/app/controllers/groups_controller.rb b/apps/workbench/app/controllers/groups_controller.rb
index b360b19..672fe90 100644
--- a/apps/workbench/app/controllers/groups_controller.rb
+++ b/apps/workbench/app/controllers/groups_controller.rb
@@ -5,4 +5,9 @@ class GroupsController < ApplicationController
@links_from = Link.where link_class: 'permission', tail_uuid: @group_uuids
@links_to = Link.where link_class: 'permission', head_uuid: @group_uuids
end
+
+ def show
+ @objects = @object.owned_items include_linked: true
+ super
+ end
end
diff --git a/apps/workbench/app/helpers/application_helper.rb b/apps/workbench/app/helpers/application_helper.rb
index b172313..d6b258b 100644
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@ -141,16 +141,29 @@ module ApplicationHelper
attrvalue = attrvalue.to_json if attrvalue.is_a? Hash or attrvalue.is_a? Array
+ ajax_options = {
+ "data-pk" => {
+ id: object.uuid,
+ key: object.class.to_s.underscore
+ }
+ }
+ if object.uuid
+ ajax_options['data-url'] = url_for(action: "update", id: object.uuid, controller: object.class.to_s.pluralize.underscore)
+ else
+ ajax_options['data-url'] = url_for(action: "create", controller: object.class.to_s.pluralize.underscore)
+ ajax_options['data-pk'][:defaults] = object.attributes
+ ajax_options['data-pk'][:_method] = 'post'
+ end
+ ajax_options['data-pk'] = ajax_options['data-pk'].to_json
+
link_to attrvalue.to_s, '#', {
"data-emptytext" => "none",
"data-placement" => "bottom",
"data-type" => input_type,
- "data-url" => url_for(action: "update", id: object.uuid, controller: object.class.to_s.pluralize.underscore),
"data-title" => "Update #{attr.gsub '_', ' '}",
"data-name" => attr,
- "data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
:class => "editable"
- }.merge(htmloptions)
+ }.merge(htmloptions).merge(ajax_options)
end
def render_pipeline_component_attribute(object, attr, subattr, value_info, htmloptions={})
diff --git a/apps/workbench/app/models/arvados_base.rb b/apps/workbench/app/models/arvados_base.rb
index 1cf0d1f..81732ba 100644
--- a/apps/workbench/app/models/arvados_base.rb
+++ b/apps/workbench/app/models/arvados_base.rb
@@ -299,6 +299,10 @@ class ArvadosBase < ActiveRecord::Base
(name if self.respond_to? :name) || uuid
end
+ def content_summary
+ self.class.to_s
+ end
+
def selection_label
friendly_link_name
end
diff --git a/apps/workbench/app/models/arvados_resource_list.rb b/apps/workbench/app/models/arvados_resource_list.rb
index ba3f0a0..a474b13 100644
--- a/apps/workbench/app/models/arvados_resource_list.rb
+++ b/apps/workbench/app/models/arvados_resource_list.rb
@@ -159,7 +159,7 @@ class ArvadosResourceList
end
def name_for item_or_uuid
- links_for(item_or_uuid, 'name').first.name
+ links_for(item_or_uuid, 'name').first.andand.name
end
end
diff --git a/apps/workbench/app/models/collection.rb b/apps/workbench/app/models/collection.rb
index 5460e9a..a63bf90 100644
--- a/apps/workbench/app/models/collection.rb
+++ b/apps/workbench/app/models/collection.rb
@@ -1,4 +1,5 @@
class Collection < ArvadosBase
+ include ApplicationHelper
MD5_EMPTY = 'd41d8cd98f00b204e9800998ecf8427e'
@@ -7,6 +8,10 @@ class Collection < ArvadosBase
!!locator.to_s.match("^#{MD5_EMPTY}(\\+.*)?\$")
end
+ def content_summary
+ human_readable_bytes_html(total_bytes) + " " + super
+ end
+
def total_bytes
if files
tot = 0
diff --git a/apps/workbench/app/views/groups/show.html.erb b/apps/workbench/app/views/groups/show.html.erb
index fdb460e..41541f8 100644
--- a/apps/workbench/app/views/groups/show.html.erb
+++ b/apps/workbench/app/views/groups/show.html.erb
@@ -2,91 +2,6 @@
.arvados-nav-container {
display:none;
}
-.card {
- padding-top: 20px;
- margin: 10px 0 20px 0;
- background-color: #ffffff;
- border: 1px solid #d8d8d8;
- border-top-width: 0;
- border-bottom-width: 2px;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-.card.arvados-object {
- position: relative;
- display: inline-block;
- width: 170px;
- height: 175px;
- padding-top: 0;
- margin-left: 20px;
- overflow: hidden;
- vertical-align: top;
-}
-.card.arvados-object .card-top.green {
- background-color: #53a93f;
-}
-.card.arvados-object .card-top.blue {
- background-color: #427fed;
-}
-.card.arvados-object .card-top {
- position: absolute;
- top: 0;
- left: 0;
- display: inline-block;
- width: 170px;
- height: 25px;
- background-color: #ffffff;
-}
-.card.arvados-object .card-info {
- position: absolute;
- top: 25px;
- display: inline-block;
- width: 100%;
- height: 101px;
- overflow: hidden;
- background: #ffffff;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-.card.arvados-object .card-info .title {
- display: block;
- margin: 8px 14px 0 14px;
- overflow: hidden;
- font-size: 16px;
- font-weight: bold;
- line-height: 18px;
- color: #404040;
-}
-.card.arvados-object .card-info .desc {
- display: block;
- margin: 8px 14px 0 14px;
- overflow: hidden;
- font-size: 12px;
- line-height: 16px;
- color: #737373;
- text-overflow: ellipsis;
-}
-.card.arvados-object .card-bottom {
- position: absolute;
- bottom: 0;
- left: 0;
- display: inline-block;
- width: 100%;
- padding: 10px 20px;
- line-height: 29px;
- text-align: center;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
<% end %>
<% content_for :above_left_nav do %>
@@ -241,74 +156,34 @@
</div>
<div class="panel-body">
<p>
- </p><table class="table">
+ </p>
+ <table class="table">
<tbody>
+ <colgroup>
+ <col width="30%" />
+ <col width="20%" />
+ <col width="20%" />
+ <col width="30%" />
+ </colgroup>
+ <% @objects.each do |object| %>
<tr>
<td>
- Some Subfolder
- </td>
- <td>
- 12 items
- </td>
- <td>
- 2014-04-01
- </td>
- </tr>
- <tr>
- <td>
- Test Dataset
+ <% name_link = @objects.links_for(object, 'name').first || Link.new(link_class: "name", owner_uuid: @object.uuid, tail_uuid: @object.uuid, head_uuid: object.uuid, name: "") %>
+ <%= render_editable_attribute name_link, 'name', nil, { 'data-emptytext' => "Unnamed #{object.class}" } %>
</td>
<td>
- 4 GiB
+ <%= object.content_summary %>
</td>
- <td>
- 2014-04-01
+ <td title="<%= object.modified_at %>">
+ <span>
+ <%= raw distance_of_time_in_words(object.modified_at, Time.now).sub('about ','~').sub(' ',' ') + ' ago' %>
+ </span>
</td>
- </tr>
- <tr>
- <td>
- Test Dataset 2
- </td>
- <td>
- 4 GiB
- </td>
- <td>
- 2014-04-01
- </td>
- </tr>
- <tr>
- <td>
- GATK Exome Pipeline
- </td>
- <td>
- 7 components
- </td>
- <td>
- 2014-03-21
- </td>
- </tr>
- <tr>
- <td>
- Reference result
- </td>
- <td>
- 250 MiB
- </td>
- <td>
- 2014-03-22
- </td>
- </tr>
- <tr>
- <td>
- Some other thing
- </td>
- <td>
- 1.2 TiB
- </td>
- <td>
- 2014-01-01
+ <td class="arvados-uuid">
+ <%= link_to_if_arvados_object(object, {no_tags: true}) %>
</td>
</tr>
+ <% end %>
</tbody>
<thead>
<tr>
@@ -320,6 +195,9 @@
<th>
Modified
</th>
+ <th>
+ UUID
+ </th>
</tr>
</thead>
</table>
commit 7e8f99556391cc81c014b517a9fa6efed8fe8113
Author: Brett Smith <brett at curoverse.com>
Date: Mon Apr 28 13:01:18 2014 -0400
api: Support filters in API client auths index.
Per comments on Refs #1904. filters is generally the preferred way to
do searching now. I maintained existing limits on what can be
searched with this method.
diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 9674bb7..73d1405 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -208,6 +208,10 @@ class ApplicationController < ActionController::Base
end
end
+ def default_orders
+ ["#{table_name}.modified_at desc"]
+ end
+
def load_limit_offset_order_params
if params[:limit]
unless params[:limit].to_s.match(/^\d+$/)
@@ -240,7 +244,7 @@ class ApplicationController < ActionController::Base
end
end
if @orders.empty?
- @orders << "#{table_name}.modified_at desc"
+ @orders = default_orders
end
end
diff --git a/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb b/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
index ff322a7..dc95b2f 100644
--- a/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
+++ b/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
@@ -34,21 +34,34 @@ class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
protected
+ def default_orders
+ ["#{table_name}.created_at desc"]
+ end
+
def find_objects_for_index
# Here we are deliberately less helpful about searching for client
- # authorizations. Rather than use the generic index/where/order
- # features, we look up tokens belonging to the current user and
- # filter by exact match on api_token (which we expect in the form
- # of a where[uuid] parameter to make things easier for API client
- # libraries).
+ # authorizations. We look up tokens belonging to the current user
+ # and filter by exact matches on api_token and scopes.
+ wanted_scopes = []
+ if @filters
+ wanted_scopes.concat(@filters.map { |attr, operator, operand|
+ ((attr == 'scopes') and (operator == '=')) ? operand : nil
+ })
+ @filters.select! { |attr, operator, operand|
+ (attr == 'uuid') and (operator == '=')
+ }
+ end
+ if @where
+ wanted_scopes << @where['scopes']
+ @where.select! { |attr, val| attr == 'uuid' }
+ end
@objects = model_class.
includes(:user, :api_client).
- where('user_id=? and (? or api_token=?)', current_user.id, !@where['uuid'], @where['uuid']).
- order('created_at desc')
- unless @where['scopes'].nil?
- @objects = @objects.select { |auth|
- (auth.scopes & @where['scopes']) == (auth.scopes | @where['scopes'])
- }
+ where('user_id=?', current_user.id)
+ super
+ wanted_scopes.compact.each do |scope_list|
+ sorted_scopes = scope_list.sort
+ @objects = @objects.select { |auth| auth.scopes.sort == sorted_scopes }
end
end
diff --git a/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb b/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
index 0072792..8877719 100644
--- a/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
@@ -37,22 +37,33 @@ class Arvados::V1::ApiClientAuthorizationsControllerTest < ActionController::Tes
assert_response 403
end
- test "admin search filters where scopes exactly match" do
- def check_tokens_by_scopes(scopes, *expected_tokens)
- expected_tokens.map! { |name| api_client_authorizations(name).api_token }
- get :index, where: {scopes: scopes}
- assert_response :success
- got_tokens = JSON.parse(@response.body)['items']
- .map { |auth| auth['api_token'] }
- assert_equal(expected_tokens.sort, got_tokens.sort,
- "wrong results for scopes = #{scopes}")
+ def assert_found_tokens(auth, search_params, *expected_tokens)
+ authorize_with auth
+ expected_tokens.map! { |name| api_client_authorizations(name).api_token }
+ get :index, search_params
+ assert_response :success
+ got_tokens = JSON.parse(@response.body)['items']
+ .map { |auth| auth['api_token'] }
+ assert_equal(expected_tokens.sort, got_tokens.sort,
+ "wrong results for #{search_params.inspect}")
+ end
+
+ # Three-tuples with auth to use, scopes to find, and expected tokens.
+ # Make two tests for each tuple, one searching with where and the other
+ # with filter.
+ [[:admin_trustedclient, [], :admin_noscope],
+ [:active_trustedclient, ["GET /arvados/v1/users"], :active_userlist],
+ [:active_trustedclient,
+ ["POST /arvados/v1/api_client_authorizations",
+ "GET /arvados/v1/api_client_authorizations"],
+ :active_apitokens],
+ ].each do |auth, scopes, *expected|
+ test "#{auth.to_s} can find auths where scopes=#{scopes.inspect}" do
+ assert_found_tokens(auth, {where: {scopes: scopes}}, *expected)
+ end
+
+ test "#{auth.to_s} can find auths filtered with scopes=#{scopes.inspect}" do
+ assert_found_tokens(auth, {filters: [['scopes', '=', scopes]]}, *expected)
end
- authorize_with :admin_trustedclient
- check_tokens_by_scopes([], :admin_noscope)
- authorize_with :active_trustedclient
- check_tokens_by_scopes(["GET /arvados/v1/users"], :active_userlist)
- check_tokens_by_scopes(["POST /arvados/v1/api_client_authorizations",
- "GET /arvados/v1/api_client_authorizations"],
- :active_apitokens)
end
end
commit 2f3e496712802324e5d184f9ae59866df1772ef0
Author: Brett Smith <brett at curoverse.com>
Date: Wed Apr 23 16:15:37 2014 -0400
api: Support scope searching in API token index.
diff --git a/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb b/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
index 8fd915d..ff322a7 100644
--- a/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
+++ b/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
@@ -45,6 +45,11 @@ class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
includes(:user, :api_client).
where('user_id=? and (? or api_token=?)', current_user.id, !@where['uuid'], @where['uuid']).
order('created_at desc')
+ unless @where['scopes'].nil?
+ @objects = @objects.select { |auth|
+ (auth.scopes & @where['scopes']) == (auth.scopes | @where['scopes'])
+ }
+ end
end
def find_object_by_uuid
diff --git a/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb b/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
index cbb0096..0072792 100644
--- a/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/api_client_authorizations_controller_test.rb
@@ -1,7 +1,6 @@
require 'test_helper'
class Arvados::V1::ApiClientAuthorizationsControllerTest < ActionController::TestCase
-
test "should get index" do
authorize_with :active_trustedclient
get :index
@@ -38,4 +37,22 @@ class Arvados::V1::ApiClientAuthorizationsControllerTest < ActionController::Tes
assert_response 403
end
+ test "admin search filters where scopes exactly match" do
+ def check_tokens_by_scopes(scopes, *expected_tokens)
+ expected_tokens.map! { |name| api_client_authorizations(name).api_token }
+ get :index, where: {scopes: scopes}
+ assert_response :success
+ got_tokens = JSON.parse(@response.body)['items']
+ .map { |auth| auth['api_token'] }
+ assert_equal(expected_tokens.sort, got_tokens.sort,
+ "wrong results for scopes = #{scopes}")
+ end
+ authorize_with :admin_trustedclient
+ check_tokens_by_scopes([], :admin_noscope)
+ authorize_with :active_trustedclient
+ check_tokens_by_scopes(["GET /arvados/v1/users"], :active_userlist)
+ check_tokens_by_scopes(["POST /arvados/v1/api_client_authorizations",
+ "GET /arvados/v1/api_client_authorizations"],
+ :active_apitokens)
+ end
end
commit bf15373590e21dafd696fa0c10906eb653610d1d
Author: Brett Smith <brett at curoverse.com>
Date: Mon Apr 28 14:01:53 2014 -0400
api: Migrate VM auth scopes to new system.
VirtualMachinesController was the only one doing anything special with
API token scopes before we provided the more general-purpose system.
This commit removes its specialized code, and provides a database
migration to convert those specialized scopes to the general-purpose
schema.
diff --git a/services/api/app/controllers/arvados/v1/virtual_machines_controller.rb b/services/api/app/controllers/arvados/v1/virtual_machines_controller.rb
index 10b4bd8..e176348 100644
--- a/services/api/app/controllers/arvados/v1/virtual_machines_controller.rb
+++ b/services/api/app/controllers/arvados/v1/virtual_machines_controller.rb
@@ -1,12 +1,8 @@
class Arvados::V1::VirtualMachinesController < ApplicationController
skip_before_filter :find_object_by_uuid, :only => :get_all_logins
skip_before_filter :render_404_if_no_object, :only => :get_all_logins
- skip_before_filter(:require_auth_scope_all,
- :only => [:logins, :get_all_logins])
before_filter(:admin_required,
:only => [:logins, :get_all_logins])
- before_filter(:require_auth_scope_for_get_all_logins,
- :only => [:logins, :get_all_logins])
def logins
get_all_logins
@@ -44,16 +40,4 @@ class Arvados::V1::VirtualMachinesController < ApplicationController
end
render json: { kind: "arvados#HashList", items: @response }
end
-
- protected
-
- def require_auth_scope_for_get_all_logins
- if @object
- # Client wants all logins for a single VM.
- require_auth_scope(['all', arvados_v1_virtual_machine_url(@object.uuid)])
- else
- # ...for a non-existent VM, or all VMs.
- require_auth_scope(['all'])
- end
- end
end
diff --git a/services/api/db/migrate/20140423133559_new_scope_format.rb b/services/api/db/migrate/20140423133559_new_scope_format.rb
new file mode 100644
index 0000000..5b69e95
--- /dev/null
+++ b/services/api/db/migrate/20140423133559_new_scope_format.rb
@@ -0,0 +1,48 @@
+# At the time we introduced scopes everywhere, VirtualMachinesController
+# recognized scopes that gave the URL for a VM to grant access to that VM's
+# login list. This migration converts those VM-specific scopes to the new
+# general format, and back.
+
+class NewScopeFormat < ActiveRecord::Migration
+ include CurrentApiClient
+
+ VM_PATH_REGEX =
+ %r{(/arvados/v1/virtual_machines/[0-9a-z]{5}-[0-9a-z]{5}-[0-9a-z]{15})}
+ OLD_SCOPE_REGEX = %r{^https?://[^/]+#{VM_PATH_REGEX.source}$}
+ NEW_SCOPE_REGEX = %r{^GET #{VM_PATH_REGEX.source}/logins$}
+
+ def fix_scopes_matching(regex)
+ act_as_system_user
+ ApiClientAuthorization.find_each do |auth|
+ auth.scopes = auth.scopes.map do |scope|
+ if match = regex.match(scope)
+ yield match
+ else
+ scope
+ end
+ end
+ auth.save!
+ end
+ end
+
+ def up
+ fix_scopes_matching(OLD_SCOPE_REGEX) do |match|
+ "GET #{match[1]}/logins"
+ end
+ end
+
+ def down
+ case Rails.env
+ when 'test'
+ hostname = 'www.example.com'
+ else
+ require 'socket'
+ hostname = Socket.gethostname
+ end
+ fix_scopes_matching(NEW_SCOPE_REGEX) do |match|
+ Rails.application.routes.url_for(controller: 'virtual_machines',
+ uuid: match[1].split('/').last,
+ host: hostname, protocol: 'https')
+ end
+ end
+end
diff --git a/services/api/db/schema.rb b/services/api/db/schema.rb
index 988cb87..034ee35 100644
--- a/services/api/db/schema.rb
+++ b/services/api/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20140422011506) do
+ActiveRecord::Schema.define(:version => 20140423133559) do
create_table "api_client_authorizations", :force => true do |t|
t.string "api_token", :null => false
diff --git a/services/api/test/fixtures/api_client_authorizations.yml b/services/api/test/fixtures/api_client_authorizations.yml
index 77e5048..f772c4f 100644
--- a/services/api/test/fixtures/api_client_authorizations.yml
+++ b/services/api/test/fixtures/api_client_authorizations.yml
@@ -42,7 +42,7 @@ admin_vm:
api_token: adminvirtualmachineabcdefghijklmnopqrstuvwxyz12345
expires_at: 2038-01-01 00:00:00
# scope refers to the testvm fixture.
- scopes: ["https://www.example.com/arvados/v1/virtual_machines/zzzzz-2x53u-382brsig8rp3064"]
+ scopes: ["GET /arvados/v1/virtual_machines/zzzzz-2x53u-382brsig8rp3064/logins"]
admin_noscope:
api_client: untrusted
commit 8086f73aca674d7533e88bdd3850042553487d2b
Author: Brett Smith <brett at curoverse.com>
Date: Tue Apr 22 17:45:46 2014 -0400
api: Introduce path-based API token scopes.
Refs #1904, #2662 for background discussion.
diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 0f144d8..9674bb7 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -7,7 +7,7 @@ class ApplicationController < ActionController::Base
around_filter :thread_with_auth_info, :except => [:render_error, :render_not_found]
before_filter :remote_ip
- before_filter :require_auth_scope_all, :except => :render_not_found
+ before_filter :require_auth_scope, :except => :render_not_found
before_filter :catch_redirect_hint
before_filter :find_object_by_uuid, :except => [:index, :create,
@@ -406,12 +406,9 @@ class ApplicationController < ActionController::Base
end
end
- def require_auth_scope_all
- require_login and require_auth_scope(['all'])
- end
-
- def require_auth_scope(ok_scopes)
- unless current_api_client_auth_has_scope(ok_scopes)
+ def require_auth_scope
+ return false unless require_login
+ unless current_api_client_auth_has_scope("#{request.method} #{request.path}")
render :json => { errors: ['Forbidden'] }.to_json, status: 403
end
end
diff --git a/services/api/app/controllers/arvados/v1/keep_disks_controller.rb b/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
index 3d91916..47018d4 100644
--- a/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
+++ b/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
@@ -1,5 +1,5 @@
class Arvados::V1::KeepDisksController < ApplicationController
- skip_before_filter :require_auth_scope_all, :only => :ping
+ skip_before_filter :require_auth_scope, :only => :ping
def self._ping_requires_parameters
{
diff --git a/services/api/app/controllers/arvados/v1/nodes_controller.rb b/services/api/app/controllers/arvados/v1/nodes_controller.rb
index eda8b07..d7a477d 100644
--- a/services/api/app/controllers/arvados/v1/nodes_controller.rb
+++ b/services/api/app/controllers/arvados/v1/nodes_controller.rb
@@ -1,5 +1,5 @@
class Arvados::V1::NodesController < ApplicationController
- skip_before_filter :require_auth_scope_all, :only => :ping
+ skip_before_filter :require_auth_scope, :only => :ping
skip_before_filter :find_object_by_uuid, :only => :ping
skip_before_filter :render_404_if_no_object, :only => :ping
diff --git a/services/api/app/controllers/arvados/v1/schema_controller.rb b/services/api/app/controllers/arvados/v1/schema_controller.rb
index 1cc8496..625519e 100644
--- a/services/api/app/controllers/arvados/v1/schema_controller.rb
+++ b/services/api/app/controllers/arvados/v1/schema_controller.rb
@@ -2,7 +2,7 @@ class Arvados::V1::SchemaController < ApplicationController
skip_before_filter :find_objects_for_index
skip_before_filter :find_object_by_uuid
skip_before_filter :render_404_if_no_object
- skip_before_filter :require_auth_scope_all
+ skip_before_filter :require_auth_scope
def index
expires_in 24.hours, public: true
@@ -69,7 +69,7 @@ class Arvados::V1::SchemaController < ApplicationController
schemas: {},
resources: {}
}
-
+
ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
begin
ctl_class = "Arvados::V1::#{k.to_s.pluralize}Controller".constantize
@@ -175,7 +175,7 @@ class Arvados::V1::SchemaController < ApplicationController
description:
%|List #{k.to_s.pluralize}.
- The <code>list</code> method returns a
+ The <code>list</code> method returns a
<a href="/api/resources.html">resource list</a> of
matching #{k.to_s.pluralize}. For example:
diff --git a/services/api/app/controllers/static_controller.rb b/services/api/app/controllers/static_controller.rb
index fda0880..c71b850 100644
--- a/services/api/app/controllers/static_controller.rb
+++ b/services/api/app/controllers/static_controller.rb
@@ -3,7 +3,7 @@ class StaticController < ApplicationController
skip_before_filter :find_object_by_uuid
skip_before_filter :render_404_if_no_object
- skip_before_filter :require_auth_scope_all, :only => [ :home, :login_failure ]
+ skip_before_filter :require_auth_scope, :only => [ :home, :login_failure ]
def home
if Rails.configuration.respond_to? :workbench_address
diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb
index a7391bd..3d4b05a 100644
--- a/services/api/app/controllers/user_sessions_controller.rb
+++ b/services/api/app/controllers/user_sessions_controller.rb
@@ -1,5 +1,5 @@
class UserSessionsController < ApplicationController
- before_filter :require_auth_scope_all, :only => [ :destroy ]
+ before_filter :require_auth_scope, :only => [ :destroy ]
skip_before_filter :find_object_by_uuid
skip_before_filter :render_404_if_no_object
diff --git a/services/api/lib/current_api_client.rb b/services/api/lib/current_api_client.rb
index bbba4dc..0803d54 100644
--- a/services/api/lib/current_api_client.rb
+++ b/services/api/lib/current_api_client.rb
@@ -29,14 +29,17 @@ module CurrentApiClient
Thread.current[:api_client_ip_address]
end
- # Does the current API client authorization include any of ok_scopes?
- def current_api_client_auth_has_scope(ok_scopes)
- auth_scopes = current_api_client_authorization.andand.scopes || []
- unless auth_scopes.index('all') or (auth_scopes & ok_scopes).any?
- logger.warn "Insufficient auth scope: need #{ok_scopes}, #{current_api_client_authorization.inspect} has #{auth_scopes}"
- return false
- end
- true
+ # Is the current API client authorization scoped for the request?
+ def current_api_client_auth_has_scope(req_s)
+ (current_api_client_authorization.andand.scopes || []).select { |scope|
+ if scope == 'all'
+ true
+ elsif scope.end_with? '/'
+ req_s.start_with? scope
+ else
+ req_s == scope
+ end
+ }.any?
end
def system_user_uuid
diff --git a/services/api/test/fixtures/api_client_authorizations.yml b/services/api/test/fixtures/api_client_authorizations.yml
index 5a715e3..77e5048 100644
--- a/services/api/test/fixtures/api_client_authorizations.yml
+++ b/services/api/test/fixtures/api_client_authorizations.yml
@@ -51,6 +51,28 @@ admin_noscope:
expires_at: 2038-01-01 00:00:00
scopes: []
+active_userlist:
+ api_client: untrusted
+ user: active
+ api_token: activeuserlistabcdefghijklmnopqrstuvwxyz1234568900
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /arvados/v1/users"]
+
+active_specimens:
+ api_client: untrusted
+ user: active
+ api_token: activespecimensabcdefghijklmnopqrstuvwxyz123456890
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /arvados/v1/specimens/"]
+
+active_apitokens:
+ api_client: trusted_workbench
+ user: active
+ api_token: activeapitokensabcdefghijklmnopqrstuvwxyz123456789
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /arvados/v1/api_client_authorizations",
+ "POST /arvados/v1/api_client_authorizations"]
+
spectator:
api_client: untrusted
user: spectator
diff --git a/services/api/test/integration/api_client_authorizations_scopes_test.rb b/services/api/test/integration/api_client_authorizations_scopes_test.rb
index 7269d38..ba91670 100644
--- a/services/api/test/integration/api_client_authorizations_scopes_test.rb
+++ b/services/api/test/integration/api_client_authorizations_scopes_test.rb
@@ -20,7 +20,6 @@ class Arvados::V1::ApiTokensScopeTest < ActionController::IntegrationTest
end
def request_with_auth(method, path, params={})
- https!
send(method, path, @token.merge(params))
end
@@ -32,6 +31,51 @@ class Arvados::V1::ApiTokensScopeTest < ActionController::IntegrationTest
request_with_auth(:post_via_redirect, *args)
end
+ test "user list token can only list users" do
+ auth_with :active_userlist
+ get_with_auth v1_url('users')
+ assert_response :success
+ get_with_auth v1_url('users', '') # Add trailing slash.
+ assert_response :success
+ get_with_auth v1_url('users', 'current')
+ assert_response 403
+ get_with_auth v1_url('virtual_machines')
+ assert_response 403
+ end
+
+ test "specimens token can see exactly owned specimens" do
+ auth_with :active_specimens
+ get_with_auth v1_url('specimens')
+ assert_response 403
+ get_with_auth v1_url('specimens', specimens(:owned_by_active_user).uuid)
+ assert_response :success
+ get_with_auth v1_url('specimens', specimens(:owned_by_spectator).uuid)
+ assert_includes(403..404, @response.status)
+ end
+
+ test "token with multiple scopes can use them all" do
+ def get_token_count
+ get_with_auth v1_url('api_client_authorizations')
+ assert_response :success
+ token_count = JSON.parse(@response.body)['items_available']
+ assert_not_nil(token_count, "could not find token count")
+ token_count
+ end
+ auth_with :active_apitokens
+ # Test the GET scope.
+ token_count = get_token_count
+ # Test the POST scope.
+ post_with_auth(v1_url('api_client_authorizations'),
+ api_client_authorization: {user_id: users(:active).id})
+ assert_response :success
+ assert_equal(token_count + 1, get_token_count,
+ "token count suggests POST was not accepted")
+ # Test other requests are denied.
+ get_with_auth v1_url('api_client_authorizations',
+ api_client_authorizations(:active_apitokens).uuid)
+ assert_response 403
+ end
+
test "token without scope has no access" do
# Logs are good for this test, because logs have relatively
# few access controls enforced at the model level.
commit 8b77f66275fd87f70dd79075a71d8062311541bc
Author: Brett Smith <brett at curoverse.com>
Date: Tue Apr 22 14:44:34 2014 -0400
api: Test VM login scopes.
The virtual machine controller is the only one doing anything
interesting with API token scopes right now. I'm writing this test
for that functionality to make sure it stays effective through
refactoring.
diff --git a/services/api/test/fixtures/api_client_authorizations.yml b/services/api/test/fixtures/api_client_authorizations.yml
index 5cada90..5a715e3 100644
--- a/services/api/test/fixtures/api_client_authorizations.yml
+++ b/services/api/test/fixtures/api_client_authorizations.yml
@@ -36,6 +36,21 @@ active_trustedclient:
api_token: 27bnddk6x2nmq00a1e3gq43n9tsl5v87a3faqar2ijj8tud5en
expires_at: 2038-01-01 00:00:00
+admin_vm:
+ api_client: untrusted
+ user: admin
+ api_token: adminvirtualmachineabcdefghijklmnopqrstuvwxyz12345
+ expires_at: 2038-01-01 00:00:00
+ # scope refers to the testvm fixture.
+ scopes: ["https://www.example.com/arvados/v1/virtual_machines/zzzzz-2x53u-382brsig8rp3064"]
+
+admin_noscope:
+ api_client: untrusted
+ user: admin
+ api_token: adminnoscopeabcdefghijklmnopqrstuvwxyz123456789012
+ expires_at: 2038-01-01 00:00:00
+ scopes: []
+
spectator:
api_client: untrusted
user: spectator
diff --git a/services/api/test/integration/api_client_authorizations_scopes_test.rb b/services/api/test/integration/api_client_authorizations_scopes_test.rb
new file mode 100644
index 0000000..7269d38
--- /dev/null
+++ b/services/api/test/integration/api_client_authorizations_scopes_test.rb
@@ -0,0 +1,59 @@
+# The v1 API uses token scopes to control access to the REST API at the path
+# level. This is enforced in the base ApplicationController, making it a
+# functional test that we can run against many different controllers.
+
+require 'test_helper'
+
+class Arvados::V1::ApiTokensScopeTest < ActionController::IntegrationTest
+ fixtures :all
+
+ def setup
+ @token = {}
+ end
+
+ def auth_with(name)
+ @token = {api_token: api_client_authorizations(name).api_token}
+ end
+
+ def v1_url(*parts)
+ (['arvados', 'v1'] + parts).join('/')
+ end
+
+ def request_with_auth(method, path, params={})
+ https!
+ send(method, path, @token.merge(params))
+ end
+
+ def get_with_auth(*args)
+ request_with_auth(:get_via_redirect, *args)
+ end
+
+ def post_with_auth(*args)
+ request_with_auth(:post_via_redirect, *args)
+ end
+
+ test "token without scope has no access" do
+ # Logs are good for this test, because logs have relatively
+ # few access controls enforced at the model level.
+ auth_with :admin_noscope
+ get_with_auth v1_url('logs')
+ assert_response 403
+ get_with_auth v1_url('logs', logs(:log1).uuid)
+ assert_response 403
+ post_with_auth(v1_url('logs'), log: {})
+ assert_response 403
+ end
+
+ test "VM login scopes work" do
+ # A system administration script makes an API token with limited scope
+ # for virtual machines to let it see logins.
+ def vm_logins_url(name)
+ v1_url('virtual_machines', virtual_machines(name).uuid, 'logins')
+ end
+ auth_with :admin_vm
+ get_with_auth vm_logins_url(:testvm)
+ assert_response :success
+ get_with_auth vm_logins_url(:testvm2)
+ assert(@response.status >= 400, "getting testvm2 logins should have failed")
+ end
+end
commit 57dc9e64bb38f186e2b235a98d7437a5f986bc83
Author: Brett Smith <brett at curoverse.com>
Date: Mon Apr 28 11:03:22 2014 -0400
api: Shorten name of authorized_keys index.
I had trouble running the new TimestampsNotNull migration, because I
ran into the index name limit described in the migration comments.
Running this migration first worked around the problem for me, and I
hope it saves others from tripping over it too.
diff --git a/services/api/db/migrate/20140421151939_rename_auth_keys_user_index.rb b/services/api/db/migrate/20140421151939_rename_auth_keys_user_index.rb
new file mode 100644
index 0000000..2b057f0
--- /dev/null
+++ b/services/api/db/migrate/20140421151939_rename_auth_keys_user_index.rb
@@ -0,0 +1,11 @@
+class RenameAuthKeysUserIndex < ActiveRecord::Migration
+ # Rails' default name for this index is so long, Rails can't modify
+ # the index later, because the autogenerated temporary name exceeds
+ # PostgreSQL's 64-character limit. This migration gives the index
+ # an explicit name to work around that issue.
+ def change
+ rename_index("authorized_keys",
+ "index_authorized_keys_on_authorized_user_uuid_and_expires_at",
+ "index_authkeys_on_user_and_expires_at")
+ end
+end
diff --git a/services/api/db/schema.rb b/services/api/db/schema.rb
index af751fa..988cb87 100644
--- a/services/api/db/schema.rb
+++ b/services/api/db/schema.rb
@@ -64,7 +64,7 @@ ActiveRecord::Schema.define(:version => 20140422011506) do
t.datetime "updated_at", :null => false
end
- add_index "authorized_keys", ["authorized_user_uuid", "expires_at"], :name => "index_authorized_keys_on_authorized_user_uuid_and_expires_at"
+ add_index "authorized_keys", ["authorized_user_uuid", "expires_at"], :name => "index_authkeys_on_user_and_expires_at"
add_index "authorized_keys", ["uuid"], :name => "index_authorized_keys_on_uuid", :unique => true
create_table "collections", :force => true do |t|
commit d7ccebe29c68df51633f6a18eba6aa6a982c3739
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 14 11:44:23 2014 -0400
Render group with folder view
diff --git a/apps/workbench/app/views/groups/show.html.erb b/apps/workbench/app/views/groups/show.html.erb
new file mode 100644
index 0000000..fdb460e
--- /dev/null
+++ b/apps/workbench/app/views/groups/show.html.erb
@@ -0,0 +1,330 @@
+<% content_for :css do %>
+.arvados-nav-container {
+ display:none;
+}
+.card {
+ padding-top: 20px;
+ margin: 10px 0 20px 0;
+ background-color: #ffffff;
+ border: 1px solid #d8d8d8;
+ border-top-width: 0;
+ border-bottom-width: 2px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.card.arvados-object {
+ position: relative;
+ display: inline-block;
+ width: 170px;
+ height: 175px;
+ padding-top: 0;
+ margin-left: 20px;
+ overflow: hidden;
+ vertical-align: top;
+}
+.card.arvados-object .card-top.green {
+ background-color: #53a93f;
+}
+.card.arvados-object .card-top.blue {
+ background-color: #427fed;
+}
+.card.arvados-object .card-top {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 170px;
+ height: 25px;
+ background-color: #ffffff;
+}
+.card.arvados-object .card-info {
+ position: absolute;
+ top: 25px;
+ display: inline-block;
+ width: 100%;
+ height: 101px;
+ overflow: hidden;
+ background: #ffffff;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.card.arvados-object .card-info .title {
+ display: block;
+ margin: 8px 14px 0 14px;
+ overflow: hidden;
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 18px;
+ color: #404040;
+}
+.card.arvados-object .card-info .desc {
+ display: block;
+ margin: 8px 14px 0 14px;
+ overflow: hidden;
+ font-size: 12px;
+ line-height: 16px;
+ color: #737373;
+ text-overflow: ellipsis;
+}
+.card.arvados-object .card-bottom {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ display: inline-block;
+ width: 100%;
+ padding: 10px 20px;
+ line-height: 29px;
+ text-align: center;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+<% end %>
+
+<% content_for :above_left_nav do %>
+<div class="panel panel-info">
+ <div class="panel-heading">
+ <a class="btn btn-xs btn-info pull-right">
+ Rename
+ </a>
+ <h3 class="panel-title">
+ <%= @object.name %>
+ </h3>
+ </div>
+ <div class="panel-body">
+ <img src="/favicon.ico" class="pull-right" alt=""/>
+ <p>
+ This folder was created <%= @object.created_at %>. (This
+ description defaults to something generic.)
+ </p>
+ <a href="#" class="btn btn-xs btn-info">Edit description</a>
+ </div>
+</div>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ Activity
+ </h3>
+ </div>
+ <div class="panel-body">
+ <input type="text" class="form-control" placeholder="Search"/>
+ <div style="height:0.5em;"></div>
+ <p>
+ 11:12 - Some Subfolder added
+ </p>
+ <p>
+ 10:06 - <%= @object.name %> - renamed from OldFolderName to <%= @object.name %>
+ </p>
+ <p>
+ 10:01 - Test Dataset (4 GiB collection) added to <%= @object.name %> by <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
+ </p>
+ </div>
+</div>
+
+<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 style="height:0.5em;"></div>
+ <table class="table table-condensed">
+ <tbody>
+ <tr>
+ <td><%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %></td>
+ <td>Owner</td>
+ </tr>
+ <tr>
+ <td>Someone Else</td>
+ <td>read only</td>
+ </tr>
+ <tr>
+ <td>Someone Else</td>
+ <td>read+write</td>
+ </tr>
+ </tbody>
+ <thead><tr><th>User</th><th>Role</th></tr></thead>
+ </table>
+ </div>
+</div>
+<% end %>
+
+<div class="row">
+ <div class="card arvados-object">
+ <div class="card-top green">
+ <a href="#">
+ <img src="/favicon.ico" alt=""/>
+ </a>
+ </div>
+ <div class="card-info">
+ <a class="title" href="#">Test dataset</a>
+ <div class="desc">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
+ </div>
+ <div class="card-bottom">
+ <button class="btn btn-default btn-block">Show details</button>
+ </div>
+ </div>
+
+ <div class="card arvados-object">
+ <div class="card-top green">
+ <a href="#">
+ <img src="/favicon.ico" alt=""/>
+ </a>
+ </div>
+ <div class="card-info">
+ <a class="title" href="#">GATK Exome Pipeline</a>
+ <div class="desc">exome p.e. fastq -> vcf</div>
+ </div>
+ <div class="card-bottom">
+ <button class="btn btn-default btn-block">Show details</button>
+ </div>
+ </div>
+
+ <div class="card arvados-object">
+ <div class="card-top blue">
+ <a href="#">
+ <img src="/favicon.ico" alt=""/>
+ </a>
+ </div>
+ <div class="card-info">
+ <a class="title" href="#">Reference result</a>
+ <div class="desc">Known good -- checked concordance against other datasets</div>
+ </div>
+ <div class="card-bottom">
+ <button class="btn btn-default btn-block">Show details</button>
+ </div>
+ </div>
+
+ <div class="card arvados-object">
+ <div class="card-top blue">
+ <a href="#">
+ <img src="/favicon.ico" alt=""/>
+ </a>
+ </div>
+ <div class="card-info">
+ <a class="title" href="#">Some other thing</a>
+ <div class="desc">(Objects that are "starred" appear in this area)</div>
+ </div>
+ <div class="card-bottom">
+ <button class="btn btn-default btn-block">Show details</button>
+ </div>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-info">
+ <div class="panel-heading">
+ <div class="row">
+ <div class="col-md-6">
+ <h3 class="panel-title" style="vertical-align:middle;">
+ Contents
+ </h3>
+ </div>
+ <div class="col-md-6">
+ <div class="input-group input-group-sm pull-right">
+ <input type="text" class="form-control" placeholder="Search folder contents"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="panel-body">
+ <p>
+ </p><table class="table">
+ <tbody>
+ <tr>
+ <td>
+ Some Subfolder
+ </td>
+ <td>
+ 12 items
+ </td>
+ <td>
+ 2014-04-01
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Test Dataset
+ </td>
+ <td>
+ 4 GiB
+ </td>
+ <td>
+ 2014-04-01
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Test Dataset 2
+ </td>
+ <td>
+ 4 GiB
+ </td>
+ <td>
+ 2014-04-01
+ </td>
+ </tr>
+ <tr>
+ <td>
+ GATK Exome Pipeline
+ </td>
+ <td>
+ 7 components
+ </td>
+ <td>
+ 2014-03-21
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Reference result
+ </td>
+ <td>
+ 250 MiB
+ </td>
+ <td>
+ 2014-03-22
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Some other thing
+ </td>
+ <td>
+ 1.2 TiB
+ </td>
+ <td>
+ 2014-01-01
+ </td>
+ </tr>
+ </tbody>
+ <thead>
+ <tr>
+ <th>
+ Name
+ </th>
+ <th>
+ </th>
+ <th>
+ Modified
+ </th>
+ </tr>
+ </thead>
+ </table>
+ <p></p>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index 9da171e..ece2f2e 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -178,6 +178,7 @@
</div>
</div>
<div class="col-sm-3 left-nav">
+ <%= yield :above_left_nav %>
<div class="arvados-nav-container">
<% if current_user.andand.is_active %>
<div class="well">
commit d649a716392760cd394e18a628dc23aaec5fa3b3
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 28 20:10:17 2014 -0400
Fix patch_paging_vars args.
diff --git a/apps/workbench/app/controllers/api_client_authorizations_controller.rb b/apps/workbench/app/controllers/api_client_authorizations_controller.rb
index 24b4ae3..8385b6b 100644
--- a/apps/workbench/app/controllers/api_client_authorizations_controller.rb
+++ b/apps/workbench/app/controllers/api_client_authorizations_controller.rb
@@ -7,7 +7,7 @@ class ApiClientAuthorizationsController < ApplicationController
filtered = m.to_ary.reject do |x|
x.api_client_id == 0 or (x.expires_at and x.expires_at < Time.now) rescue false
end
- ArvadosApiClient::patch_paging_vars(filtered, items_available, offset, limit)
+ ArvadosApiClient::patch_paging_vars(filtered, items_available, offset, limit, nil)
@objects = ArvadosResourceList.new(ApiClientAuthorization)
@objects.results= filtered
super
diff --git a/apps/workbench/app/models/arvados_api_client.rb b/apps/workbench/app/models/arvados_api_client.rb
index bc07b88..d6e385f 100644
--- a/apps/workbench/app/models/arvados_api_client.rb
+++ b/apps/workbench/app/models/arvados_api_client.rb
@@ -90,7 +90,7 @@ class ArvadosApiClient
resp
end
- def self.patch_paging_vars(ary, items_available, offset, limit, links)
+ def self.patch_paging_vars(ary, items_available, offset, limit, links=nil)
if items_available
(class << ary; self; end).class_eval { attr_accessor :items_available }
ary.items_available = items_available
commit 50b746d9246c19c1ad2cf506bb18a0eb8ddd0755
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 28 19:31:48 2014 -0400
Convert joins to subqueries to fix duplicates in owned_items
diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 8e15a07..0f144d8 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -95,15 +95,9 @@ class ApplicationController < ActionController::Base
cond_sql = "#{klass.table_name}.owner_uuid = ?"
cond_params = [@object.uuid]
if params[:include_linked]
- @objects = @objects.
- joins("LEFT JOIN links namelinks"\
- " ON namelinks.link_class=#{klass.sanitize 'name'}"\
- " AND namelinks.owner_uuid=#{klass.sanitize @object.uuid}"\
- " AND namelinks.tail_uuid=#{klass.sanitize @object.uuid}"\
- " AND namelinks.head_uuid=#{klass.table_name}.uuid")
- cond_sql += " OR namelinks.uuid IS NOT NULL"
+ cond_sql += " OR #{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'} AND links.owner_uuid=#{klass.sanitize @object.uuid} AND links.tail_uuid=#{klass.sanitize @object.uuid})"
end
- @objects = @objects.where(cond_sql, *cond_params).order(:uuid)
+ @objects = @objects.where(cond_sql, *cond_params).order("#{klass.table_name}.uuid")
@limit = limit_all - all_objects.count
apply_where_limit_order_params
items_available = @objects.
diff --git a/services/api/app/models/arvados_model.rb b/services/api/app/models/arvados_model.rb
index 25d7317..bfb8ea6 100644
--- a/services/api/app/models/arvados_model.rb
+++ b/services/api/app/models/arvados_model.rb
@@ -82,11 +82,10 @@ class ArvadosModel < ActiveRecord::Base
if self == Link and user
or_references_me = "OR (#{table_name}.link_class in (#{sanitize 'permission'}, #{sanitize 'resources'}) AND #{sanitize user.uuid} IN (#{table_name}.head_uuid, #{table_name}.tail_uuid))"
end
- joins("LEFT JOIN links permissions ON permissions.head_uuid in (#{table_name}.owner_uuid, #{table_name}.uuid) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'").
- where("?=? OR #{table_name}.owner_uuid in (?) OR #{table_name}.uuid=? OR permissions.head_uuid IS NOT NULL #{or_references_me}",
- true, user.is_admin,
- uuid_list,
- user.uuid)
+ where("?=? OR #{table_name}.owner_uuid in (?) OR #{table_name}.uuid=? OR #{table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class='permission' AND tail_uuid IN (#{sanitized_uuid_list})) #{or_references_me}",
+ true, user.is_admin,
+ uuid_list,
+ user.uuid)
end
def logged_attributes
commit 520d80f58ab4358dfce0233fe6880794c819760c
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 28 15:53:31 2014 -0400
Expose names for owned_items as list_response.name_for() in Workbench
diff --git a/apps/workbench/app/models/arvados_api_client.rb b/apps/workbench/app/models/arvados_api_client.rb
index cf14106..bc07b88 100644
--- a/apps/workbench/app/models/arvados_api_client.rb
+++ b/apps/workbench/app/models/arvados_api_client.rb
@@ -90,7 +90,7 @@ class ArvadosApiClient
resp
end
- def self.patch_paging_vars(ary, items_available, offset, limit)
+ def self.patch_paging_vars(ary, items_available, offset, limit, links)
if items_available
(class << ary; self; end).class_eval { attr_accessor :items_available }
ary.items_available = items_available
@@ -102,14 +102,22 @@ class ArvadosApiClient
if limit
(class << ary; self; end).class_eval { attr_accessor :limit }
ary.limit = limit
- end
+ end
+ if links
+ (class << ary; self; end).class_eval { attr_accessor :links }
+ ary.links = links
+ end
ary
end
def unpack_api_response(j, kind=nil)
if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
ary = j[:items].collect { |x| unpack_api_response x, x[:kind] }
- self.class.patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit])
+ links = ArvadosResourceList.new Link
+ links.results = (j[:links] || []).collect do |x|
+ unpack_api_response x, x[:kind]
+ end
+ self.class.patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit], links)
elsif j.is_a? Hash and (kind || j[:kind])
oclass = self.kind_class(kind || j[:kind])
if oclass
diff --git a/apps/workbench/app/models/arvados_resource_list.rb b/apps/workbench/app/models/arvados_resource_list.rb
index 16a59b1..ba3f0a0 100644
--- a/apps/workbench/app/models/arvados_resource_list.rb
+++ b/apps/workbench/app/models/arvados_resource_list.rb
@@ -1,7 +1,7 @@
class ArvadosResourceList
include Enumerable
- def initialize(resource_class)
+ def initialize resource_class=nil
@resource_class = resource_class
end
@@ -134,4 +134,32 @@ class ArvadosResourceList
results.offset if results.respond_to? :offset
end
+ def result_links
+ results.links if results.respond_to? :links
+ end
+
+ def links_for item_or_uuid, link_class=nil
+ unless @links_for_uuid
+ @links_for_uuid = {}
+ results.links.each do |link|
+ if link.respond_to? :head_uuid
+ @links_for_uuid[link.head_uuid] ||= []
+ @links_for_uuid[link.head_uuid] << link
+ end
+ end
+ end
+ if item_or_uuid.respond_to? :uuid
+ uuid = item_or_uuid.uuid
+ else
+ uuid = item_or_uuid
+ end
+ (@links_for_uuid[uuid] || []).select do |link|
+ link.link_class == link_class
+ end
+ end
+
+ def name_for item_or_uuid
+ links_for(item_or_uuid, 'name').first.name
+ end
+
end
diff --git a/apps/workbench/app/models/group.rb b/apps/workbench/app/models/group.rb
index f53a6f4..0004c88 100644
--- a/apps/workbench/app/models/group.rb
+++ b/apps/workbench/app/models/group.rb
@@ -1,6 +1,10 @@
class Group < ArvadosBase
- def self.owned_items
- res = $arvados_api_client.api self, "/#{self.uuid}/owned_items", {}
- $arvados_api_client.unpack_api_response(res)
+ def owned_items params={}
+ res = $arvados_api_client.api self.class, "/#{self.uuid}/owned_items", {
+ _method: 'GET'
+ }.merge(params)
+ ret = ArvadosResourceList.new
+ ret.results = $arvados_api_client.unpack_api_response(res)
+ ret
end
end
diff --git a/apps/workbench/app/models/user.rb b/apps/workbench/app/models/user.rb
index c03e317..58c1d3f 100644
--- a/apps/workbench/app/models/user.rb
+++ b/apps/workbench/app/models/user.rb
@@ -17,9 +17,13 @@ class User < ArvadosBase
end
end
- def owned_items
- res = $arvados_api_client.api self.class, "/#{self.uuid}/owned_items"
- $arvados_api_client.unpack_api_response(res)
+ def owned_items params={}
+ res = $arvados_api_client.api self.class, "/#{self.uuid}/owned_items", {
+ _method: 'GET'
+ }.merge(params)
+ ret = ArvadosResourceList.new
+ ret.results = $arvados_api_client.unpack_api_response(res)
+ ret
end
def full_name
diff --git a/apps/workbench/test/unit/group_test.rb b/apps/workbench/test/unit/group_test.rb
index 0821e1f..1e7d087 100644
--- a/apps/workbench/test/unit/group_test.rb
+++ b/apps/workbench/test/unit/group_test.rb
@@ -1,7 +1,28 @@
require 'test_helper'
-class ProjectTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+class GroupTest < ActiveSupport::TestCase
+ test "get owned_items with names" do
+ use_token :active
+ oi = Group.
+ find(api_fixture('groups')['asubfolder']['uuid']).
+ owned_items(include_linked: true)
+ assert_operator(0, :<, oi.count,
+ "Expected to find some items belonging to :active user")
+ assert_operator(0, :<, oi.items_available,
+ "Expected owned_items response to have items_available > 0")
+ assert_operator(0, :<, oi.result_links.count,
+ "Expected to receive name links with owned_items response")
+ oi_uuids = oi.collect { |i| i['uuid'] }
+
+ expect_uuid = api_fixture('specimens')['in_asubfolder']['uuid']
+ assert_includes(oi_uuids, expect_uuid,
+ "Expected '#{expect_uuid}' in asubfolder's owned_items")
+
+ expect_uuid = api_fixture('specimens')['in_afolder_linked_from_asubfolder']['uuid']
+ expect_name = api_fixture('links')['specimen_is_in_two_folders']['name']
+ assert_includes(oi_uuids, expect_uuid,
+ "Expected '#{expect_uuid}' in asubfolder's owned_items")
+ assert_equal(expect_name, oi.name_for(expect_uuid),
+ "Expected name_for '#{expect_uuid}' to be '#{expect_name}'")
+ end
end
diff --git a/services/api/test/fixtures/groups.yml b/services/api/test/fixtures/groups.yml
index ce04ece..947d762 100644
--- a/services/api/test/fixtures/groups.yml
+++ b/services/api/test/fixtures/groups.yml
@@ -65,5 +65,5 @@ asubfolder:
modified_at: 2014-04-21 15:37:48 -0400
updated_at: 2014-04-21 15:37:48 -0400
name: A Subfolder
- description: Test folder belonging to active user's first test folder
+ description: "Test folder belonging to active user's first test folder"
group_class: folder
commit f8cc86219281026b2867c543524f8e7fa23da291
Author: Tom Clegg <tom at curoverse.com>
Date: Mon Apr 28 11:15:50 2014 -0400
Use name links instead of permission links to include objects in groups.
diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 713f2cf..8e15a07 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -96,12 +96,12 @@ class ApplicationController < ActionController::Base
cond_params = [@object.uuid]
if params[:include_linked]
@objects = @objects.
- joins("LEFT JOIN links mng_links"\
- " ON mng_links.link_class=#{klass.sanitize 'permission'}"\
- " AND mng_links.name=#{klass.sanitize 'can_manage'}"\
- " AND mng_links.tail_uuid=#{klass.sanitize @object.uuid}"\
- " AND mng_links.head_uuid=#{klass.table_name}.uuid")
- cond_sql += " OR mng_links.uuid IS NOT NULL"
+ joins("LEFT JOIN links namelinks"\
+ " ON namelinks.link_class=#{klass.sanitize 'name'}"\
+ " AND namelinks.owner_uuid=#{klass.sanitize @object.uuid}"\
+ " AND namelinks.tail_uuid=#{klass.sanitize @object.uuid}"\
+ " AND namelinks.head_uuid=#{klass.table_name}.uuid")
+ cond_sql += " OR namelinks.uuid IS NOT NULL"
end
@objects = @objects.where(cond_sql, *cond_params).order(:uuid)
@limit = limit_all - all_objects.count
@@ -116,10 +116,17 @@ class ApplicationController < ActionController::Base
end
end
@objects = all_objects || []
+ @links = Link.where('link_class=? and owner_uuid=?'\
+ ' and owner_uuid=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,
diff --git a/services/api/test/fixtures/links.yml b/services/api/test/fixtures/links.yml
index 5b89015..60d822e 100644
--- a/services/api/test/fixtures/links.yml
+++ b/services/api/test/fixtures/links.yml
@@ -292,7 +292,7 @@ test_timestamps:
specimen_is_in_two_folders:
uuid: zzzzz-o0j2j-ryhm1bn83ni03sn
- owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
created_at: 2014-04-21 15:37:48 -0400
modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
@@ -300,8 +300,8 @@ specimen_is_in_two_folders:
updated_at: 2014-04-21 15:37:48 -0400
tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
head_uuid: zzzzz-2x53u-5gid26432uujf79
- link_class: permission
- name: can_manage
+ link_class: name
+ name: "I'm in a subfolder, too"
properties: {}
foo_collection_tag:
diff --git a/services/api/test/functional/arvados/v1/groups_controller_test.rb b/services/api/test/functional/arvados/v1/groups_controller_test.rb
index 74110ae..4b041b6 100644
--- a/services/api/test/functional/arvados/v1/groups_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/groups_controller_test.rb
@@ -125,5 +125,19 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
assert_response :success
uuids = json_response['items'].collect { |i| i['uuid'] }
assert_includes uuids, expected_uuid, "Did not get #{expected_uuid}"
+
+ expected_name = links(:specimen_is_in_two_folders).name
+ found_specimen_name = false
+ assert(json_response['links'].any?,
+ "Expected a non-empty array of links in response")
+ json_response['links'].each do |link|
+ if link['head_uuid'] == expected_uuid
+ if link['name'] == expected_name
+ found_specimen_name = true
+ end
+ end
+ end
+ assert(found_specimen_name,
+ "Expected to find name '#{expected_name}' in response")
end
end
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list