$('.btn').button();
});
+ $(document).
+ on('ready ajax:complete', function() {
+ $('[data-toggle~=tooltip]').tooltip({container:'body'});
+ });
+
HeaderRowFixer = function(selector) {
this.duplicateTheadTr = function() {
$(selector).each(function() {
$(document).
on('ready ajax:complete', function() {
- $('#editable-submit').click(function() {
- console.log($(this));
- });
$('.editable').
+ not('.editable-done-setup').
+ addClass('editable-done-setup').
editable({
success: function(response, newValue) {
// If we just created a new object, stash its UUID
});
}
});
+ }).
+ on('ready ajax:complete', function() {
+ $("[data-toggle~='x-editable']").
+ not('.editable-done-setup').
+ addClass('editable-done-setup').
+ click(function(e) {
+ e.stopPropagation();
+ $($(this).attr('data-toggle-selector')).editable('toggle');
+ });
});
$.fn.editabletypes.text.defaults.tpl = '<input type="text" name="editable-text">'
--- /dev/null
+$(document).
+ on('paste keyup change', 'input[type=text].filterable-control', function() {
+ var q = new RegExp($(this).val(), 'i');
+ $($(this).attr('data-filterable-target')).
+ addClass('filterable-container').
+ data('q', q).
+ trigger('refresh');
+ }).on('refresh', '.filterable-container', function() {
+ var q = $(this).data('q');
+ var filters = $(this).data('filters');
+ $('.filterable', this).hide().filter(function() {
+ var $row = $(this);
+ var pass = true;
+ if (q && !$row.text().match(q))
+ return false;
+ if (filters) {
+ $.each(filters, function(filterby, val) {
+ if (!val) return;
+ if (!pass) return;
+ pass = false;
+ $.each(val.split(" "), function(i, e) {
+ if ($row.attr(filterby) == e)
+ pass = true;
+ });
+ });
+ }
+ return pass;
+ }).show();
+ $('.infinite-scroller').add(window).trigger('scroll');
+ }).on('change', 'select.filterable-control', function() {
+ var val = $(this).val();
+ var filterby = $(this).attr('data-filterable-attribute');
+ var $target = $($(this).attr('data-filterable-target')).
+ addClass('filterable-container');
+ var filters = $target.data('filters') || {};
+ filters[filterby] = val;
+ $target.
+ data('filters', filters).
+ trigger('refresh');
+ }).on('ajax:complete', function() {
+ $('.filterable-control').trigger('change');
+ });
+++ /dev/null
-$(document).
- on('ready ajax:complete', function() {
- $("[data-toggle='x-editable']").click(function(e) {
- e.stopPropagation();
- $($(this).attr('data-toggle-selector')).editable('toggle');
- });
- }).on('paste keyup change', 'input.search-folder-contents', function() {
- var q = new RegExp($(this).val(), 'i');
- $(this).closest('div.panel').find('tbody tr').each(function() {
- $(this).toggle(!!$(this).text().match(q));
- });
- });
--- /dev/null
+function maybe_load_more_content() {
+ var scroller = this; // element with scroll bars
+ var container; // element that receives new content
+ var src; // url for retrieving content
+ var scrollHeight;
+ scrollHeight = scroller.scrollHeight || $('body')[0].scrollHeight;
+ if ($(scroller).scrollTop() + $(scroller).height()
+ >
+ scrollHeight - 50) {
+ container = $(this).data('infinite-container');
+ src = $(container).attr('data-infinite-content-href');
+ if (!src)
+ // Finished
+ return;
+ // Don't start another request until this one finishes
+ $(container).attr('data-infinite-content-href', null);
+ $.ajax(src,
+ {dataType: 'json',
+ type: 'GET',
+ data: {},
+ context: {container: container, src: src}}).
+ fail(function(jqxhr, status, error) {
+ if (jqxhr.readyState == 0 || jqxhr.status == 0) {
+ message = "Cancelled."
+ } else if (jqxhr.responseJSON && jqxhr.responseJSON.errors) {
+ message = jqxhr.responseJSON.errors.join("; ");
+ } else {
+ message = "Request failed.";
+ }
+ // TODO: report this to the user.
+ console.log(message);
+ $(this.container).attr('data-infinite-content-href', this.src);
+ }).
+ done(function(data, status, jqxhr) {
+ $(this.container).append(data.content);
+ $(this.container).attr('data-infinite-content-href', data.next_page_href);
+ $(document).trigger('ajax:complete');
+ });
+ }
+}
+$(document).
+ on('ready ajax:complete', function() {
+ $('[data-infinite-scroller]').each(function() {
+ var $scroller = $($(this).attr('data-infinite-scroller'));
+ if (!$scroller.hasClass('smart-scroll'))
+ $scroller = $(window);
+ $scroller.
+ addClass('infinite-scroller').
+ data('infinite-container', this).
+ on('scroll', maybe_load_more_content);
+ });
+ });
function run_pipeline_button_state() {
- var a = $('a.editable.required.editable-empty');
+ var a = $('a.editable.required.editable-empty,input.form-control.required[value=]');
if (a.length > 0) {
$(".run-pipeline-button").addClass("disabled");
}
});
$(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(event, eventData){
- var parsedData = JSON.parse(eventData);
+ var wasatbottom = ($(this).scrollTop() + $(this).height() >=
+ this.scrollHeight);
+ var parsedData = JSON.parse(eventData);
+ var propertyText = undefined;
+ var properties = parsedData.properties;
- var propertyText = undefined
-
- var properties = parsedData.properties;
if (properties !== null) {
- propertyText = properties.text;
+ propertyText = properties.text;
}
-
if (propertyText !== undefined) {
- $(this).append(propertyText + "<br/>");
+ $(this).append(propertyText + "<br/>");
} else {
- $(this).append(parsedData.summary + "<br/>");
+ $(this).append(parsedData.summary + "<br/>");
}
+ if (wasatbottom)
+ this.scrollTop = this.scrollHeight;
+}).on('ready ajax:complete', function(){
+ $('.arv-log-event-handler-append-logs').each(function() {
+ this.scrollTop = this.scrollHeight;
+ });
});
+
+var showhide_compare = function() {
+ var form = $('form#compare')[0];
+ $('input[type=hidden][name="uuids[]"]', form).remove();
+ $('input[type=submit]', form).prop('disabled',true).show();
+ var checked_inputs = $('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]:checked');
+ if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
+ checked_inputs.each(function(){
+ if(this.checked) {
+ $('input[type=submit]', form).prop('disabled',false).show();
+ $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
+ }
+ });
+ }
+};
+$('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]').on('click', showhide_compare);
+showhide_compare();
$(document).on('click', '.selectable', function() {
+ var any;
var $this = $(this);
if (!$this.hasClass('multiple')) {
$this.closest('.selectable-container').
removeClass('active');
}
$this.toggleClass('active');
+ any = ($this.
+ closest('.selectable-container').
+ find('.selectable.active').length > 0)
+ $this.
+ closest('.modal').
+ find('[data-enable-if-selection]').
+ prop('disabled', !any);
}).on('click', '.modal button[data-action-href]', function() {
var selection = [];
- var data = {};
+ var data = [];
var $modal = $(this).closest('.modal');
+ var action_data = $(this).data('action-data');
+ var selection_param = action_data.selection_param;
$modal.find('.modal-error').removeClass('hide').hide();
$modal.find('.selectable.active[data-object-uuid]').each(function() {
- selection.push($(this).attr('data-object-uuid'));
+ var val = $(this).attr('data-object-uuid');
+ data.push({name: selection_param, value: val});
+ });
+ $.each(action_data, function(key, value) {
+ data.push({name: key, value: value});
});
- data[$(this).data('action-data').selection_param] = selection[0];
$.ajax($(this).attr('data-action-href'),
{dataType: 'json',
type: $(this).attr('data-method'),
data: data,
- context: {modal: $modal}}).
+ traditional: true,
+ context: {modal: $modal, action_data: action_data}}).
fail(function(jqxhr, status, error) {
if (jqxhr.readyState == 0 || jqxhr.status == 0) {
message = "Cancelled."
html('<div class="alert alert-danger">' + message + '</div>').
show();
}).
- success(function() {
+ done(function(data, status, jqxhr) {
+ var event_name = this.action_data.success;
this.modal.find('.modal-error').hide();
- window.location.reload();
+ $(document).trigger(event_name!=null ? event_name : 'page-refresh',
+ [data, status, jqxhr, this.action_data]);
});
});
+$(document).on('page-refresh', function(event, data, status, jqxhr, action_data) {
+ window.location.reload();
+}).on('redirect-to-created-object', function(event, data, status, jqxhr, action_data) {
+ window.location.href = data.href.replace(/^[^\/]*\/\/[^\/]*/, '');
+});
if (lst.length > 0) {
html = '<li><a href="#" class="btn btn-xs btn-info" id="clear_selections_button"><i class="fa fa-fw fa-ban"></i> Clear selections</a></li>';
if (this_object_uuid.match('-j7d0g-'))
- html += '<li><button class="btn btn-xs btn-info" type="submit" name="copy_selections_into_folder" id="copy_selections_into_folder"><i class="fa fa-fw fa-folder-open"></i> Copy selections into this folder</button></li>';
+ html += '<li><button class="btn btn-xs btn-info" type="submit" name="copy_selections_into_project" id="copy_selections_into_project"><i class="fa fa-fw fa-folder-open"></i> Copy selections into this project</button></li>';
html += '<li><button class="btn btn-xs btn-info" type="submit" name="combine_selected_files_into_collection" '
+ ' id="combine_selected_files_into_collection">'
+ '<i class="fa fa-fw fa-archive"></i> Combine selected collections and files into a new collection</button></li>'
$('.remove-selection').on('click', remove_selection_click);
$('#clear_selections_button').on('click', clear_selections);
+ $(document).trigger('selections-updated', [lst]);
};
$(document).
on('change', '.persistent-selection:checkbox', function(e) {
- //console.log($(this));
- //console.log($(this).val());
-
var inc = 0;
if ($(this).is(":checked")) {
add_selection($(this).val(), $(this).attr('friendly_name'), $(this).attr('href'), $(this).attr('friendly_type'));
}
});
-
$(window).on('load storage', update_count);
$('#selection-form-content').on("click", function(e) {
return ret;
};
})();
+
+function dispatch_selection_action() {
+ // Build a new "href" attribute for this link by starting with the
+ // "data-href" attribute and appending ?foo[]=bar&foo[]=baz (or
+ // &foo=... as appropriate) to reflect the current object
+ // selections.
+ var data = [];
+ var param_name = $(this).attr('data-selection-param-name');
+ var href = $(this).attr('data-href');
+ $('.persistent-selection:checkbox:checked').each(function() {
+ data.push({name: param_name, value: $(this).val()});
+ });
+ if (href.indexOf('?') >= 0)
+ href += '&';
+ else
+ href += '?';
+ href += $.param(data, true);
+ $(this).attr('href', href);
+ return true;
+}
+
+function enable_disable_selection_actions() {
+ var $checked = $('.persistent-selection:checkbox:checked');
+ $('[data-selection-action]').
+ closest('div.btn-group-sm').
+ find('*').
+ prop('disabled', ($checked.length == 0));
+ $('[data-selection-action=compare]').
+ closest('li').
+ toggleClass('disabled',
+ ($checked.filter('[value*=-d1hrv-]').length < 2) ||
+ ($checked.not('[value*=-d1hrv-]').length > 0));
+}
+
+$(document).
+ on('selections-updated ready ajax:complete', function() {
+ $('[data-selection-action]').click(dispatch_selection_action);
+ enable_disable_selection_actions();
+ });
font-size: .8em;
color: #888;
}
+.arvados-filename,
.arvados-uuid {
font-size: .8em;
font-family: monospace;
overflow-x: hidden;
max-width: 120px;
}
+table.arv-index > thead > tr > th {
+ border-top: none;
+}
table.table-fixedlayout {
white-space: nowrap;
table-layout: fixed;
white-space: nowrap;
}
-.navbar .nav li.nav-separator span {
- display: block;
- float: none;
- color: #bbbbbb;
- padding: 10px 0 10px;
- text-decoration: none;
- text-shadow: 0 1px 0 #ffffff;
+/* top nav */
+$top-nav-bg: #3c163d;
+$top-nav-bg-bottom: #260027;
+nav.navbar-fixed-top .navbar-brand {
+ color: #79537a;
+ letter-spacing: 0.4em;
+}
+nav.navbar-fixed-top {
+ background: $top-nav-bg;
+ background: linear-gradient(to bottom, $top-nav-bg 0%,$top-nav-bg-bottom 100%);
+}
+.navbar.breadcrumbs {
+ line-height: 50px;
+ border-radius: 0;
+ margin-bottom: 0;
+ border-right: 0;
+ border-left: 0;
+}
+.navbar.breadcrumbs .nav > li > a,
+.navbar.breadcrumbs .nav > li {
+ color: #000;
+}
+.navbar.breadcrumbs .nav > li.nav-separator > i {
+ color: #bbb;
+}
+nav.navbar-fixed-top .navbar-nav.navbar-right > li.open > a,
+nav.navbar-fixed-top .navbar-nav.navbar-right > li.open > a:focus,
+nav.navbar-fixed-top .navbar-nav.navbar-right > li.open > a:hover {
+ background: lighten($top-nav-bg, 5%);
+}
+nav.navbar-fixed-top .navbar-nav.navbar-right > li > a,
+nav.navbar-fixed-top .navbar-nav.navbar-right > li > a:focus,
+nav.navbar-fixed-top .navbar-nav.navbar-right > li > a:hover {
+ color: #fff;
}
.dax {
z-index:1055;
}
-.navbar-nav.side-nav {
- box-shadow: inset -1px 0 #e7e7e7;
+/* Do not leave space for left-nav */
+div#wrapper {
+ padding-left: 0;
}
-.navbar-nav.side-nav > li:first-child {
- margin-top: 5px; /* keep "hover" bg below top nav bottom border */
+
+.arv-description-as-subtitle {
+ padding-bottom: 1em;
}
-.navbar-nav.side-nav > li > a {
- padding-top: 10px;
- padding-bottom: 10px;
+.arv-description-in-table {
+ max-height: 3.5em;
+ overflow-x: hidden;
+ overflow-y: hidden;
}
-.navbar-nav.side-nav > li.dropdown > ul.dropdown-menu > li > a {
- padding-top: 5px;
- padding-bottom: 5px;
+.arv-description-in-table:hover {
+ overflow-y: auto;
}
-.navbar-nav.side-nav a.active,
-.navbar-nav.side-nav a:hover,
-.navbar-nav.side-nav a:focus {
- border-right: 1px solid #ffffff;
- background: #ffffff;
+
+.btn.btn-nodecorate {
+ border: none;
}
svg text {
font-size: 6pt;
-}
\ No newline at end of file
+}
+++ /dev/null
-.arv-folder-list > .row {
- padding-top: 5px;
- padding-bottom: 5px;
- padding-right: 1em;
-}
-.arv-folder-list > .row.folder:hover {
- background: #d9edf7;
-}
-.arv-folder-list > .row.folder.active,
-.arv-folder-list > .row.folder.active:hover {
- background: #428bca;
- color: #fff;
-}
-// Place all the styles related to the Jobs controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
+.arv-job-log-window {
+ height: 20em;
+ white-space: nowrap;
+ overflow: scroll;
+ background: black;
+ color: white;
+ font-family: monospace;
+ font-size: .8em;
+ border: 2px solid black;
+}
--- /dev/null
+.arv-project-list > .row {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-right: 1em;
+}
+.arv-project-list > .row.project:hover {
+ background: #d9edf7;
+}
+div.scroll-20em {
+ height: 20em;
+ overflow-y: scroll;
+}
+.selectable-container > .row {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-right: 1em;
+}
.selectable.active, .selectable:hover {
background: #d9edf7;
}
+.selectable.active,
+.selectable.active *,
+.selectable.active:hover,
+.selectable.active:hover * {
+ background: #428bca;
+ color: #fff;
+}
redirect_to :back
end
- expose_action :copy_selections_into_folder do
+ expose_action :copy_selections_into_project do
+ link_selections = Link.filter([['uuid','in',params["selection"]]])
+ link_uuids = link_selections.collect(&:uuid)
+
+ # Given a link uuid, we'll add the link's head_uuid. Given another
+ # type, we'll add the object itself.
+ uuids_to_add = params["selection"] - link_uuids
+ uuids_to_add += link_selections.collect(&:head_uuid)
+
+ # Skip anything that's already here.
already_named = Link.
filter([['tail_uuid','=',@object.uuid],
- ['head_uuid','in',params["selection"]]]).
+ ['head_uuid','in',uuids_to_add],
+ ['link_class','=','name']]).
collect(&:head_uuid)
- (params["selection"] - already_named).each do |s|
- Link.create(tail_uuid: @object.uuid,
- head_uuid: s,
- link_class: 'name',
- name: "#{s} added #{Time.now}")
+ uuids_to_add -= already_named
+
+ # Given a name link, we'll try to add the linked object using the
+ # same name.
+ name_for = {}
+ link_selections.
+ select { |x| x.link_class == 'name' }.
+ each do |link|
+ name_for[link.head_uuid] = link.name
+ end
+
+ uuids_to_add.each do |s|
+ name = name_for[s] || s
+ begin
+ Link.create(tail_uuid: @object.uuid,
+ head_uuid: s,
+ link_class: 'name',
+ name: name)
+ rescue
+ Link.create(tail_uuid: @object.uuid,
+ head_uuid: s,
+ link_class: 'name',
+ name: name + " (#{Time.now.localtime})")
+ end
end
redirect_to @object
end
class ApplicationController < ActionController::Base
include ArvadosApiClientHelper
+ include ApplicationHelper
respond_to :html, :json, :js
protect_from_forgery
self.render_error status: 404
end
- def render_index
- respond_to do |f|
- f.json { render json: @objects }
- f.html {
- if params['tab_pane']
- comparable = self.respond_to? :compare
- render(partial: 'show_' + params['tab_pane'].downcase,
- locals: { comparable: comparable, objects: @objects })
- else
- render
- end
- }
- f.js { render }
- end
- end
-
- def index
+ def find_objects_for_index
@limit ||= 200
if params[:limit]
@limit = params[:limit].to_i
end
@objects ||= model_class
- @objects = @objects.filter(@filters).limit(@limit).offset(@offset).all
+ @objects = @objects.filter(@filters).limit(@limit).offset(@offset)
+ end
+
+ def render_index
+ respond_to do |f|
+ f.json { render json: @objects }
+ f.html {
+ if params['tab_pane']
+ comparable = self.respond_to? :compare
+ render(partial: 'show_' + params['tab_pane'].downcase,
+ locals: { comparable: comparable, objects: @objects })
+ else
+ render
+ end
+ }
+ f.js { render }
+ end
+ end
+
+ def index
+ find_objects_for_index if !@objects
render_index
end
+ helper_method :next_page_offset
+ def next_page_offset
+ if @objects.respond_to?(:result_offset) and
+ @objects.respond_to?(:result_limit) and
+ @objects.respond_to?(:items_available)
+ next_offset = @objects.result_offset + @objects.result_limit
+ if next_offset < @objects.items_available
+ next_offset
+ else
+ nil
+ end
+ end
+ end
+
def show
if !@object
return render_not_found("object not found")
comparable = self.respond_to? :compare
render(partial: 'show_' + params['tab_pane'].downcase,
locals: { comparable: comparable, objects: @objects })
+ elsif request.method.in? ['GET', 'HEAD']
+ render
else
- if request.method == 'GET'
- render
- else
- redirect_to params[:return_to] || @object
- end
+ redirect_to params[:return_to] || @object
end
}
f.js { render }
end
end
+ def choose
+ params[:limit] ||= 20
+ 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
+ }
+ }
+ end
+ f.js {
+ render partial: 'choose', locals: {multiple: params[:multiple]}
+ }
+ end
+ end
+
def render_content
if !@object
return render_not_found("object not found")
end
def update
- @updates ||= params[@object.class.to_s.underscore.singularize.to_sym]
+ @updates ||= params[@object.resource_param_name.to_sym]
@updates.keys.each do |attr|
if @object.send(attr).is_a? Hash
if @updates[attr].is_a? String
end
end
+ # Clone the given object, merging any attribute values supplied as
+ # with a create action.
+ def copy
+ @new_resource_attrs ||= params[model_class.to_s.underscore.singularize]
+ @new_resource_attrs ||= {}
+ @object = @object.dup
+ @object.update_attributes @new_resource_attrs
+ if not @new_resource_attrs[:name] and @object.respond_to? :name
+ if @object.name and @object.name != ''
+ @object.name = "Copy of #{@object.name}"
+ else
+ @object.name = "Copy of unnamed #{@object.class_for_display.downcase}"
+ end
+ end
+ @object.save!
+ show
+ end
+
def destroy
if @object.destroy
respond_to do |f|
end
def show_pane_list
- %w(Attributes Metadata JSON API)
+ %w(Attributes Advanced)
end
protected
def redirect_to_login
respond_to do |f|
f.html {
- if request.method == 'GET'
+ if request.method.in? ['GET', 'HEAD']
redirect_to arvados_api_client.arvados_login_url(return_to: request.url)
else
flash[:error] = "Either you are not logged in, or your session has timed out. I can't automatically log you in and re-attempt this request."
if params[:uuid].empty?
@object = nil
else
- @object = model_class.find(params[:uuid])
+ if (model_class != Link and
+ resource_class_for_uuid(params[:uuid]) == Link)
+ @name_link = Link.find(params[:uuid])
+ @object = model_class.find(@name_link.head_uuid)
+ else
+ @object = model_class.find(params[:uuid])
+ end
end
else
@object = model_class.where(uuid: params[:uuid]).first
is_admin: u.is_admin,
prefs: u.prefs
}
- if !request.format.json? and request.method == 'GET'
+ if !request.format.json? and request.method.in? ['GET', 'HEAD']
# Repeat this request with api_token in the (new) session
# cookie instead of the query string. This prevents API
# tokens from appearing in (and being inadvisedly copied
end
def thread_with_mandatory_api_token
- thread_with_api_token do
- yield
+ thread_with_api_token(true) do
+ if Thread.current[:arvados_api_token]
+ yield
+ elsif session[:arvados_api_token]
+ # Expired session. Clear it before refreshing login so that,
+ # if this login procedure fails, we end up showing the "please
+ # log in" page instead of getting stuck in a redirect loop.
+ session.delete :arvados_api_token
+ redirect_to_login
+ else
+ render 'users/welcome'
+ end
end
end
end
def check_user_agreements
- if current_user && !current_user.is_active && current_user.is_invited
+ if current_user && !current_user.is_active
+ if not current_user.is_invited
+ return render 'users/inactive'
+ end
signatures = UserAgreement.signatures
@signed_ua_uuids = UserAgreement.signatures.map &:head_uuid
@required_user_agreements = UserAgreement.all.map do |ua|
end
end
- helper_method :my_folders
- def my_folders
- return @my_folders if @my_folders
- @my_folders = []
+ helper_method :all_projects
+ def all_projects
+ @all_projects ||= Group.filter([['group_class','in',['project','folder']]])
+ end
+
+ helper_method :my_projects
+ def my_projects
+ return @my_projects if @my_projects
+ @my_projects = []
root_of = {}
- Group.filter([['group_class','=','folder']]).each do |g|
+ all_projects.each do |g|
root_of[g.uuid] = g.owner_uuid
- @my_folders << g
+ @my_projects << g
end
done = false
while not done
end
end
end
- @my_folders = @my_folders.select do |g|
+ @my_projects = @my_projects.select do |g|
root_of[g.uuid] == current_user.uuid
end
end
+ helper_method :projects_shared_with_me
+ def projects_shared_with_me
+ my_project_uuids = my_projects.collect &:uuid
+ all_projects.reject { |x| x.uuid.in? my_project_uuids }
+ end
+
+ helper_method :recent_jobs_and_pipelines
+ def recent_jobs_and_pipelines
+ in_my_projects = ['owner_uuid','in',my_projects.collect(&:uuid)]
+ (Job.limit(10).filter([in_my_projects]) |
+ PipelineInstance.limit(10).filter([in_my_projects])).
+ sort_by do |x|
+ x.finished_at || x.started_at || x.created_at rescue x.created_at
+ end
+ end
+
+ helper_method :get_object
+ def get_object uuid
+ if @get_object.nil? and @objects
+ @get_object = @objects.each_with_object({}) do |object, h|
+ h[object.uuid] = object
+ end
+ end
+ @get_object ||= {}
+ @get_object[uuid]
+ end
+
+ helper_method :project_breadcrumbs
+ def project_breadcrumbs
+ crumbs = []
+ current = @name_link || @object
+ while current
+ if current.is_a?(Group) and current.group_class.in?(['project','folder'])
+ crumbs.prepend current
+ end
+ if current.is_a? Link
+ current = Group.find?(current.tail_uuid)
+ else
+ current = Group.find?(current.owner_uuid)
+ end
+ end
+ crumbs
+ end
+
+ helper_method :current_project_uuid
+ def current_project_uuid
+ if @object.is_a? Group and @object.group_class.in?(['project','folder'])
+ @object.uuid
+ elsif @name_link.andand.tail_uuid
+ @name_link.tail_uuid
+ elsif @object and resource_class_for_uuid(@object.owner_uuid) == Group
+ @object.owner_uuid
+ else
+ nil
+ end
+ end
+
# helper method to get links for given object or uuid
helper_method :links_for_object
def links_for_object object_or_uuid
only: [:show_file, :show_file_links])
skip_before_filter(:find_object_by_uuid,
only: [:provenance, :show_file, :show_file_links])
+ # We depend on show_file to display the user agreement:
+ skip_before_filter :check_user_agreements, only: [:show_file]
RELATION_LIMIT = 5
def show_pane_list
- %w(Files Attributes Metadata Provenance_graph Used_by JSON API)
+ %w(Files Provenance_graph Used_by Advanced)
end
def set_persistent
end
end
+ def choose
+ params[:limit] ||= 20
+ @objects = Link.
+ filter([['link_class','=','name'],
+ ['head_uuid','is_a','arvados#collection']])
+ find_objects_for_index
+ @next_page_href = (next_page_offset and
+ url_for(offset: next_page_offset, partial: true))
+ @name_links = @objects
+ @objects = Collection.
+ filter([['uuid','in',@name_links.collect(&:head_uuid)]])
+ super
+ end
+
def index
if params[:search].andand.length.andand > 0
tags = Link.where(any: ['contains', params[:search]])
end
@output_of = jobs_with.call(output: @object.uuid)
@log_of = jobs_with.call(log: @object.uuid)
- @folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+ @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
.where(head_uuid: @object.uuid, link_class: 'name').results
- folder_hash = Group.where(uuid: @folder_links.map(&:tail_uuid)).to_hash
- @folders = @folder_links.map { |link| folder_hash[link.tail_uuid] }
+ project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash
+ @projects = @project_links.map { |link| project_hash[link.tail_uuid] }
@permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
.where(head_uuid: @object.uuid, link_class: 'permission',
name: 'can_read').results
+++ /dev/null
-class FoldersController < ApplicationController
- def model_class
- Group
- end
-
- def index_pane_list
- %w(Folders)
- end
-
- def remove_item
- @removed_uuids = []
- links = []
- item = ArvadosBase.find params[:item_uuid]
- if (item.class == Link and
- item.link_class == 'name' and
- item.tail_uuid = @object.uuid)
- # Given uuid is a name link, linking an object to this
- # folder. First follow the link to find the item we're removing,
- # then delete the link.
- links << item
- item = ArvadosBase.find item.head_uuid
- else
- # Given uuid is an object. Delete all names.
- links += Link.where(tail_uuid: @object.uuid,
- head_uuid: item.uuid,
- link_class: 'name')
- end
- links.each do |link|
- @removed_uuids << link.uuid
- link.destroy
- end
- if item.owner_uuid == @object.uuid
- # Object is owned by this folder. Remove it from the folder by
- # changing owner to the current user.
- item.update_attributes owner_uuid: current_user
- @removed_uuids << item.uuid
- end
- end
-
- def index
- @objects = Group.where(group_class: 'folder').order('name')
- parent_of = {current_user.uuid => 'me'}
- @objects.each do |ob|
- parent_of[ob.uuid] = ob.owner_uuid
- end
- children_of = {false => [], 'me' => [current_user]}
- @objects.each do |ob|
- if ob.owner_uuid != current_user.uuid and
- not parent_of.has_key? ob.owner_uuid
- parent_of[ob.uuid] = false
- end
- children_of[parent_of[ob.uuid]] ||= []
- children_of[parent_of[ob.uuid]] << ob
- end
- buildtree = lambda do |children_of, root_uuid=false|
- tree = {}
- children_of[root_uuid].andand.each do |ob|
- tree[ob] = buildtree.call(children_of, ob.uuid)
- end
- tree
- end
- sorted_paths = lambda do |tree, depth=0|
- paths = []
- tree.keys.sort_by { |ob|
- ob.is_a?(String) ? ob : ob.friendly_link_name
- }.each do |ob|
- paths << {object: ob, depth: depth}
- paths += sorted_paths.call tree[ob], depth+1
- end
- paths
- end
- @my_folder_tree =
- sorted_paths.call buildtree.call(children_of, 'me')
- @shared_folder_tree =
- sorted_paths.call({'Shared with me' =>
- buildtree.call(children_of, false)})
- end
-
- def choose
- index
- render partial: 'choose'
- end
-
- def show
- @objects = @object.contents include_linked: true
- @share_links = Link.filter([['head_uuid', '=', @object.uuid],
- ['link_class', '=', 'permission']])
- @logs = Log.limit(10).filter([['object_uuid', '=', @object.uuid]])
-
- @objects_and_names = []
- @objects.each do |object|
- if !(name_links = @objects.links_for(object, 'name')).empty?
- name_links.each do |name_link|
- @objects_and_names << [object, name_link]
- end
- else
- @objects_and_names << [object,
- Link.new(tail_uuid: @object.uuid,
- head_uuid: object.uuid,
- link_class: "name",
- name: "")]
- end
- end
-
- super
- end
-
- def create
- @new_resource_attrs = (params['folder'] || {}).merge(group_class: 'folder')
- @new_resource_attrs[:name] ||= 'New folder'
- super
- end
-
- def update
- @updates = params['folder']
- super
- end
-end
class GroupsController < ApplicationController
def index
- @groups = Group.filter [['group_class', 'not in', ['folder']]]
+ @groups = Group.filter [['group_class', 'not in', ['folder', 'project']]]
@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
end
def show
- return redirect_to(folder_path(@object)) if @object.group_class == 'folder'
- super
+ if @object.group_class.in?(['project','folder'])
+ redirect_to(project_path(@object))
+ else
+ super
+ end
end
end
end
def show_pane_list
- %w(Status Attributes Provenance Metadata JSON API)
+ %w(Status Details Provenance Advanced)
end
end
before_filter :find_objects_by_uuid, only: :compare
include PipelineInstancesHelper
+ def copy
+ @object = @object.dup
+ @object.components.each do |cname, component|
+ component.delete :job
+ end
+ @object.state = 'New'
+ super
+ end
+
+ def update
+ @updates ||= params[@object.class.to_s.underscore.singularize.to_sym]
+ if (components = @updates[:components])
+ components.each do |cname, component|
+ if component[:script_parameters]
+ component[:script_parameters].each do |param, value_info|
+ if value_info.is_a? Hash
+ if resource_class_for_uuid(value_info[:value]) == Link
+ # Use the link target, not the link itself, as script
+ # parameter; but keep the link info around as well.
+ link = Link.find value_info[:value]
+ value_info[:value] = link.head_uuid
+ value_info[:link_uuid] = link.uuid
+ value_info[:link_name] = link.name
+ else
+ # Delete stale link_uuid and link_name data.
+ value_info[:link_uuid] = nil
+ value_info[:link_name] = nil
+ end
+ end
+ end
+ end
+ end
+ end
+ super
+ end
+
def graph(pipelines)
return nil, nil if params['tab_pane'] != "Graph"
:combine_jobs => :script_and_version,
:script_version_nodes => true,
:pips => pips }
+ @object = @objects.first
end
def show_pane_list
- panes = %w(Components Graph Attributes Metadata JSON API)
+ panes = %w(Components Graph Advanced)
if @object and @object.state.in? ['New', 'Ready']
panes = %w(Inputs) + panes
end
+ if not @object.components.values.collect { |x| x[:job] }.compact.any?
+ panes -= ['Graph']
+ end
panes
end
end
def show_pane_list
- %w(Components Pipelines Attributes Metadata JSON API)
+ %w(Components Pipelines Advanced)
end
-
end
--- /dev/null
+class ProjectsController < ApplicationController
+ def model_class
+ Group
+ end
+
+ def index_pane_list
+ %w(Projects)
+ end
+
+ def show_pane_list
+ %w(Contents Permissions Advanced)
+ end
+
+ def remove_item
+ params[:item_uuids] = [params[:item_uuid]]
+ remove_items
+ render template: 'projects/remove_items'
+ end
+
+ def remove_items
+ @removed_uuids = []
+ links = []
+ params[:item_uuids].collect { |uuid| ArvadosBase.find uuid }.each do |item|
+ if (item.class == Link and
+ item.link_class == 'name' and
+ item.tail_uuid == @object.uuid)
+ # Given uuid is a name link, linking an object to this
+ # project. First follow the link to find the item we're removing,
+ # then delete the link.
+ links << item
+ item = ArvadosBase.find item.head_uuid
+ else
+ # Given uuid is an object. Delete all names.
+ links += Link.where(tail_uuid: @object.uuid,
+ head_uuid: item.uuid,
+ link_class: 'name')
+ end
+ links.each do |link|
+ @removed_uuids << link.uuid
+ link.destroy
+ end
+ if item.owner_uuid == @object.uuid
+ # Object is owned by this project. Remove it from the project by
+ # changing owner to the current user.
+ item.update_attributes owner_uuid: current_user.uuid
+ @removed_uuids << item.uuid
+ end
+ end
+ end
+
+ def find_objects_for_index
+ @objects = Group.
+ filter([['group_class','in',['project','folder']]]).
+ order('name')
+ super
+ parent_of = {current_user.uuid => 'me'}
+ @objects.each do |ob|
+ parent_of[ob.uuid] = ob.owner_uuid
+ end
+ children_of = {false => [], 'me' => [current_user]}
+ @objects.each do |ob|
+ if ob.owner_uuid != current_user.uuid and
+ not parent_of.has_key? ob.owner_uuid
+ parent_of[ob.uuid] = false
+ end
+ children_of[parent_of[ob.uuid]] ||= []
+ children_of[parent_of[ob.uuid]] << ob
+ end
+ buildtree = lambda do |children_of, root_uuid=false|
+ tree = {}
+ children_of[root_uuid].andand.each do |ob|
+ tree[ob] = buildtree.call(children_of, ob.uuid)
+ end
+ tree
+ end
+ sorted_paths = lambda do |tree, depth=0|
+ paths = []
+ tree.keys.sort_by { |ob|
+ ob.is_a?(String) ? ob : ob.friendly_link_name
+ }.each do |ob|
+ paths << {object: ob, depth: depth}
+ paths += sorted_paths.call tree[ob], depth+1
+ end
+ paths
+ end
+ @my_project_tree =
+ sorted_paths.call buildtree.call(children_of, 'me')
+ @shared_project_tree =
+ sorted_paths.call({'Shared with me' =>
+ buildtree.call(children_of, false)})
+ end
+
+ def show
+ @objects = @object.contents(limit: 50,
+ include_linked: true,
+ offset: params[:offset] || 0)
+ @share_links = Link.filter([['head_uuid', '=', @object.uuid],
+ ['link_class', '=', 'permission']])
+ @logs = Log.limit(10).filter([['object_uuid', '=', @object.uuid]])
+
+ @objects_and_names = []
+ @objects.each do |object|
+ if !(name_links = @objects.links_for(object, 'name')).empty?
+ name_links.each do |name_link|
+ @objects_and_names << [object, name_link]
+ end
+ elsif object.respond_to? :name
+ @objects_and_names << [object, object]
+ else
+ @objects_and_names << [object,
+ Link.new(owner_uuid: @object.uuid,
+ tail_uuid: @object.uuid,
+ head_uuid: object.uuid,
+ link_class: "name",
+ name: "")]
+ end
+ end
+ if params[:partial]
+ respond_to do |f|
+ f.json {
+ render json: {
+ content: render_to_string(partial: 'show_contents_rows.html',
+ formats: [:html],
+ locals: {
+ objects_and_names: @objects_and_names,
+ project: @object
+ }),
+ next_page_href: (next_page_offset and
+ url_for(offset: next_page_offset, partial: true))
+ }
+ }
+ end
+ else
+ super
+ end
+ end
+
+ def create
+ @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
+ @new_resource_attrs[:name] ||= 'New project'
+ super
+ end
+
+ def update
+ @updates = params['project']
+ super
+ end
+end
class UsersController < ApplicationController
skip_before_filter :find_object_by_uuid, :only => [:welcome, :activity, :storage]
- skip_around_filter :thread_with_mandatory_api_token, :only => :welcome
before_filter :ensure_current_user_is_admin, only: [:sudo, :unsetup, :setup]
def welcome
#
def link_to_if_arvados_object(attrvalue, opts={}, style_opts={})
if (resource_class = resource_class_for_uuid(attrvalue, opts))
- link_uuid = attrvalue.is_a?(ArvadosBase) ? attrvalue.uuid : attrvalue
+ if attrvalue.is_a? ArvadosBase
+ object = attrvalue
+ link_uuid = attrvalue.uuid
+ else
+ object = nil
+ link_uuid = attrvalue
+ end
link_name = opts[:link_text]
if !link_name
- link_name = link_uuid
+ link_name = object.andand.default_name || resource_class.default_name
if opts[:friendly_name]
if attrvalue.respond_to? :friendly_link_name
if opts[:no_link]
raw(link_name)
else
- link_to raw(link_name), { controller: resource_class.to_s.tableize, action: 'show', id: link_uuid }, style_opts
+ link_to raw(link_name), { controller: resource_class.to_s.tableize, action: 'show', id: ((opts[:name_link].andand.uuid) || link_uuid) }, style_opts
end
else
# just return attrvalue if it is not recognizable as an Arvados object or uuid.
attrvalue = object.send(attr) if attrvalue.nil?
if !object.attribute_editable?(attr, :ever) or
(!object.editable? and
- !object.owner_uuid.in?(my_folders.collect(&:uuid)))
- return attrvalue
+ !object.owner_uuid.in?(my_projects.collect(&:uuid)))
+ return ((attrvalue && attrvalue.length > 0 && attrvalue) ||
+ (attr == 'name' and object.andand.default_name) ||
+ '(none)')
end
input_type = 'text'
ajax_options['data-pk'][:defaults] = object.attributes
end
ajax_options['data-pk'] = ajax_options['data-pk'].to_json
+ @unique_id ||= (Time.now.to_f*1000000).to_i
+ span_id = object.uuid.to_s + '-' + attr.to_s + '-' + (@unique_id += 1).to_s
- content_tag 'span', attrvalue.to_s, {
- "data-emptytext" => "none",
+ span_tag = content_tag 'span', attrvalue.to_s, {
+ "data-emptytext" => (object.andand.default_name || 'none'),
"data-placement" => "bottom",
"data-type" => input_type,
- "data-title" => "Update #{attr.gsub '_', ' '}",
+ "data-title" => "Edit #{attr.gsub '_', ' '}",
"data-name" => attr,
"data-object-uuid" => object.uuid,
+ "data-toggle" => "manual",
+ "id" => span_id,
:class => "editable"
}.merge(htmloptions).merge(ajax_options)
+ edit_button = raw('<a href="#" class="btn btn-xs btn-default btn-nodecorate" data-toggle="x-editable tooltip" data-toggle-selector="#' + span_id + '" data-placement="top" title="' + (htmloptions[:tiptitle] || 'edit') + '"><i class="fa fa-fw fa-pencil"></i></a>')
+ if htmloptions[:btnplacement] == :left
+ edit_button + ' ' + span_tag
+ else
+ span_tag + ' ' + edit_button
+ end
end
def render_pipeline_component_attribute(object, attr, subattr, value_info, htmloptions={})
if !object or
!object.attribute_editable?(attr, :ever) or
(!object.editable? and
- !object.owner_uuid.in?(my_folders.collect(&:uuid)))
+ !object.owner_uuid.in?(my_projects.collect(&:uuid)))
return link_to_if_arvados_object attrvalue
end
dataclass = ArvadosBase.resource_class_for_uuid(attrvalue)
end
+ id = "#{object.uuid}-#{subattr.join('-')}"
+ dn = "[#{attr}]"
+ subattr.each do |a|
+ dn += "[#{a}]"
+ end
+ if value_info.is_a? Hash
+ dn += '[value]'
+ end
+
+ if dataclass == Collection
+ selection_param = object.class.to_s.underscore + dn
+ display_value = attrvalue
+ if value_info.is_a?(Hash)
+ if (link = Link.find? value_info[:link_uuid])
+ display_value = link.name
+ elsif value_info[:link_name]
+ display_value = value_info[:link_name]
+ end
+ end
+ modal_path = choose_collections_path \
+ ({ title: 'Choose a dataset:',
+ filters: [['tail_uuid', '=', object.owner_uuid]].to_json,
+ action_name: 'OK',
+ action_href: pipeline_instance_path(id: object.uuid),
+ action_method: 'patch',
+ action_data: {
+ merge: true,
+ selection_param: selection_param,
+ success: 'page-refresh'
+ }.to_json,
+ })
+ return content_tag('div', :class => 'input-group') do
+ html = text_field_tag(dn, display_value,
+ :class =>
+ "form-control #{'required' if required}")
+ html + content_tag('span', :class => 'input-group-btn') do
+ link_to('Choose',
+ modal_path,
+ { :class => "btn btn-primary",
+ :remote => true,
+ :method => 'get',
+ })
+ end
+ end
+ end
+
if dataclass.andand.is_a?(Class)
datatype = 'select'
elsif dataclass == 'number'
datatype = 'text'
end
- id = "#{object.uuid}-#{subattr.join('-')}"
- dn = "[#{attr}]"
- subattr.each do |a|
- dn += "[#{a}]"
- end
- if value_info.is_a? Hash
- dn += '[value]'
- end
-
# preload data
preload_uuids = []
items = []
"data-title" => "Set value for #{subattr[-1].to_s}",
"data-name" => dn,
"data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
- "data-showbuttons" => "false",
"data-value" => attrvalue,
+ # "clear" button interferes with form-control's up/down arrows
+ "data-clear" => false,
:class => "editable #{'required' if required} form-control",
:id => id
}.merge(htmloptions)
button_href, params, *rest)
end
end
+
+ def render_controller_partial partial, opts
+ cname = opts.delete :controller_name
+ begin
+ render opts.merge(partial: "#{cname}/#{partial}")
+ rescue ActionView::MissingTemplate
+ render opts.merge(partial: "application/#{partial}")
+ end
+ end
+
+ def fa_icon_class_for_object object
+ case object.class.to_s.to_sym
+ when :User
+ 'fa-user'
+ when :Group
+ object.group_class ? 'fa-folder' : 'fa-users'
+ when :Job, :PipelineInstance, :PipelineTemplate
+ 'fa-gears'
+ when :Collection
+ 'fa-archive'
+ when :Specimen
+ 'fa-flask'
+ when :Trait
+ 'fa-clipboard'
+ when :Human
+ 'fa-male'
+ when :VirtualMachine
+ 'fa-terminal'
+ when :Repository
+ 'fa-code-fork'
+ when :Link
+ 'fa-arrows-h'
+ when :User
+ 'fa-user'
+ when :Node
+ 'fa-cloud'
+ when :KeepService
+ 'fa-exchange'
+ when :KeepDisk
+ 'fa-hdd-o'
+ else
+ 'fa-cube'
+ end
+ end
end
+++ /dev/null
-module FoldersHelper
-end
end
if pj[:job][:success]
pj[:result] = 'complete'
+ pj[:labeltype] = 'success'
pj[:complete] = true
pj[:progress] = 1.0
elsif pj[:job][:finished_at]
pj[:result] = 'failed'
+ pj[:labeltype] = 'danger'
pj[:failed] = true
elsif pj[:job][:started_at]
pj[:result] = 'running'
+ pj[:labeltype] = 'primary'
elsif pj[:job][:uuid]
pj[:result] = 'queued'
+ pj[:labeltype] = 'default'
else
pj[:result] = 'none'
+ pj[:labeltype] = 'default'
end
pj[:job_id] = pj[:job][:uuid]
pj[:script] = pj[:job][:script] || c[:script]
--- /dev/null
+module ProjectsHelper
+end
new.private_reload(hash)
end
+ def self.find?(*args)
+ find(*args) rescue nil
+ end
+
def self.order(*args)
ArvadosResourceList.new(self).order(*args)
end
uuid
end
- def dup
- super.forget_uuid!
+ def initialize_copy orig
+ super
+ forget_uuid!
end
def attributes_for_display
end
def class_for_display
- self.class.to_s
+ self.class.to_s.underscore.humanize
end
def self.creatable?
current_user
end
- def self.goes_in_folders?
+ def self.goes_in_projects?
false
end
resource_class
end
+ def resource_param_name
+ self.class.to_s.underscore
+ end
+
def friendly_link_name
- (name if self.respond_to? :name) || uuid
+ (name if self.respond_to? :name) || default_name
end
def content_summary
friendly_link_name
end
+ def self.default_name
+ self.to_s.underscore.humanize
+ end
+
+ def controller
+ (self.class.to_s.pluralize + 'Controller').constantize
+ end
+
+ def controller_name
+ self.class.to_s.tableize
+ end
+
+ # Placeholder for name when name is missing or empty
+ def default_name
+ if self.respond_to? :name
+ "New #{class_for_display.downcase}"
+ else
+ uuid
+ end
+ end
+
def owner
ArvadosBase.find(owner_uuid) rescue nil
end
class Collection < ArvadosBase
- include ApplicationHelper
-
MD5_EMPTY = 'd41d8cd98f00b204e9800998ecf8427e'
# Return true if the given string is the locator of a zero-length blob
!!locator.to_s.match("^#{MD5_EMPTY}(\\+.*)?\$")
end
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
def content_summary
- human_readable_bytes_html(total_bytes) + " " + super
+ ApplicationController.helpers.human_readable_bytes_html(total_bytes) + " " + super
end
def total_bytes
tot += file[2]
end
tot
+ else
+ 0
end
end
def files_tree
+ return [] if files.empty?
tree = files.group_by { |file_spec| File.split(file_spec.first) }
# Fill in entries for empty directories.
tree.keys.map { |basedir, _| File.split(basedir) }.each do |splitdir|
class Group < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
end
def class_for_display
- group_class == 'folder' ? 'Folder' : super
+ group_class.in?(['folder', 'project']) ? 'Project' : super
end
def editable?
class Human < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
end
class Job < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
+ def content_summary
+ "#{script} job"
+ end
+
def attribute_editable? attr, *args
false
end
false
end
+ def default_name
+ if script
+ x = "\"#{script}\" job"
+ else
+ x = super
+ end
+ if finished_at
+ x += " finished #{finished_at.strftime('%b %-d')}"
+ elsif started_at
+ x += " started #{started_at.strftime('%b %-d')}"
+ elsif created_at
+ x += " submitted #{created_at.strftime('%b %-d')}"
+ end
+ end
+
def cancel
arvados_api_client.api "jobs/#{self.uuid}/", "cancel", {}
end
def self.by_tail(t, opts={})
where(opts.merge :tail_uuid => t.uuid)
end
+
+ def default_name
+ self.class.resource_class_for_uuid(head_uuid).default_name rescue super
+ end
end
class PipelineInstance < ArvadosBase
attr_accessor :pipeline_template
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
+ def content_summary
+ begin
+ PipelineTemplate.find(pipeline_template_uuid).name
+ rescue
+ super
+ end
+ end
+
def update_job_parameters(new_params)
self.components[:steps].each_with_index do |step, i|
step[:params].each do |param|
class PipelineTemplate < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
class Specimen < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
end
class Trait < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
end
<%= message %><br />
<% end %>
<% else %>
- <% if obj.attribute_editable?(attr) %>
+ <% if obj.attribute_editable?(attr) and (!defined?(editable) || editable) %>
<%= render_editable_attribute obj, attr %>
<% if resource_class_for_uuid(attrvalue, {referring_object: obj, referring_attr: attr}) %>
<br />
--- /dev/null
+<div class="modal arv-choose">
+ <div class="modal-dialog" style="width:80%">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">×</button>
+ <h4 class="modal-title"><%= params[:title] || "Choose #{@objects.first.class_for_display}" %></h4>
+ </div>
+ <div class="modal-body">
+ <div>
+ <div class="row">
+ <div class="col-sm-6">
+ </div>
+ <div class="col-sm-6">
+ <input type="text" class="form-control filterable-control" placeholder="Search" data-filterable-target=".modal.arv-choose .selectable-container"/>
+ </div>
+ </div>
+ </div>
+ <div class="container-fluid arv-filterable-list selectable-container" style="height: 20em; 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>
+ </div>
+
+ <div class="modal-footer">
+ <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Cancel</button>
+ <button class="btn btn-primary" aria-hidden="true" data-enable-if-selection disabled><%= raw(params[:action_name]) || 'Select' %></button>
+ <div class="modal-error hide" style="text-align: left; margin-top: 1em;">
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
-$('body > .modal-container').html("<%= escape_javascript(render partial: 'choose.html') %>");
+$('body > .modal-container').html("<%= escape_javascript(render partial: 'choose.html', locals: {multiple: multiple}) %>");
$('body > .modal-container .modal').modal('show');
$('body > .modal-container .modal .modal-footer .btn-primary').
addClass('<%= j params[:action_class] %>').
+<% content_for :content_top do %>
+ <% if @object and not @object.is_a?(Group) and @object.class.goes_in_projects? and @object.owner_uuid == current_user.uuid %>
+ <div class="pull-right" style="width: 40%">
+ <div class="alert alert-warning alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+ <strong>Hey.</strong> This <%= @object.class_for_display.downcase %> belongs to your account, but it's not in any of your projects. If you want it to be easy to find in the future, you should move it to a project.<br />
+ <%= button_to(choose_projects_path(
+ title: 'Move to...',
+ editable: true,
+ action_name: 'Move',
+ action_href: url_for(action: :update),
+ action_method: 'patch',
+ action_data: {selection_param: @object.resource_param_name+'[owner_uuid]', success: 'page-refresh'}.to_json),
+ { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
+ <i class="fa fa-fw fa-folder"></i> Choose a project...
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+<% end %>
+
<% content_for :js do %>
tab_pane_valid_state = {};
<% end %>
<% end %>
- <div class="smart-scroll" style="margin-top:0.5em;">
+ <div id="<%= pane %>-scroll" class="<%= 'smart-scroll' if pane.match(/graph/) %>" style="margin-top:0.5em;">
<div class="pane-content">
<% if i == 0 %>
<%= render(partial: 'show_' + pane.downcase,
<%= content_for :content_top %>
-<%= content_for :tab_line_buttons %>
+<br clear="all" />
+<div class="pull-right">
+ <%= content_for :tab_line_buttons %>
+</div>
<%= content_for :tab_panes %>
<% if object.editable? %>
- <%= link_to({action: 'destroy', id: object.uuid}, method: :delete, remote: true, data: {confirm: "You are about to delete #{object.class_for_display.downcase} '#{object.friendly_link_name}' (#{object.uuid}).\n\nAre you sure?"}) do %>
+ <%= link_to({action: 'destroy', id: object.uuid}, method: :delete, remote: true, data: {confirm: "Really delete #{object.class_for_display.downcase} '#{object.friendly_link_name}'?"}) do %>
<i class="glyphicon glyphicon-trash"></i>
<% end %>
<% end %>
-<%if object and object.class.goes_in_folders? %>
+<%if object and object.class.goes_in_projects? %>
<% fn = if defined? friendly_name
friendly_name
else
--- /dev/null
+<div class="panel-group" id="arv-adv-accordion">
+ <% ['Metadata',
+ 'API response',
+ 'Python example',
+ 'CLI example',
+ 'curl example'].each do |section| %>
+ <% section_id = section.gsub(" ","_").downcase %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h4 class="panel-title">
+ <a data-toggle="collapse" data-parent="#arv-adv-accordion" href="#advanced_<%=section_id%>">
+ <%= section %>
+ </a>
+ </h4>
+ </div>
+ <div id="advanced_<%=section_id%>" class="panel-collapse collapse">
+ <div class="panel-body">
+ <%= render partial: "show_advanced_#{section_id}", locals: {object: @object} %>
+ </div>
+ </div>
+ </div>
+ <% end %>
+</div>
--- /dev/null
+<pre>
+<%= JSON.pretty_generate(object.attributes.reject { |k,v| k == 'id' }) rescue nil %>
+</pre>
--- /dev/null
+<pre>
+arv --pretty <%= object.class.to_s.underscore %> get \
+ --uuid <%= object.uuid %>
+
+arv <%= object.class.to_s.underscore %> update \
+ --uuid <%= object.uuid %> \
+ --<%= object.class.to_s.underscore.gsub '_', '-' %> '<%= JSON.generate({object.attributes.keys[-3] => object.attributes.values[-3]}).gsub("'","'\''") %>'
+</pre>
--- /dev/null
+<pre>
+curl -X PUT \
+ -H "Authorization: OAuth2 $ARVADOS_API_TOKEN" \
+ --data-urlencode <%= object.class.to_s.underscore %>@/dev/stdin \
+ https://$ARVADOS_API_HOST/arvados/v1/<%= object.class.to_s.pluralize.underscore %>/<%= object.uuid %> \
+ <<EOF
+<%= JSON.pretty_generate({object.attributes.keys[-3] => object.attributes.values[-3]}) %>
+EOF
+</pre>
--- /dev/null
+<% outgoing = Link.where(tail_uuid: @object.uuid) %>
+<% incoming = Link.where(head_uuid: @object.uuid) %>
+
+<%
+ preload_uuids = []
+ preload_head_uuids = []
+ outgoing.results.each do |link|
+ preload_uuids << link.uuid
+ preload_uuids << link.head_uuid
+ preload_head_uuids << link.head_uuid
+ end
+ preload_collections_for_objects preload_uuids
+ preload_links_for_objects preload_head_uuids
+%>
+
+<% if (outgoing | incoming).any? %>
+<table class="table topalign">
+ <colgroup>
+ <col width="20%" />
+ <col width="10%" />
+ <col width="10%" />
+ <col width="20%" />
+ <col width="20%" />
+ <col width="20%" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th></th>
+ <th>link_class</th>
+ <th>name</th>
+ <th>tail</th>
+ <th>head</th>
+ <th>properties</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% (outgoing | incoming).each do |link| %>
+ <tr>
+ <td>
+ <%= render partial: 'show_object_button', locals: { object: link, size: 'xs' } %>
+ <span class="arvados-uuid"><%= link.uuid %></span>
+ </td>
+ <td><%= link.link_class %></td>
+ <td><%= link.name %></td>
+ <td><%= link.tail_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "tail_uuid", attrvalue: link.tail_uuid, editable: false }) %></td>
+ <td><%= link.head_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "head_uuid", attrvalue: link.head_uuid, editable: false }) %></td>
+ <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties, editable: false } %></td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+<% else %>
+<span class="deemphasize">
+ (No metadata links found)
+</span>
+<% end %>
--- /dev/null
+<pre>
+import arvados
+
+x = arvados.api().<%= object.class.to_s.pluralize.underscore %>().get(uuid='<%= object.uuid %>').execute()
+</pre>
+++ /dev/null
-<pre>
-<%= JSON.pretty_generate(@object.attributes.reject { |k,v| k == 'id' }) rescue nil %>
-</pre>
+++ /dev/null
-<% outgoing = Link.where(tail_uuid: @object.uuid) %>
-<% incoming = Link.where(head_uuid: @object.uuid) %>
-
-<%
- preload_uuids = []
- preload_head_uuids = []
- outgoing.results.each do |link|
- preload_uuids << link.uuid
- preload_uuids << link.head_uuid
- preload_head_uuids << link.head_uuid
- end
- preload_collections_for_objects preload_uuids
- preload_links_for_objects preload_head_uuids
-%>
-
-<h3>Metadata about this object</h3>
-<% if outgoing.items_available > 0 %>
-<table class="table topalign">
- <thead>
- <tr>
- <th>metadata uuid</th>
- <th>class</th>
- <th>name</th>
- <th>properties</th>
- <th>object</th>
- </tr>
- </thead>
- <tbody>
- <% outgoing.each do |link| %>
- <tr>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "uuid", attrvalue: link.uuid } %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "link_class", attrvalue: link.link_class } %></td>
- <td><%= render_editable_attribute link, 'name' %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties } %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "head_uuid", attrvalue: link.head_uuid } %></td>
- </tr>
- <% end %>
- </tbody>
-</table>
-<% else %>
-No metadata.
-<% end %>
-
-<h3>Metadata that refers to this object</h3>
-<% if incoming.items_available > 0 %>
-<table class="table topalign">
- <thead>
- <tr>
- <th>metadata uuid</th>
- <th>subject</th>
- <th>class</th>
- <th>name</th>
- <th>properties</th>
- </tr>
- </thead>
- <tbody>
- <% incoming.each do |link| %>
- <tr>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "uuid", attrvalue: link.uuid } %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "tail_uuid", attrvalue: link.tail_uuid } %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "link_class", attrvalue: link.link_class } %></td>
- <td><%= render_editable_attribute link, 'name' %></td>
- <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties } %></td>
- </tr>
- <% end %>
- </tbody>
-</table>
-<% else %>
-No metadata.
-<% end %>
<% htmloptions = {class: ''}.merge(htmloptions || {})
- htmloptions[:class] += " btn-#{size}" rescue nil %>
-<%= link_to_if_arvados_object object, { link_text: raw('Show <i class="fa fa-fw fa-arrow-circle-right"></i>') }, { class: 'btn btn-default ' + htmloptions[:class] } %>
+ htmloptions[:class] += " btn-#{size}" rescue nil
+ link_text = 'Show' unless defined?(link_text) and link_text
+ %>
+<%= link_to_if_arvados_object object, {
+ link_text: raw('<i class="fa fa-fw ' + fa_icon_class_for_object(object) + '"></i> ' + link_text),
+ name_link: (defined?(name_link) && name_link && name_link.uuid) ? name_link : nil
+ }, {
+ data: {
+ toggle: 'tooltip',
+ placement: 'top'
+ },
+ title: 'show ' + object.class_for_display.downcase,
+ class: 'btn btn-default ' + htmloptions[:class],
+ } %>
--- /dev/null
+<%= object.content_summary %>
+
<table class="table table-condensed arv-index">
<thead>
<tr>
- <% if objects.first and objects.first.class.goes_in_folders? %>
+ <% if objects.first and objects.first.class.goes_in_projects? %>
<th></th>
<% end %>
<th></th>
<tbody>
<% objects.each do |object| %>
<tr data-object-uuid="<%= object.uuid %>">
- <% if objects.first.class.goes_in_folders? %>
+ <% if objects.first.class.goes_in_projects? %>
<td>
<%= render :partial => "selection_checkbox", :locals => {:object => object} %>
</td>
border-width: 1px;
border-color: gray;
position: absolute;
- left: 225px;
- right: 1px;
+ left: 25px;
+ right: 25px;
}
path:hover {
stroke-width: 5;
<% if controller.model_class.creatable? %>
<% if controller.model_class.name == 'User' %>
- <%= link_to "Add a new #{controller.model_class.to_s.underscore.gsub '_', ' '}", setup_user_popup_path,
- {class: 'btn btn-primary pull-right', :remote => true, 'data-toggle' => "modal",
- 'data-target' => '#user-setup-modal-window', return_to: request.url} %>
+ <%= link_to setup_user_popup_path,
+ {class: 'btn btn-sm btn-primary', :remote => true, 'data-toggle' => "modal",
+ 'data-target' => '#user-setup-modal-window', return_to: request.url} do %>
+ <i class="fa fa-fw fa-plus"></i> Add a new user
+ <% end %>
<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.controller_name.singularize.humanize.downcase}",
- { action: 'create' },
- { class: 'btn btn-primary pull-right' } %>
+ <%= button_to({action: 'create'}, {class: 'btn btn-sm btn-primary'}) do %>
+ <i class="fa fa-fw fa-plus"></i>
+ Add a new
+ <%= controller.controller_name.singularize.humanize.downcase %>
+ <% end %>
<% end %>
<% end %>
<% content_for :page_title do %>
<%= (@object.respond_to?(:properties) ? @object.properties[:page_title] : nil) ||
- @object.friendly_link_name %>
+ @name_link.andand.name ||
+ @object.friendly_link_name %>
<% end %>
<% content_for :content_top do %>
--- /dev/null
+../application/_choose.js.erb
\ No newline at end of file
--- /dev/null
+<% @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 %>">
+ <div class="col-sm-12" style="overflow-x:hidden">
+ <i class="fa fa-fw fa-archive"></i>
+ <%= name_link.name %>
+ </div>
+ <div class="col-sm-11 col-sm-push-1" style="overflow-x: hidden">
+ <%= render_controller_partial(
+ 'show_object_description_cell.html',
+ controller_name: 'collections',
+ locals: {object: object})
+ %>
+ </div>
+ </div>
+ <% end %>
+<% end %>
<% friendly_name += raw(" <span class='label label-info'>#{tag_link.name}</span>") %>
<% end %>
<%= render :partial => "selection_checkbox", :locals => {:object => c, :friendly_name => friendly_name} %>
+
+ <%= render :partial => "show_object_button", :locals => {object: c, size: 'xs'} %>
</td>
<td>
- <%= link_to_if_arvados_object c.uuid, {:no_tags => true } %>
+ <%= c.uuid %>
</td>
<td>
<% i = 0 %>
<% content_for :tab_line_buttons do %>
-<div class="row">
- <div class="col-md-6"></div>
- <div class="col-md-6">
- <div class="pull-right">
- <span style="padding-left: 1em">Collection storage status:</span>
- <%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
-
- </div>
- </div>
-</div>
+<span style="padding-left: 1em">Collection storage status:</span>
+<%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
<% end %>
<% file_tree = @object.andand.files_tree %>
--- /dev/null
+<div class="nowrap">
+ <span class="deemphasize">
+ Files (<%= human_readable_bytes_html(object.total_bytes) %>):
+ </span><span class="arvados-filename">
+ <% object.files.each do |path, file, size| %>
+ <%= file %>
+ <% end %>
+ </span>
+ <div>
+ <% Link.filter([['link_class','=','tag'],['head_uuid','=',object.uuid]]).collect(&:name).each do |tagname| %>
+ <span class="label label-info"><%= tagname %></span>
+ <% end %>
+ </div>
+</div>
<table id="collections-index" class="topalign table table-condensed table-fixedlayout"> <!-- table-fixed-header-row -->
<colgroup>
- <col width="4%" />
+ <col width="8%" />
<col width="10%" />
<col width="34%" />
- <col width="15%" />
+ <col width="11%" />
<col width="12%" />
<col width="29%" />
</colgroup>
<% end %>
<% if @output_of.andand.any? %>
- <p>Output of jobs:<br />
+ <p>This collection was the output of:<br />
<%= render_arvados_object_list_start(@output_of, 'Show all jobs',
jobs_path(filter: [['output', '=', @object.uuid]].to_json)) do |job| %>
- <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+ <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
<% if @log_of.andand.any? %>
- <p>Log of jobs:<br />
+ <p>This collection contains log messages from:<br />
<%= render_arvados_object_list_start(@log_of, 'Show all jobs',
jobs_path(filter: [['log', '=', @object.uuid]].to_json)) do |job| %>
- <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+ <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
<input type="text" class="form-control" placeholder="Search"/>
-->
<div style="height:0.5em;"></div>
- <% if not @logs.andand.any? %>
+ <% name_or_object = @name_link.andand.uuid ? @name_link : @object %>
+ <% if name_or_object.created_at and not @logs.andand.any? %>
<p>
- Created: <%= @object.created_at.to_s(:long) %>
+ Created: <%= name_or_object.created_at.to_s(:long) %>
</p>
<p>
- Last modified: <%= @object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object @object.modified_by_user_uuid, friendly_name: true %>
+ Last modified: <%= name_or_object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object name_or_object.modified_by_user_uuid, friendly_name: true %>
</p>
<% else %>
<%= render_arvados_object_list_start(@logs, 'Show all activity',
- logs_path(filters: [['object_uuid','=',@object.uuid]].to_json)) do |log| %>
+ logs_path(filters: [['object_uuid','=',name_or_object.uuid]].to_json)) do |log| %>
<p>
<%= time_ago_in_words(log.event_at) rescue 'unknown time' %> ago: <%= log.summary %>
<% if log.object_uuid %>
</div>
<div style="height:0.5em;"></div>
- <% if @folders.andand.any? %>
- <p>Included in folders:<br />
- <%= render_arvados_object_list_start(@folders, 'Show all folders',
+ <% if @projects.andand.any? %>
+ <p>Included in projects:<br />
+ <%= render_arvados_object_list_start(@projects, 'Show all projects',
links_path(filter: [['head_uuid', '=', @object.uuid],
- ['link_class', '=', 'name']].to_json)) do |folder| %>
- <%= link_to_if_arvados_object(folder, friendly_name: true) %><br />
+ ['link_class', '=', 'name']].to_json)) do |project| %>
+ <%= link_to_if_arvados_object(project, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
+++ /dev/null
-<div class="container-fluid arv-folder-list">
- <% [@my_folder_tree, @shared_folder_tree].each do |tree| %>
- <% tree.each do |foldernode| %>
- <% rowtype = foldernode[:object].class %>
- <div class="<%= 'folder' if rowtype == Group %> row" style="padding-left: <%= 1 + foldernode[:depth] %>em;">
- <% if rowtype == String %>
- <i class="fa fa-fw fa-folder-open-o"></i>
- <%= foldernode[:object] %>
- <% elsif rowtype == User %>
- <% if foldernode[:object].uuid == current_user.andand.uuid %>
- <i class="fa fa-fw fa-folder-open-o"></i>
- My Folders
- <% else %>
- <i class="fa fa-fw fa-folder-o"></i>
- <%= foldernode[:object].friendly_link_name %>
- <% end %>
- <% else %>
- <i class="fa fa-fw fa-folder-o"></i>
- <%= link_to foldernode[:object] do %>
- <%= foldernode[:object].friendly_link_name %>
- <% end %>
- <div class="pull-right">
- <%= render partial: 'delete_object_button', locals: {object: foldernode[:object]} %>
- </div>
- <% end %>
- </div>
- <% end %>
- <% end %>
-</div>
+++ /dev/null
-<div class="row row-fill-height">
- <div class="col-md-6">
- <div class="panel panel-info">
- <div class="panel-heading">
- <h3 class="panel-title">
- <%= render_editable_attribute @object, 'name', nil, {data: {emptytext: "New folder"}} %>
- </h3>
- </div>
- <div class="panel-body">
- <img src="/favicon.ico" class="pull-right" alt="" style="opacity: 0.3"/>
- <%= render_editable_attribute @object, 'description', nil, { 'data-emptytext' => "Created: #{@object.created_at.to_s(:long)}", 'data-toggle' => 'manual', 'id' => "#{@object.uuid}-description" } %>
- <% if @object.attribute_editable? 'description' %>
- <div style="margin-top: 1em;">
- <a href="#" class="btn btn-xs btn-default" data-toggle="x-editable" data-toggle-selector="#<%= @object.uuid %>-description"><i class="fa fa-fw fa-pencil"></i> Edit description</a>
- </div>
- <% end %>
- </div>
- </div>
- </div>
- <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 smaller-text">
- <!--
- <input type="text" class="form-control" placeholder="Search"/>
- -->
- <div style="height:0.5em;"></div>
- <% if @logs.any? %>
- <%= render_arvados_object_list_start(@logs, 'Show all activity',
- logs_path(filters: [['object_uuid','=',@object.uuid]].to_json)) do |log| %>
- <p>
- <%= time_ago_in_words(log.event_at) %> ago: <%= log.summary %>
- <% if log.object_uuid %>
- <%= link_to_if_arvados_object log.object_uuid, link_text: raw('<i class="fa fa-hand-o-right"></i>') %>
- <% end %>
- </p>
- <% end %>
- <% else %>
- <p>
- Created: <%= @object.created_at.to_s(:long) %>
- </p>
- <p>
- Last modified: <%= @object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object @object.modified_by_user_uuid, friendly_name: true %>
- </p>
- <% end %>
- </div>
- </div>
- </div>
- <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>
- <% if @object.owner %>
- <p>Permissions inherited from:
- <br />
- <% if User == resource_class_for_uuid(@object.owner_uuid) %>
- <i class="fa fa-fw fa-user"></i>
- <% else %>
- <i class="fa fa-fw fa-folder"></i>
- <% end %>
- <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
- <%= button_to('Move to...',
- choose_folders_path(
- title: 'Move to...',
- editable: true,
- action_name: 'Move',
- action_href: folder_path(@object.uuid),
- action_method: 'put',
- action_data: {selection_param: 'folder[owner_uuid]'}.to_json),
- { class: "btn btn-default btn-xs arv-move-to-folder", remote: true, method: 'get' }) %>
- </p>
- <hr />
- <% end %>
- <p>
- <% if not @share_links.any? %>
- <span class="deemphasize">(No additional permissions)</span>
- <% else %>
- Also shared with:
- <% @share_links.andand.each do |link| %>
- <br /><%= link_to_if_arvados_object link.tail_uuid, friendly_name: true %>
- <% end %>
- <% end %>
- </p>
- </div>
- </div>
- </div>
-</div>
-
-<% if @show_cards %>
-<!-- disable cards section until we have bookmarks -->
-<div class="row">
- <% @objects[0..3].each do |object| %>
- <div class="card arvados-object">
- <div class="card-top blue">
- <a href="#">
- <img src="/favicon.ico" alt=""/>
- </a>
- </div>
- <div class="card-info">
- <span class="title"><%= @objects.name_for(object) || object.class_for_display %></span>
- <div class="desc"><%= object.respond_to?(:description) ? object.description : object.uuid %></div>
- </div>
- <div class="card-bottom">
- <%= render :partial => "show_object_button", :locals => {object: object, htmloptions: {class: 'btn-default btn-block'}} %>
- </div>
- </div>
- <% end %>
-</div>
-<!-- end disabled cards section -->
-<% end %>
-
-<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 search-folder-contents" placeholder="Search folder contents"/>
- </div>
- </div>
- </div>
- </div>
- <div class="panel-body">
- <p>
- </p>
- <table class="table table-condensed arv-index">
- <tbody>
- <colgroup>
- <col width="3%" />
- <col width="8%" />
- <col width="30%" />
- <col width="15%" />
- <col width="15%" />
- <col width="20%" />
- <col width="8%" />
- </colgroup>
- <% @objects_and_names.each do |object, name_link| %>
- <tr data-object-uuid="<%= (name_link && name_link.uuid) || object.uuid %>">
- <td>
- <%= render :partial => "selection_checkbox", :locals => {object: object} %>
- </td>
- <td>
- <%= render :partial => "show_object_button", :locals => {object: object, size: 'xs'} %>
- </td>
- <td>
- <%= render_editable_attribute name_link, 'name', nil, {data: {emptytext: "Unnamed #{object.class_for_display}"}} %>
- </td>
- <td>
- <%= object.content_summary %>
- </td>
- <td title="<%= object.modified_at %>">
- <span>
- <%= raw distance_of_time_in_words(object.modified_at, Time.now).sub('about ','~').sub(' ',' ') + ' ago' rescue object.modified_at %>
- </span>
- </td>
- <td class="arvados-uuid">
- <%= object.uuid %>
- </td>
- <td>
- <% if @object.editable? %>
- <%= link_to({action: 'remove_item', id: @object.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "You are about to remove #{object.class_for_display} #{object.uuid} from this folder.\n\nAre you sure?"}, class: 'btn btn-xs btn-default') do %>
- Remove <i class="fa fa-fw fa-ban"></i>
- <% end %>
- <% end %>
- </td>
- </tr>
- <% end %>
- </tbody>
- <thead>
- <tr>
- <th>
- </th>
- <th>
- </th>
- <th>
- name
- </th>
- <th>
- type
- </th>
- <th>
- modified
- </th>
- <th>
- uuid
- </th>
- <th>
- </th>
- </tr>
- </thead>
- </table>
- <p></p>
- </div>
- </div>
- </div>
-</div>
--- /dev/null
+<%= render partial: 'application/show_attributes' %>
--- /dev/null
+<div class="nowrap">
+ <div class="row">
+ <div class="col-sm-2 inline-progress-container">
+ <%= render partial: 'job_progress', locals: {j: object} %>
+ </div>
+ <div class="col-sm-10">
+ <%= object.script %>
+ <span class="deemphasize">
+ job
+ using <%= object.script_version %> commit
+ from <%= object.repository %> repository
+ </span>
+ </div>
+ </div>
+</div>
height: 100%;
}
- body > div.container-fluid {
- padding-top: 70px; /* 70px to make the container go all the way to the bottom of the navbar */
- }
-
@media (max-width: 979px) { body { padding-top: 0; } }
- .navbar .nav li.nav-separator > span.glyphicon.glyphicon-arrow-right {
- padding-top: 1.25em;
- }
-
@media (max-width: 767px) {
.breadcrumbs {
display: none;
}
}
</style>
- <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
</head>
<body>
- <div id="wrapper">
+ <div id="wrapper" class="container-fluid">
<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=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" href="/"><%= Rails.configuration.site_name rescue Rails.application.class.parent_name %></a>
+ <a class="navbar-brand" href="/"><%= Rails.configuration.site_name.downcase rescue Rails.application.class.parent_name %></a>
</div>
<div class="collapse navbar-collapse">
- <% if current_user.andand.is_active %>
- <ul class="nav navbar-nav side-nav">
-
- <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 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-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
- <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
- <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
- </ul>
- </li>
-
- <li class="dropdown">
- <a href="/folders">
- <i class="fa fa-lg fa-folder-o fa-fw"></i> Folders
- </a></li>
- <li><a href="/collections">
- <i class="fa fa-lg fa-briefcase fa-fw"></i> Collections (data files)
- </a></li>
- <li><a href="/jobs">
- <i class="fa fa-lg fa-tasks fa-fw"></i> Jobs
- </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> </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-terminal fa-fw"></i> Virtual machines
- </a></li>
- <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>
- <% 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-cloud fa-fw"></i> Compute nodes
- </a></li>
- <li><a href="/keep_services">
- <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
- </a></li>
- <li><a href="/keep_disks">
- <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
- </a></li>
- </ul>
- <% end %>
-
- <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.controller_name.humanize.downcase,
- 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, {friendly_name: true}, {data: {object_uuid: @object.andand.uuid, name: 'name'}} %>
- </li>
- <li style="padding: 14px 0 14px">
- <%= form_tag do |f| %>
- <%= render :partial => "selection_checkbox", :locals => {:object => @object} %>
- <% end %>
- </li>
- <% end %>
- <% end %>
- <% end %>
- </ul>
-
<ul class="nav navbar-nav navbar-right">
<li>
</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 %>
- <%= hidden_field_tag 'uuid', @object.andand.uuid %>
- <div id="selection-form-content"></div>
- <% end %>
- </ul>
- </li>
-
- <% if current_user.is_active %>
+ <% if current_user %>
<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>
+ <%= current_user.email %>
</a>
<ul class="dropdown-menu" role="menu">
- <% if (@notifications || []).length > 0 %>
+ <% if current_user.is_active %>
+ <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>
+ <% if current_user.is_active and
+ (@notifications || []).length > 0 %>
+ <li role="presentation" class="divider"></li>
<% @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>
+ <li class="dropdown notification-menu">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
+ <span class="fa fa-lg fa-paperclip"></span>
+ <span class="badge" id="persistent-selection-count"></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>
+ <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
+ <%= form_tag '/actions' do %>
+ <%= hidden_field_tag 'uuid', @object.andand.uuid %>
+ <div id="selection-form-content"></div>
<% 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>
+
+ <% if current_user.is_active %>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="system-menu">
+ <span class="fa fa-lg fa-gear"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li role="presentation" class="dropdown-header">
+ System tools
+ </li>
+ <li role="presentation"><a href="/repositories">
+ <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
+ </a></li>
+ <li role="presentation"><a href="/virtual_machines">
+ <i class="fa fa-lg fa-terminal fa-fw"></i> Virtual machines
+ </a></li>
+ <li role="presentation"><a href="/links">
+ <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
+ </a></li>
+ <% if current_user.andand.is_admin %>
+ <li role="presentation"><a href="/users">
+ <i class="fa fa-lg fa-user fa-fw"></i> Users
+ </a></li>
+ <% end %>
+ <li role="presentation"><a href="/groups">
+ <i class="fa fa-lg fa-users fa-fw"></i> Groups
+ </a></li>
+ <li role="presentation"><a href="/nodes">
+ <i class="fa fa-lg fa-cloud fa-fw"></i> Compute nodes
+ </a></li>
+ <li role="presentation"><a href="/keep_services">
+ <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
+ </a></li>
+ <li role="presentation"><a href="/keep_disks">
+ <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
+ </a></li>
+ </ul>
+ </li>
+ <% end %>
<% else %>
<li><a href="<%= arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
<% end %>
</div><!-- /.navbar-collapse -->
</nav>
+ <% if current_user.andand.is_active %>
+ <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">
+ <i class="fa fa-lg fa-fw fa-home"></i>
+ Projects
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li role="presentation" class="dropdown-header">
+ <%= link_to projects_path('project[owner_uuid]' => current_project_uuid), method: 'post', class: 'btn btn-xs btn-default pull-right' do %>
+ <i class="fa fa-plus"></i> New project
+ <% end %>
+ My projects
+ </li>
+ <% my_projects.each do |p| %>
+ <li>
+ <%= link_to(p.name, project_path(p.uuid)) %>
+ </li>
+ <% end %>
+ <li class="divider">
+ <li role="presentation" class="dropdown-header">
+ Projects shared with me
+ </li>
+ <% projects_shared_with_me.each do |p| %>
+ <li>
+ <%= link_to project_path(p.uuid) do %>
+ <i class="fa fa-fw fa-share-alt"></i> <%= p.name %>
+ <% end %>
+ </li>
+ <% end %>
+ </ul>
+ </li>
+ <% project_breadcrumbs.each do |p| %>
+ <li class="nav-separator">
+ <i class="fa fa-lg fa-angle-double-right"></i>
+ </li>
+ <li>
+ <%= link_to(p.name, project_path(p.uuid), data: {object_uuid: p.uuid, name: 'name'}) %>
+ </li>
+ <% end %>
+ <% if current_project_uuid.andand != @object.andand.uuid %>
+ <li class="nav-separator">
+ <i class="fa fa-lg fa-angle-double-right"></i>
+ </li>
+ <% end %>
+ </ul>
+ </nav>
+ <% end %>
+
<div id="page-wrapper">
<%= yield %>
</div>
--- /dev/null
+<% pipeline_jobs(object).each do |pj| %>
+ <span class="label label-<%= pj[:labeltype] %>">
+ <%= pj[:name] %>
+ </span>
+<% end %>
</div>
<% @objects.each do |object| %>
<div class="col-sm-<%= pi_span %>" style="overflow-x: hidden; text-overflow: ellipsis;">
- <%= link_to_if_arvados_object object, friendly_name: true %>
+ <%= render :partial => "show_object_button", :locals => {object: object, size: 'sm' } %>
+ <%= object.name || "unnamed #{object.class_for_display.downcase}" %>
<br />
- Template: <%= link_to_if_arvados_object object.pipeline_template_uuid, friendly_name: true %>
+ <span class="deemphasize">Template:</span> <%= link_to_if_arvados_object object.pipeline_template_uuid, friendly_name: true %>
</div>
<% end %>
</div>
-<% content_for :css do %>
-
-<% end %>
-
<% template = PipelineTemplate.find(@object.pipeline_template_uuid) rescue nil %>
-
<%= content_for :content_top do %>
<h2>
- <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => 'Unnamed pipeline' } %>
+ <%= render_editable_attribute @object, 'name', nil %>
</h2>
<% if template %>
- <h4>
- From template:
- <%= link_to_if_arvados_object template, friendly_name: true %>
- </h4>
+ <blockquote><span class="deemphasize">From template:</span><br />
+ <%= link_to_if_arvados_object template, friendly_name: true %><br />
+ <%= template.description %>
+ </blockquote>
+ <% end %>
+<% end %>
+
+<% content_for :tab_line_buttons do %>
+ <%= link_to(copy_pipeline_instance_path('id' => @object.uuid, 'pipeline_instance[state]' => 'New'),
+ class: 'btn btn-primary',
+ #data: {toggle: :tooltip, placement: :top}, title: 'copy and modify',
+ method: :post,
+ ) do %>
+ Clone and edit <i class="fa fa-fw fa-copy"></i>
<% end %>
<% end %>
-<% pipeline_job_uuids = [] %>
+<% if !@object.state.in? ['New', 'Ready'] %>
-<% if !@object.state.in? ['New', 'Ready', 'Paused'] %>
-<table class="table pipeline-components-table">
- <colgroup>
- <col style="width: 15%" />
- <col style="width: 20%" />
- <col style="width: 12%" />
- <col style="width: 12%" />
- <col style="width: 45%" />
- </colgroup>
- <thead>
- <tr>
- <th>
- component
- </th><th>
- script, version
- </th><th>
- job
- <%# format:'js' here helps browsers avoid using the cached js
- content in html context (e.g., duplicate tab -> see
- javascript) %>
- <%= link_to '(refresh)', {format: :js}, {class: 'refresh hide', remote: true, method: 'get'} %>
- </th><th>
- </th><th>
- output
- </th>
- </tr>
- </thead>
- <tbody>
- <% render_pipeline_jobs.each do |pj| %>
- <% if pj[:job].andand[:uuid]
- pipeline_job_uuids << pj[:job][:uuid]
- end %>
- <tr>
- <td>
- <%= pj[:name] %>
- </td><td>
- <%= pj[:script] %>
- <br /><span class="deemphasize"><%= pj[:script_version] %></span>
- </td><td>
- <%= pj[:progress_bar] %>
- <% if @object.state == 'Complete' || @object.state == 'Failed' %>
- <% if pj[:job].andand[:uuid] %>
- <span class="deemphasize">
- <%= link_to("..."+pj[:job][:uuid].last(15), job_url(id: pj[:job][:uuid])) %>
- </span>
+ <% content_for :tab_line_buttons do %>
+ <% if @object.state.in? ['RunningOnClient', 'RunningOnServer'] %>
+ <%= link_to(url_for('pipeline_instance[state]' => 'Paused'),
+ class: 'btn btn-primary run-pipeline-button',
+ method: :patch
+ ) do %>
+ Stop <i class="fa fa-fw fa-stop"></i>
+ <% end %>
+ <% elsif @object.state == 'Paused' %>
+ <%= link_to(url_for('pipeline_instance[state]' => 'RunningOnServer'),
+ class: 'btn btn-primary run-pipeline-button',
+ method: :patch
+ ) do %>
+ Resume <i class="fa fa-fw fa-play"></i>
+ <% end %>
+ <% end %>
+ <% end %>
- <% current_job = Job.find(pj[:job][:uuid]) rescue nil %>
- <% if current_job.andand[:log] %>
- <% fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(current_job[:log])%>
- <% Collection.limit(1).where(uuid: fixup[1]).each do |c| %>
- <% c.files.each do |file| %>
- <br/><span class="deemphasize">
- <a href="<%= collection_path(current_job[:log]) %>/<%= file[1] %>?disposition=inline&size=<%= file[2] %>">log</a>
- </span>
+ <% pipeline_job_uuids = [] %>
+
+ <div class="pull-right">
+ Current state: <span class="badge badge-info"><%= @object.state.sub('OnServer', '') %></span>
+ </div>
+
+ <table class="table pipeline-components-table">
+ <colgroup>
+ <col style="width: 15%" />
+ <col style="width: 25%" />
+ <col style="width: 8%" />
+ <col style="width: 13%" />
+ <col style="width: 12%" />
+ <col style="width: 14%" />
+ <col style="width: 13%" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th colspan="2">
+ component
+ </th><th colspan="5">
+ job
+ <%# format:'js' here helps browsers avoid using the cached js
+ content in html context (e.g., duplicate tab -> see
+ javascript) %>
+ <%= link_to '(refresh)', {format: :js}, {class: 'refresh hide', remote: true, method: 'get'} %>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <% render_pipeline_jobs.each do |pj| %>
+ <% if pj[:job].andand[:uuid]
+ pipeline_job_uuids << pj[:job][:uuid]
+ end %>
+ <tr>
+ <td>
+ <%= pj[:name] %>
+ </td><td>
+ <%= pj[:script] %>
+ <br /><span class="deemphasize"><%= pj[:script_version] %></span>
+ </td><td>
+ <%= render(partial: 'job_status_label', locals: { j: pj[:job] }) %>
+ </td><td>
+ <%= pj[:progress_bar] %>
+ </td>
+ <% current_job = Job.find(pj[:job][:uuid]) rescue nil %>
+ <td>
+ <% if current_job %>
+ <%= render partial: 'show_object_button', locals: {object: current_job, size: 'xs', link_text: 'Show job details'} %>
+ <% end %>
+ </td><td>
+ <% if current_job.andand[:log] %>
+ <% fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(current_job[:log])%>
+ <% Collection.limit(1).where(uuid: fixup[1]).each do |c| %>
+ <% c.files.first.andand do |file| %>
+ <%= link_to url_for(controller: 'collections', action: 'show_file', uuid: current_job[:log], file: "#{file[0]}/#{file[1]}", disposition: 'inline', size: file[2]), class: 'btn btn-default btn-xs' do %>
+ <i class="fa fa-fw fa-info"></i> Show log messages
<% end %>
<% end %>
<% end %>
<% end %>
- <% end %>
- </td><td>
- <%= render(partial: 'job_status_label',
- locals: { :j => pj[:job] }) %>
- </td><td>
- <%= link_to_if_arvados_object pj[:output], {:thumbnail => true} %>
- </td>
- </tr>
- <% end %>
- </tbody>
- <tfoot>
- <tr><td colspan="5"></td></tr>
- </tfoot>
-</table>
-
-<% if @object.state == 'RunningOnServer' || @object.state == 'RunningOnClient' %>
-
-<% content_for :tab_line_buttons do %>
- <%= form_tag @object, :method => :put do |f| %>
+ </td><td>
+ <% if current_job.andand[:output] %>
+ <%= link_to_if_arvados_object current_job[:output], {thumbnail: true, link_text: raw('<i class="fa fa-fw fa-archive"></i> Show output files')}, {class: 'btn btn-default btn-xs'} %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ <tfoot>
+ <tr><td colspan="7"></td></tr>
+ </tfoot>
+ </table>
- <%= hidden_field @object.class.to_s.underscore.singularize.to_sym, :state, :value => 'Paused' %>
+ <% if @object.state.in? %w(RunningOnServer RunningOnClient) %>
- <%= button_tag({class: 'btn btn-primary pull-right run-pipeline-button'}) do %>
- Stop <i class="fa fa-fw fa-stop"></i>
+ <% content_for :js do %>
+ setInterval(function(){$('a.refresh').click()}, 15000);
<% end %>
- <% end %>
-<% end %>
<% if !pipeline_job_uuids.empty? %>
<h4>Log messages from running jobs</h4>
<% log_history = pipeline_log_history(pipeline_job_uuids) %>
- <div id="pipeline_event_log_history_div">
+ <div class="arv-log-event-listener arv-log-event-handler-append-logs arv-job-log-window" id="pipeline_event_log_div" data-object-uuids="<%=pipeline_job_uuids.join(" ")%>">
<% log_history.each do |entry| %>
<%=entry%><br/>
<% end %>
</div>
- <div class="arv-log-event-listener arv-log-event-handler-append-logs" id="pipeline_event_log_div" data-object-uuids="<%=pipeline_job_uuids.join(" ")%>"/>
<% end %>
- <% end %>
-<% else %> <%# State new or ready or paused %>
- <% if @object.state == 'New' %>
- <p>Please set the desired input parameters for the components of this pipeline. Parameters highlighted in red are required.</p>
<% end %>
- <% content_for :tab_line_buttons do %>
- <%= form_tag @object, :method => :put do |f| %>
+<% else %>
+ <%# state is either New or Ready %>
- <%= hidden_field @object.class.to_s.underscore.singularize.to_sym, :state, :value => 'RunningOnServer' %>
+ <% if @object.state.in? %w(New Ready) %>
+ <p><i>Here are all of the pipeline's components (jobs that will need to run in order to complete the pipeline). If you know what you're doing (or you're experimenting) you can modify these parameters before starting the pipeline. Usually, you only need to edit the settings presented on the "Inputs" tab above.</i></p>
+ <% end %>
- <%= button_tag({class: 'btn btn-primary pull-right run-pipeline-button'}) do %>
- Run <i class="fa fa-fw fa-play"></i>
- <% end %>
+ <% content_for :tab_line_buttons do %>
+ <%= link_to(url_for('pipeline_instance[state]' => 'RunningOnServer'),
+ class: 'btn btn-primary run-pipeline-button',
+ method: :patch
+ ) do %>
+ Run <i class="fa fa-fw fa-play"></i>
<% end %>
<% end %>
<% component[:script_parameters].andand.each do |pname, pvalue_spec| %>
<% if pvalue_spec.is_a? Hash %>
<% if (pvalue_spec[:description] or
- (pvalue_spec[:required] and not pvalue_spec[:value])) %>
+ ((pvalue_spec[:required] or pvalue_spec[:optional] == false) and
+ not pvalue_spec[:value])) %>
<% n_inputs += 1 %>
<label for="<% "#{cname}-#{pname}" %>">
<%= pvalue_spec[:title] ||
<% end %>
<% if n_inputs == 0 %>
- <p>This pipeline does not need any further inputs specified. You can start it by clicking the "Run" button.</p>
+ <p>This pipeline does not need any further inputs specified. You can start it by clicking the "Run" button whenever you're ready. (It's not too late to change existing settings, though.)</p>
<% else %>
<p><i>Provide <%= n_inputs > 1 ? 'values' : 'a value' %> for the following <%= n_inputs > 1 ? 'parameters' : 'parameter' %>, then click the "Run" button to start the pipeline.</i></p>
<%= content_for :pi_input_form %>
- <%= form_tag @object, :method => :put do |f| %>
- <%= hidden_field @object.class.to_s.underscore.singularize.to_sym, :state, :value => 'RunningOnServer' %>
- <%= button_tag({class: 'btn btn-primary run-pipeline-button'}) do %>
- Run <i class="fa fa-fw fa-play"></i>
- <% end %>
+ <%= link_to(url_for('pipeline_instance[state]' => 'RunningOnServer'),
+ class: 'btn btn-primary run-pipeline-button',
+ method: :patch
+ ) do %>
+ Run <i class="fa fa-fw fa-play"></i>
<% end %>
<% end %>
<div style="margin-top: 1em;">
- <p>Click the "Components" tab above to see a full list of pipeline components and parameters.</p>
+ <p>Click the "Components" tab above to see a full list of pipeline settings.</p>
</div>
--- /dev/null
+<div class="nowrap">
+ <%= object.content_summary %><br />
+ <%= render partial: 'pipeline_instances/component_labels', locals: {object: object} %>
+</div>
<% end %>
<%= render partial: "paging", locals: {results: @objects, object: @object} %>
-
-<% content_for :footer_js do %>
-var showhide_compare = function() {
- var form = $('form#compare')[0];
- $('input[type=hidden][name="uuids[]"]', form).remove();
- $('input[type=submit]', form).prop('disabled',true).show();
- var checked_inputs = $('input[name="uuids[]"]:checked');
- if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
- checked_inputs.each(function(){
- if(this.checked) {
- $('input[type=submit]', form).prop('disabled',false).show();
- $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
- }
- });
- }
-};
-$('form input[name="uuids[]"]').on('click', showhide_compare);
-showhide_compare();
-<% end %>
+<% if (o = Group.find?(@objects.first.owner_uuid)) %>
+ <% content_for :breadcrumbs do %>
+ <li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
+ <li>
+ <%= link_to(o.name, project_path(o.uuid)) %>
+ </li>
+ <li class="nav-separator">
+ <span class="glyphicon glyphicon-arrow-right"></span>
+ </li>
+ <li>
+ <%= link_to '#' do %>compare pipelines<% end %>
+ </li>
+ <% end %>
+<% end %>
<%= render partial: 'content', layout: 'content_layout', locals: {pane_list: controller.compare_pane_list } %>
--- /dev/null
+../application/_choose.js.erb
\ No newline at end of file
--- /dev/null
+<% @objects.each do |object| %>
+ <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+ <div class="col-sm-12" style="overflow-x:hidden">
+ <i class="fa fa-fw fa-gear"></i>
+ <%= object.name %>
+ </div>
+ <div class="col-sm-11 col-sm-push-1 arv-description-in-table">
+ <%= object.description %>
+ </div>
+ </div>
+<% end %>
<div class="modal-header">
<button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">×</button>
- <h4 class="modal-title"><%= params[:title] || 'Choose folder' %></h4>
+ <h4 class="modal-title"><%= params[:title] || 'Choose project' %></h4>
</div>
<div class="modal-body">
- <div class="container-fluid arv-folder-list selectable-container" style="height: 15em; overflow-y: scroll">
- <% [@my_folder_tree, @shared_folder_tree].each do |tree| %>
- <% tree.each do |foldernode| %>
- <% if foldernode[:object].is_a? String %>
- <div class="row" style="padding-left: <%= 1 + foldernode[:depth] %>em;">
+ <div class="selectable-container" style="height: 15em; overflow-y: scroll">
+ <% [@my_project_tree, @shared_project_tree].each do |tree| %>
+ <% tree.each do |projectnode| %>
+ <% if projectnode[:object].is_a? String %>
+ <div class="row" style="padding-left: <%= 1 + projectnode[:depth] %>em;">
<i class="fa fa-fw fa-folder-open-o"></i>
- <%= foldernode[:object] %>
+ <%= projectnode[:object] %>
</div>
<% else %>
- <div class="<%= 'selectable folder' if !params[:editable] || foldernode[:object].editable? %> row" style="padding-left: <%= 1 + foldernode[:depth] %>em;" data-object-uuid="<%= foldernode[:object].uuid %>">
+ <div class="<%= 'selectable project' if !params[:editable] || projectnode[:object].editable? %> row" style="padding-left: <%= 1 + projectnode[:depth] %>em;" data-object-uuid="<%= projectnode[:object].uuid %>">
<i class="fa fa-fw fa-folder-o"></i>
- <% if foldernode[:object].uuid == current_user.uuid %>
- My Folders
+ <% if projectnode[:object].uuid == current_user.uuid %>
+ My Projects
<% else %>
- <%= foldernode[:object].friendly_link_name || 'New folder' %>
+ <%= projectnode[:object].friendly_link_name || 'New project' %>
<% end %>
</div>
<% end %>
--- /dev/null
+../application/_choose.js.erb
\ No newline at end of file
--- /dev/null
+<div>
+ <% any = false %>
+ <% recent_jobs_and_pipelines[0..9].each do |object| %>
+ <% any = true %>
+ <div class="row">
+ <div class="col-sm-4">
+ <%= render :partial => "show_object_button", :locals => {object: object, size: 'xs'} %>
+ <% if object.respond_to?(:name) %>
+ <%= render_editable_attribute object, 'name', nil, {tiptitle: 'rename', btnplacement: :left} %>
+ <% else %>
+ <%= object.class_for_display %> <%= object.uuid %>
+ <% end %>
+ </div>
+ <div class="col-sm-8 arv-description-in-table">
+ <%= render_controller_partial(
+ 'show_object_description_cell.html',
+ controller_name: object.controller_name,
+ locals: {object: object})
+ %>
+ </div>
+ </div>
+ <% end %>
+ <% if not any %>
+ <span class="deemphasize">No jobs or pipelines to display.</span>
+ <% end %>
+</div>
--- /dev/null
+<div class="container-fluid arv-project-list">
+ <% tree.each do |projectnode| %>
+ <% rowtype = projectnode[:object].class %>
+ <% next if rowtype != Group and !show_root_node %>
+ <div class="<%= 'project' if rowtype == Group %> row">
+ <div class="col-md-12" style="padding-left: <%= projectnode[:depth] - (show_root_node ? 0 : 1) %>em;">
+ <% if show_root_node and rowtype == String %>
+ <i class="fa fa-fw fa-folder-open-o"></i>
+ <%= projectnode[:object] %>
+ <% elsif show_root_node and rowtype == User %>
+ <% if projectnode[:object].uuid == current_user.andand.uuid %>
+ <i class="fa fa-fw fa-folder-open-o"></i>
+ My Projects
+ <% else %>
+ <i class="fa fa-fw fa-folder-o"></i>
+ <%= projectnode[:object].friendly_link_name %>
+ <% end %>
+ <% elsif rowtype == Group %>
+ <i class="fa fa-fw fa-folder-o"></i>
+ <% opts = {} %>
+ <% opts[:title] = projectnode[:object].description %>
+ <% opts[:'data-toggle'] = 'tooltip' %>
+ <% opts[:'data-placement'] = 'bottom' %>
+ <%= link_to projectnode[:object], opts do %>
+ <%= projectnode[:object].friendly_link_name %>
+ <% end %>
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+</div>
--- /dev/null
+<% content_for :content_top do %>
+
+<h2>
+ <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => "New project" } %>
+</h2>
+
+<div class="arv-description-as-subtitle">
+ <%= render_editable_attribute @object, 'description', nil, { 'data-emptytext' => "(No description provided)", 'data-toggle' => 'manual' } %>
+</div>
+
+<% end %>
+
+<% content_for :tab_line_buttons do %>
+ <% if @objects_and_names.empty? %>
+ <%= button_to(project_path(id: @object.uuid, return_to: projects_path), method: 'delete', class: 'btn btn-sm btn-primary', data: {confirm: "Really delete project '#{@object.name}'?"}) do %>
+ <i class="fa fa-fw fa-trash-o"></i> Delete project
+ <% end %>
+ <% end %>
+<% end %>
+
+<div class="selection-action-container">
+ <div class="row">
+ <div class="col-sm-5">
+ <div class="btn-group btn-group-sm">
+ <button type="button" class="btn btn-default">Selection...</button>
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+ <span class="caret"></span>
+ <span class="sr-only">Toggle Dropdown</span>
+ </button>
+ <ul class="dropdown-menu" role="menu">
+ <li><%= link_to "Compare selected", '#',
+ 'data-href' => compare_pipeline_instances_path,
+ 'data-selection-param-name' => 'uuids[]',
+ 'data-selection-action' => 'compare'
+ %></li>
+ <li><%= link_to "Remove selected", '#',
+ 'data-href' => url_for(action: :remove_items),
+ 'data-selection-param-name' => 'item_uuids[]',
+ 'data-selection-action' => 'remove',
+ 'data-remote' => true,
+ 'method' => 'delete'
+ %></li>
+ </ul>
+ </div>
+ <% if @object.editable? %>
+ <%= link_to(
+ choose_collections_path(
+ title: 'Add data to project:',
+ multiple: true,
+ action_name: 'Add',
+ action_href: actions_path(id: @object.uuid),
+ action_method: 'post',
+ action_data: {selection_param: 'selection[]', copy_selections_into_project: @object.uuid, success: 'page-refresh'}.to_json),
+ { class: "btn btn-primary btn-sm", remote: true, method: 'get', data: {'event-after-select' => 'page-refresh'} }) do %>
+ <i class="fa fa-fw fa-plus"></i> Add data
+ <% end %>
+ <%= link_to(
+ choose_pipeline_templates_path(
+ title: 'Choose a pipeline to run:',
+ action_name: 'Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i>',
+ action_href: pipeline_instances_path,
+ action_method: 'post',
+ action_data: {'selection_param' => 'pipeline_instance[pipeline_template_uuid]', 'pipeline_instance[owner_uuid]' => @object.uuid, 'success' => 'redirect-to-created-object'}.to_json),
+ { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
+ <i class="fa fa-fw fa-gear"></i> Run a pipeline
+ <% end %>
+ <% end %>
+ </div>
+ <div class="col-sm-3">
+ <form class="form-inline" role="form">
+ Show:
+ <select class="form-control form-control-sm filterable-control" data-filterable-attribute="data-kind" data-filterable-target="table.arv-index.arv-project-contents tbody">
+ <option value="">Everything</option>
+ <option value="arvados#collection">Data</option>
+ <option value="arvados#pipelineInstance arvados#job">Compute jobs</option>
+ <option value="arvados#pipelineTemplate">Pipelines</option>
+ <!--
+ <option value="arvados#specimen">Specimens</option>
+ <option value="arvados#human">Humans</option>
+ <option value="arvados#trait">Traits</option>
+ -->
+ </select>
+ </form>
+ </div>
+ <div class="col-sm-4">
+ <input type="text" class="form-control filterable-control" placeholder="Search project contents" data-filterable-target="table.arv-index.arv-project-contents tbody"/>
+ </div>
+ </div>
+
+ <table class="table table-condensed table-fixedlayout arv-index arv-project-contents" style="overflow-x: hidden">
+ <colgroup>
+ <col width="40%" />
+ <col width="60%" />
+ </colgroup>
+ <tbody data-infinite-scroller="#Contents-scroll" data-infinite-content-href="<%= url_for(format: :json, partial: :contents_rows, offset: next_page_offset) if next_page_offset %>">
+ <%= render partial: 'show_contents_rows', locals: {project: @object, objects_and_names: @objects_and_names} %>
+ </tbody>
+ <thead>
+ <tr>
+ <th>
+ </th>
+ <th>
+ description
+ </th>
+ </tr>
+ </thead>
+ </table>
+
+</div>
--- /dev/null
+<% objects_and_names.each do |object, name_link| %>
+ <% name_object = (object.respond_to?(:name) || !name_link) ? object : name_link %>
+ <tr class="filterable"
+ data-object-uuid="<%= name_object.uuid %>"
+ data-kind="<%= object.kind %>"
+ >
+ <td>
+ <%= render partial: 'selection_checkbox', locals: {object: object, friendly_name: ((name_object.name rescue '') || '')} %>
+
+ <% if project.editable? %>
+ <%= link_to({action: 'remove_item', id: project.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "Remove #{object.class_for_display.downcase} #{name_object.name rescue object.uuid} from this project?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: 'remove') do %>
+ <i class="fa fa-fw fa-ban"></i>
+ <% end %>
+ <% else %>
+ <i class="fa fa-fw"></i><%# placeholder %>
+ <% end %>
+
+ <%= render :partial => "show_object_button", :locals => {object: object, size: 'sm', name_link: name_link} %>
+
+ <%= render_editable_attribute (name_link || object), 'name', nil, {btnplacement: :left, tiptitle: 'rename'} %>
+ </td>
+ <td class="arv-description-in-table">
+ <%= render_controller_partial(
+ 'show_object_description_cell.html',
+ controller_name: object.controller_name,
+ locals: {object: object})
+ %>
+ </td>
+ </tr>
+<% end %>
--- /dev/null
+<div class="row">
+ <% @objects[0..3].each do |object| %>
+ <div class="card arvados-object">
+ <div class="card-top blue">
+ <a href="#">
+ <img src="/favicon.ico" alt=""/>
+ </a>
+ </div>
+ <div class="card-info">
+ <span class="title"><%= @objects.name_for(object) || object.class_for_display %></span>
+ <div class="desc"><%= object.respond_to?(:description) ? object.description : object.uuid %></div>
+ </div>
+ <div class="card-bottom">
+ <%= render :partial => "show_object_button", :locals => {object: object, htmloptions: {class: 'btn-default btn-block'}} %>
+ </div>
+ </div>
+ <% end %>
+</div>
--- /dev/null
+<div>
+ <div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Additional permissions
+ </div>
+ <div class="panel-body">
+ <p>
+ <% if not @share_links.any? %>
+ <span class="deemphasize">(No additional permissions)</span>
+ <% else %>
+ Also shared with:
+ <% @share_links.andand.each do |link| %>
+ <br /><%= link_to_if_arvados_object link.tail_uuid, friendly_name: true %>
+ <% end %>
+ <% end %>
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <% if @object.owner %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Inherited permissions
+ </div>
+ <div class="panel-body">
+ <p>Permissions for this project are inherited by the owner or parent project:
+ </p>
+ <p style="margin-left: 4em">
+ <% if User == resource_class_for_uuid(@object.owner_uuid) %>
+ <i class="fa fa-fw fa-user"></i>
+ <% else %>
+ <i class="fa fa-fw fa-folder"></i>
+ <% end %>
+ <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
+ <%= button_to('Move to...',
+ choose_projects_path(
+ title: 'Move to...',
+ editable: true,
+ action_name: 'Move',
+ action_href: project_path(@object.uuid),
+ action_method: 'put',
+ action_data: {selection_param: 'project[owner_uuid]', success: 'page-refresh'}.to_json),
+ { class: "btn btn-default btn-xs arv-move-to-project", remote: true, method: 'get' }) %>
+ </p>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+</div>
--- /dev/null
+<% content_for :breadcrumbs do %>
+<li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
+<li><a href="#">Home</a></li>
+<% end %>
+
+<div>
+ <div class="row">
+ <div class="col-sm-6">
+ <% if my_projects.empty? %>
+ <div class="panel panel-info">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ Welcome to <b><%= Rails.configuration.site_name %></b>.
+ </h3>
+ </div>
+ <div class="panel-body">
+ <img src="/favicon.ico" class="pull-right" alt="" style="opacity: 0.3"/>
+ <p>
+ This site runs Arvados, the open source biomedical analysis platform. <a href="https://arvados.org" target="_blank">Learn more…</a>
+ </p>
+ <p>
+ <b>To get started,</b> create a project using the "Add new project" button below.
+ </p>
+ </div>
+ </div>
+ <% end %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="pull-right">
+ <%= button_to projects_path(method: 'post'), class: 'btn btn-xs btn-primary' do %>
+ <i class="fa fa-fw fa-plus"></i>
+ Add new project
+ <% end %>
+ </div>
+ <h3 class="panel-title">
+ My projects
+ </h3>
+ </div>
+ <div class="panel-body scroll-20em">
+ <%= render partial: 'index_projects', locals: {tree: @my_project_tree, show_root_node: false} %>
+ </div>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ Shared projects
+ </h3>
+ </div>
+ <div class="panel-body scroll-20em">
+ <%= render partial: 'index_projects', locals: {tree: @shared_project_tree, show_root_node: false} %>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-sm-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="pull-right">
+ <%= link_to jobs_path, class: 'btn btn-default btn-xs' do %>
+ All jobs <i class="fa fa-fw fa-arrow-circle-right"></i>
+ <% end %>
+ <%= link_to pipeline_instances_path, class: 'btn btn-default btn-xs' do %>
+ All pipelines <i class="fa fa-fw fa-arrow-circle-right"></i>
+ <% end %>
+ </div>
+ <h3 class="panel-title">
+ Recent jobs and pipelines
+ </h3>
+ </div>
+ <div class="panel-body">
+ <%= render partial: 'index_jobs_and_pipelines' %>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
}
<% end %>
-<div class="container-fluid" id="home-tables">
+<div id="home-tables">
<%= render :partial => 'tables' %>
--- /dev/null
+<% content_for :breadcrumbs do raw '<!-- -->' end %>
+
+<div class="row">
+ <div class="col-sm-8 col-sm-push-4" style="margin-top: 1em">
+ <div class="well clearfix">
+ <%= image_tag "dax.png", style: "width: 147px; height: 197px; max-width: 25%; margin-right: 2em", class: 'pull-left' %>
+
+ <h3>Hi! You're logged in, but...</h3>
+
+ <p>
+
+ Your account is inactive.
+
+ </p><p>
+
+ An administrator must activate your account before you can get
+ any further.
+
+ </p>
+ </div>
+ </div>
+</div>
<% content_for :breadcrumbs do raw '<!-- -->' end %>
-<%= image_tag "dax.png", style: "float: left; max-width: 25%; margin-right: 2em" %>
-<h1>Hi there! Please log in to use <%= Rails.configuration.site_name %>.</h1>
-<div class="row-fluid">
- <div class="col span8" style="margin-top: 1em">
+<div class="row">
+ <div class="col-sm-8 col-sm-push-4" style="margin-top: 1em">
<div class="well clearfix">
- <p>When you click on the button below you will be taken to a Google sign-in page.
- After entering your information, you will be redirected back to <%= Rails.configuration.site_name %>
- If you have never used <%= Rails.configuration.site_name %> before, logging in for the first
- time will also create a new user account. <%= Rails.configuration.site_name %> uses your name and
- email address from Google services only for identification, and can not access any personal information
- beyond that.
- </p>
+ <%= image_tag "dax.png", style: "width: 147px; height: 197px; max-width: 25%; margin-right: 2em", class: 'pull-left' %>
+
+ <h3>Please log in.</h3>
+
+ <p>
+
+ The "Log in" button below will show you a Google sign-in page.
+ After you assure Google that you want to log in here with your
+ Google account, you will be redirected back here to
+ <%= Rails.configuration.site_name %>.
+
+ </p><p>
+
+ If you have never used <%= Rails.configuration.site_name %>
+ before, logging in for the first time will automatically
+ create a new account.
+
+ </p><p>
+
+ <i><%= Rails.configuration.site_name %> uses your name and
+ email address only for identification, and does not retrieve
+ any other personal information from Google.</i>
+
+ </p>
<p>
- <a class="pull-right btn btn-primary" href="<%= arvados_api_client.arvados_login_url(return_to: request.url) %>">
- Click here to log in to <%= Rails.configuration.site_name %> with a Google account</a>
+ <%= link_to arvados_api_client.arvados_login_url(return_to: request.url), class: "pull-right btn btn-primary" do %>
+ Log in to <%= Rails.configuration.site_name %>
+ <i class="fa fa-fw fa-arrow-circle-right"></i>
+ <% end %>
</p>
</div>
</div>
</div>
-
-
resources :uploaded_datasets
resources :groups
resources :specimens
- resources :pipeline_templates
+ resources :pipeline_templates do
+ get 'choose', on: :collection
+ end
resources :pipeline_instances do
get 'compare', on: :collection
+ post 'copy', on: :member
end
resources :links
get '/collections/graph' => 'collections#graph'
get 'sharing_popup', :on => :member
post 'share', :on => :member
post 'unshare', :on => :member
+ get 'choose', on: :collection
end
get('/collections/download/:uuid/:reader_token/*file' => 'collections#show_file',
format: false)
get '/collections/download/:uuid/:reader_token' => 'collections#show_file_links'
get '/collections/:uuid/*file' => 'collections#show_file', :format => false
- resources :folders do
+ resources :projects do
match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
+ match 'remove_items', on: :member, via: :delete, action: :remove_items
get 'choose', on: :collection
end
post 'actions' => 'actions#post'
get 'websockets' => 'websocket#index'
- root :to => 'users#welcome'
+ root :to => 'projects#index'
# Send unroutable requests to an arbitrary controller
# (ends up at ApplicationController#render_not_found)
assert_equal([['.', 'foo', 3]], assigns(:object).files)
end
- test "viewing a collection fetches related folders" do
+ test "viewing a collection fetches related projects" do
show_collection(:foo_file, :active)
- assert_includes(assigns(:folders).map(&:uuid),
- api_fixture('groups')['afolder']['uuid'],
- "controller did not find linked folder")
+ assert_includes(assigns(:projects).map(&:uuid),
+ api_fixture('groups')['aproject']['uuid'],
+ "controller did not find linked project")
end
test "viewing a collection fetches related permissions" do
assert_not_equal(read_token, session[:arvados_api_token],
"using a reader token set the session's API token")
end
+
+ test "inactive user can retrieve user agreement" do
+ ua_collection = api_fixture('collections')['user_agreement']
+ get :show_file, {
+ uuid: ua_collection['uuid'],
+ file: ua_collection['manifest_text'].match(/ \d+:\d+:(\S+)/)[1]
+ }, session_for(:inactive)
+ assert_nil(assigns(:required_user_agreements),
+ "Did not skip check_user_agreements filter " +
+ "when showing the user agreement.")
+ assert_response :success
+ end
end
+++ /dev/null
-require 'test_helper'
-
-class FoldersControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
--- /dev/null
+require 'test_helper'
+
+class ProjectsControllerTest < ActionController::TestCase
+ test "inactive user is asked to sign user agreements on front page" do
+ get :index, {}, session_for(:inactive)
+ assert_response :success
+ assert_not_empty assigns(:required_user_agreements),
+ "Inactive user did not have required_user_agreements"
+ assert_template 'user_agreements/index',
+ "Inactive user was not presented with a user agreement at the front page"
+ end
+end
test "ignore previously valid token (for deleted user), don't crash" do
get :welcome, {}, session_for(:valid_token_deleted_user)
- assert_response :success
+ assert_response :redirect
+ assert_match /^#{Rails.configuration.arvados_login_base}/, @response.redirect_url
assert_nil assigns(:my_jobs)
assert_nil assigns(:my_ssh_keys)
end
page.assert_no_selector "div[data-persistent-state='#{oldstate}']"
end
- ['/collections', '/'].each do |path|
+ ['/collections', '/users/welcome'].each do |path|
test "Flip persistent switch at #{path}" do
Capybara.current_driver = Capybara.javascript_driver
uuid = api_fixture('collections')['foo_file']['uuid']
test "Collection page renders default name links" do
uuid = api_fixture('collections')['foo_file']['uuid']
- coll_name = api_fixture('links')['foo_collection_name_in_afolder']['name']
+ coll_name = api_fixture('links')['foo_collection_name_in_aproject']['name']
visit page_with_token('active', "/collections/#{uuid}")
assert(page.has_text?(coll_name), "Collection page did not include name")
# Now check that the page is otherwise normal, and the collection name
+++ /dev/null
-require 'integration_helper'
-require 'selenium-webdriver'
-require 'headless'
-
-class FoldersTest < ActionDispatch::IntegrationTest
- setup do
- Capybara.current_driver = Capybara.javascript_driver
- end
-
- test 'Find a folder and edit its description' do
- visit page_with_token 'active', '/'
- find('nav a', text: 'Folders').click
- find('.arv-folder-list a,button', text: 'A Folder').
- click
- within('.panel', text: api_fixture('groups')['afolder']['name']) do
- find('span', text: api_fixture('groups')['afolder']['name']).click
- find('.glyphicon-ok').click
- find('.btn', text: 'Edit description').click
- find('.editable-input textarea').set('I just edited this.')
- find('.editable-submit').click
- wait_for_ajax
- end
- visit current_path
- assert(find?('.panel', text: 'I just edited this.'),
- "Description update did not survive page refresh")
- end
-
- test 'Add a new name, then edit it, without creating a duplicate' do
- folder_uuid = api_fixture('groups')['afolder']['uuid']
- specimen_uuid = api_fixture('specimens')['owned_by_afolder_with_no_name_link']['uuid']
- visit page_with_token 'active', '/folders/' + folder_uuid
- within('.panel tr', text: specimen_uuid) do
- find(".editable[data-name='name']").click
- find('.editable-input input').set('Now I have a name.')
- find('.glyphicon-ok').click
- find('.editable', text: 'Now I have a name.').click
- find('.editable-input input').set('Now I have a new name.')
- find('.glyphicon-ok').click
- wait_for_ajax
- find('.editable', text: 'Now I have a new name.')
- end
- visit current_path
- within '.panel', text: 'Contents' do
- find '.editable', text: 'Now I have a new name.'
- page.assert_no_selector '.editable', text: 'Now I have a name.'
- end
- end
-
- test 'Create a folder and move it into a different folder' do
- visit page_with_token 'active', '/folders'
- find('input[value="Add a new folder"]').click
-
- within('.panel', text: 'New folder') do
- find('.panel-title span', text: 'New folder').click
- find('.editable-input input').set('Folder 1234')
- find('.glyphicon-ok').click
- end
- wait_for_ajax
-
- visit '/folders'
- find('input[value="Add a new folder"]').click
- within('.panel', text: 'New folder') do
- find('.panel-title span', text: 'New folder').click
- find('.editable-input input').set('Folder 5678')
- find('.glyphicon-ok').click
- end
- wait_for_ajax
-
- find('input[value="Move to..."]').click
- find('.selectable', text: 'Folder 1234').click
- find('a,button', text: 'Move').click
- wait_for_ajax
-
- # Wait for the page to refresh and show the new parent folder in
- # the Permissions panel:
- find('.panel', text: 'Folder 1234')
-
- assert(find('.panel', text: 'Permissions inherited from').
- all('*', text: 'Folder 1234').any?,
- "Folder 5678 should now be inside folder 1234")
- end
-
-end
-require 'test_helper'
+require 'integration_helper'
class LoginsTest < ActionDispatch::IntegrationTest
test "login with api_token works after redirect" do
end
test "expired token yields login page, not error page" do
- skip
visit page_with_token('expired_trustedclient')
# Even the error page has a "Log in" link. We should look for
# something that only appears the real login page.
- assert page.has_text? 'Please log in'
+ assert page.has_text? ' Log in Oh... fiddlesticks. Sorry, I had some trouble handling your request'
end
end
test 'Create and run a pipeline' do
visit page_with_token('active_trustedclient')
- click_link 'Pipeline templates'
+ visit '/pipeline_templates'
within('tr', text: 'Two Part Pipeline Template') do
find('a,button', text: 'Run').click
end
+ # This pipeline needs input. So, Run should be disabled
+ page.assert_selector 'a.disabled,button.disabled', text: 'Run'
+
instance_page = current_path
+ # put this pipeline instance in "A Project"
+ find('button', text: 'Choose a project...').click
+ within('.modal-dialog') do
+ find('.selectable', text: 'A Project').click
+ find('button', text: 'Move').click
+ end
+
# Go over to the collections page and select something
- click_link 'Collections (data files)'
+ visit '/collections'
within('tr', text: 'GNU_General_Public_License') do
find('input[type=checkbox]').click
end
find('#persistent-selection-count').click
- # Go back to the pipeline instance page to use the new selection
- visit instance_page
+ # Add this collection to the project
+ visit '/projects'
+ find('.arv-project-list a,button', text: 'A Project').click
+ find('.btn', text: 'Add data').click
+ find('span', text: 'foo_tag').click
+ within('.modal-dialog') do
+ find('.btn', text: 'Add').click
+ end
+
+ find('tr[data-kind="arvados#pipelineInstance"]', text: 'New pipeline instance').
+ find('a', text: 'Show').
+ click
- page.assert_selector 'a.disabled,button.disabled', text: 'Run'
assert find('p', text: 'Provide a value')
find('div.form-group', text: 'Foo/bar pair').
- find('a,input').
+ find('.btn', text: 'Choose').
click
- find('.editable-input select').click
- find('.editable-input').
- first(:option, 'b519d9cb706a29fc7ea24dbea2f05851+249025').click
+
+ within('.modal-dialog') do
+ find('span', text: 'foo_tag').click
+ find('button', text: 'OK').click
+ end
+
wait_for_ajax
# "Run" button is now enabled
page.assert_selector 'a,button', text: 'Stop'
find('a,button', text: 'Stop').click
- # Pipeline is stopped. We have the option to resume it.
- page.assert_selector 'a,button', text: 'Run'
+ # Pipeline is stopped. It should now be in paused state and Runnable again.
+ assert page.has_text? 'Paused'
+ page.assert_no_selector 'a.disabled,button.disabled', text: 'Resume'
+ page.assert_selector 'a,button', text: 'Clone and edit'
+
+ # Since it is test env, no jobs are created to run. So, graph not visible
+ assert_not page.has_text? 'Graph'
+ end
+
+ # Create a pipeline instance from within a project and run
+ test 'Create pipeline inside a project and run' do
+ visit page_with_token('active_trustedclient')
+
+ # Go over to the collections page and select something
+ visit '/collections'
+ within('tr', text: 'GNU_General_Public_License') do
+ find('input[type=checkbox]').click
+ end
+ find('#persistent-selection-count').click
+
+ # Add this collection to the project using collections menu from top nav
+ visit '/projects'
+ find('.arv-project-list a,button', text: 'A Project').click
+
+ find('#collections-menu').click
+ click_button 'Copy selections into this project'
+
+ # create a pipeline instance
+ find('.btn', text: 'Run a pipeline').click
+ within('.modal-dialog') do
+ assert page.has_text? 'Two Part Pipeline Template'
+ find('.fa-gear').click
+ find('.btn', text: 'Next: choose inputs').click
+ end
+
+ assert find('p', text: 'Provide a value')
+
+ find('div.form-group', text: 'Foo/bar pair').
+ find('.btn', text: 'Choose').
+ click
- # Go over to the graph tab
+ within('.modal-dialog') do
+ find('span', text: 'foo_tag').click
+ find('button', text: 'OK').click
+ end
+
+ wait_for_ajax
+
+ # "Run" button present and enabled
+ page.assert_no_selector 'a.disabled,button.disabled', text: 'Run'
+ first('a,button', text: 'Run').click
+
+ # Pipeline is running. We have a "Stop" button instead now.
+ page.assert_no_selector 'a,button', text: 'Run'
+ page.assert_selector 'a,button', text: 'Stop'
+
+ # Since it is test env, no jobs are created to run. So, graph not visible
+ assert_not page.has_text? 'Graph'
+ end
+
+ test 'view pipeline with job and see graph' do
+ visit page_with_token('active_trustedclient')
+
+ visit '/pipeline_instances'
+ assert page.has_text? 'pipeline_with_job'
+
+ find('a', text: 'pipeline_with_job').click
+
+ # since the pipeline component has a job, expect to see the graph
+ assert page.has_text? 'Graph'
click_link 'Graph'
- assert page.has_css? 'div#provenance_graph'
+ assert page.has_text? 'script_version'
end
+
end
--- /dev/null
+require 'integration_helper'
+require 'selenium-webdriver'
+require 'headless'
+
+class ProjectsTest < ActionDispatch::IntegrationTest
+ setup do
+ Capybara.current_driver = Capybara.javascript_driver
+ end
+
+ test 'Find a project and edit its description' do
+ visit page_with_token 'active', '/'
+ find('.arv-project-list a,button', text: 'A Project').
+ click
+ within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
+ find('span', text: api_fixture('groups')['aproject']['name']).click
+ within('.arv-description-as-subtitle') do
+ find('.fa-pencil').click
+ find('.editable-input textarea').set('I just edited this.')
+ find('.editable-submit').click
+ end
+ wait_for_ajax
+ end
+ visit current_path
+ assert(find?('.container-fluid', text: 'I just edited this.'),
+ "Description update did not survive page refresh")
+ end
+
+ test 'Add a new name, then edit it, without creating a duplicate' do
+ project_uuid = api_fixture('groups')['aproject']['uuid']
+ specimen_uuid = api_fixture('specimens')['owned_by_aproject_with_no_name_link']['uuid']
+ visit page_with_token 'active', '/projects/' + project_uuid
+ within(".selection-action-container") do
+ within (first('tr', text: 'Specimen')) do
+ find(".fa-pencil").click
+ find('.editable-input input').set('Now I have a name.')
+ find('.glyphicon-ok').click
+ find('.editable', text: 'Now I have a name.').click
+ find(".fa-pencil").click
+ find('.editable-input input').set('Now I have a new name.')
+ find('.glyphicon-ok').click
+ end
+ wait_for_ajax
+ find('.editable', text: 'Now I have a new name.')
+ end
+ visit current_path
+ within '.selection-action-container' do
+ find '.editable', text: 'Now I have a new name.'
+ page.assert_no_selector '.editable', text: 'Now I have a name.'
+ end
+ end
+
+ test 'Create a project and move it into a different project' do
+ visit page_with_token 'active', '/projects'
+ find('.btn', text: "Add new project").click
+
+ # within('.editable', text: 'New project') do
+ within('h2') do
+ find('.fa-pencil').click
+ find('.editable-input input').set('Project 1234')
+ find('.glyphicon-ok').click
+ end
+ wait_for_ajax
+
+ visit '/projects'
+ find('.btn', text: "Add new project").click
+ within('h2') do
+ find('.fa-pencil').click
+ find('.editable-input input').set('Project 5678')
+ find('.glyphicon-ok').click
+ end
+ wait_for_ajax
+
+ click_link 'Permissions'
+ find('input[value="Move to..."]').click
+ find('.selectable', text: 'Project 1234').click
+ find('a,button', text: 'Move').click
+ wait_for_ajax
+
+ # Wait for the page to refresh and show the new parent in Permissions panel
+ click_link 'Permissions'
+ find('.panel', text: 'Project 1234')
+
+ assert(find('.panel', text: 'Permissions for this project are inherited by the owner or parent project').
+ all('*', text: 'Project 1234').any?,
+ "Project 5678 should now be inside project 1234")
+ end
+
+end
end
def all_links_in(find_spec, text_regexp=//)
- find(find_spec).all('a').collect { |tag|
+ all(find_spec + ' a').collect { |tag|
if tag[:href].nil? or tag[:href].empty? or (tag.text !~ text_regexp)
nil
else
test "all first-level links succeed" do
visit page_with_token('active_trustedclient', '/')
assert_visit_success
- click_link 'user-menu'
+ click_link 'notifications-menu'
urls = [all_links_in('nav'),
all_links_in('.navbar', /^Manage /)].flatten
seen_urls = ['/']
visit page_with_token('admin_trustedclient')
# go to Users list page
+ find('#system-menu').click
click_link 'Users'
# check active user attributes in the list page
end
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
assert page.has_text? 'Attributes'
- assert page.has_text? 'Metadata'
+ assert page.has_text? 'Advanced'
assert page.has_text? 'Admin'
# go to the Attributes tab
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
assert page.has_text? 'zzzzz-tpzed-d9tiejq69daie8f'
# verify that the new user showed up in the users page and find
# the new user's UUID
- new_user_uuid =
- find('tr[data-object-uuid]', text: 'foo@example.com').
- find('td', text: '-tpzed-').
- text
+ new_user_uuid =
+ find('tr[data-object-uuid]', text: 'foo@example.com')['data-object-uuid']
assert new_user_uuid, "Expected new user uuid not found"
# go to the new user's page
find('tr', text: new_user_uuid).
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
assert page.has_text? 'modified_by_user_uuid'
assert_equal "false", text, "Expected new user's is_active to be false"
end
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: test_repo)'
- assert !(page.has_text? '(VirtualMachine:)')
+ assert page.has_text? 'Repository: test_repo'
+ assert !(page.has_text? 'VirtualMachine:')
headless.stop
end
Capybara.current_driver = :selenium
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
# click on active user
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
# Setup user
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: test_repo)'
- assert !(page.has_text? '(VirtualMachine:)')
+ assert page.has_text? 'Repository: test_repo'
+ assert !(page.has_text? 'VirtualMachine:')
# Click on Setup button again and this time also choose a VM
click_link 'Admin'
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: second_test_repo)'
- assert page.has_text? '(VirtualMachine: testvm.shell)'
+ assert page.has_text? 'Repository: second_test_repo'
+ assert page.has_text? 'VirtualMachine: testvm.shell'
headless.stop
end
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
# click on active user
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
# Verify that is_active is set
assert_equal "false", text, "Expected user's is_active to be false after unsetup"
end
+ click_link 'Advanced'
click_link 'Metadata'
- assert !(page.has_text? '(Repository: test_repo)')
- assert !(page.has_text? '(Repository: second_test_repo)')
- assert !(page.has_text? '(VirtualMachine: testvm.shell)')
+ assert !(page.has_text? 'Repository: test_repo')
+ assert !(page.has_text? 'Repository: second_test_repo')
+ assert !(page.has_text? 'VirtualMachine: testvm.shell')
# setup user again and verify links present
click_link 'Admin'
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: second_test_repo)'
- assert page.has_text? '(VirtualMachine: testvm.shell)'
+ assert page.has_text? 'Repository: second_test_repo'
+ assert page.has_text? 'VirtualMachine: testvm.shell'
headless.stop
end
test "make and name a new virtual machine" do
Capybara.current_driver = Capybara.javascript_driver
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Virtual machines'
assert page.has_text? 'testvm.shell'
click_on 'Add a new virtual machine'
find('tr', text: 'hostname').
- find('span', text: 'none').click
- assert page.has_text? 'Update hostname'
+ find('a[data-original-title=edit]').click
+ assert page.has_text? 'Edit hostname'
fill_in 'editable-text', with: 'testname'
click_button 'editable-submit'
assert page.has_text? 'testname'
test 'links_for on non-empty resource list' do
use_token :active
- results = Group.find(api_fixture('groups')['afolder']['uuid']).contents(include_linked: true)
+ results = Group.find(api_fixture('groups')['aproject']['uuid']).contents(include_linked: true)
assert_equal [], results.links_for(api_fixture('users')['active']['uuid'])
assert_equal [], results.links_for(api_fixture('jobs')['running_cancelled']['uuid'])
assert_equal [], results.links_for(api_fixture('jobs')['running']['uuid'], 'bogus-link-class')
end
test 'links_for returns all link classes (simulated results)' do
- folder_uuid = api_fixture('groups')['afolder']['uuid']
- specimen_uuid = api_fixture('specimens')['in_afolder']['uuid']
+ project_uuid = api_fixture('groups')['aproject']['uuid']
+ specimen_uuid = api_fixture('specimens')['in_aproject']['uuid']
api_response = {
kind: 'arvados#specimenList',
links: [{kind: 'arvados#link',
uuid: 'zzzzz-o0j2j-asdfasdfasdfas0',
- tail_uuid: folder_uuid,
+ tail_uuid: project_uuid,
head_uuid: specimen_uuid,
link_class: 'name',
name: 'Alice'},
{kind: 'arvados#link',
uuid: 'zzzzz-o0j2j-asdfasdfasdfas1',
- tail_uuid: folder_uuid,
+ tail_uuid: project_uuid,
head_uuid: specimen_uuid,
link_class: 'foo',
name: 'Bob'},
{kind: 'arvados#link',
uuid: 'zzzzz-o0j2j-asdfasdfasdfas2',
- tail_uuid: folder_uuid,
+ tail_uuid: project_uuid,
head_uuid: specimen_uuid,
link_class: nil,
name: 'Clydesdale'}],
test "get contents with names" do
use_token :active
oi = Group.
- find(api_fixture('groups')['asubfolder']['uuid']).
+ find(api_fixture('groups')['asubproject']['uuid']).
contents(include_linked: true)
assert_operator(0, :<, oi.count,
"Expected to find some items belonging to :active user")
"Expected to receive name links with contents response")
oi_uuids = oi.collect { |i| i['uuid'] }
- expect_uuid = api_fixture('specimens')['in_asubfolder']['uuid']
+ expect_uuid = api_fixture('specimens')['in_asubproject']['uuid']
assert_includes(oi_uuids, expect_uuid,
- "Expected '#{expect_uuid}' in asubfolder's contents")
+ "Expected '#{expect_uuid}' in asubproject's contents")
- expect_uuid = api_fixture('specimens')['in_afolder_linked_from_asubfolder']['uuid']
- expect_name = api_fixture('links')['specimen_is_in_two_folders']['name']
+ expect_uuid = api_fixture('specimens')['in_aproject_linked_from_asubproject']['uuid']
+ expect_name = api_fixture('links')['specimen_is_in_two_projects']['name']
assert_includes(oi_uuids, expect_uuid,
- "Expected '#{expect_uuid}' in asubfolder's contents")
+ "Expected '#{expect_uuid}' in asubproject's contents")
assert_equal(expect_name, oi.name_for(expect_uuid),
"Expected name_for '#{expect_uuid}' to be '#{expect_name}'")
end
+++ /dev/null
-require 'test_helper'
-
-class FoldersHelperTest < ActionView::TestCase
-end
--- /dev/null
+require 'test_helper'
+
+class ProjectsHelperTest < ActionView::TestCase
+end
--- /dev/null
+class RenameFolderToProject < ActiveRecord::Migration
+ def up
+ Group.update_all("group_class = 'project'", "group_class = 'folder'")
+ end
+
+ def down
+ Group.update_all("group_class = 'folder'", "group_class = 'project'")
+ end
+end
owner_uuid: zzzzz-tpzed-000000000000000
name: Administrators of a subset of users
-afolder:
+aproject:
uuid: zzzzz-j7d0g-v955i6s2oi1cbso
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
created_at: 2014-04-21 15:37:48 -0400
modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
modified_at: 2014-04-21 15:37:48 -0400
updated_at: 2014-04-21 15:37:48 -0400
- name: A Folder
- description: Test folder belonging to active user
- group_class: folder
+ name: A Project
+ description: Test project belonging to active user
+ group_class: project
-asubfolder:
+asubproject:
uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
created_at: 2014-04-21 15:37:48 -0400
modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
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"
+ name: A Subproject
+ description: "Test project belonging to active user's first test project"
group_class: folder
bad_group_has_ownership_cycle_a:
name: test
properties: {}
-specimen_is_in_two_folders:
+specimen_is_in_two_projects:
uuid: zzzzz-o0j2j-ryhm1bn83ni03sn
owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
created_at: 2014-04-21 15:37:48 -0400
tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
head_uuid: zzzzz-j58dm-5gid26432uujf79
link_class: name
- name: "I'm in a subfolder, too"
+ name: "I'm in a subproject, too"
properties: {}
-template_name_in_afolder:
+template_name_in_aproject:
uuid: zzzzz-o0j2j-4kpwf3d6rwkeqhl
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
created_at: 2014-04-29 16:47:26 -0400
tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
head_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
link_class: name
- name: "I'm a template in a folder"
+ name: "I'm a template in a project"
properties: {}
-job_name_in_afolder:
+job_name_in_aproject:
uuid: zzzzz-o0j2j-1kt6dppqcxbl1yt
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
created_at: 2014-04-29 16:47:26 -0400
tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
head_uuid: zzzzz-8i9sb-pshmckwoma9plh7
link_class: name
- name: "I'm a job in a folder"
+ name: "I'm a job in a project"
properties: {}
-foo_collection_name_in_afolder:
- uuid: zzzzz-o0j2j-foofoldername12
+foo_collection_name_in_aproject:
+ uuid: zzzzz-o0j2j-fooprojectname1
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
created_at: 2014-04-21 15:37:48 -0400
modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
link_class: name
# This should resemble the default name assigned when a
- # Collection is added to a Folder.
+ # Collection is added to a Project.
name: "1f4b0bc7583c2a7f9102c395f4ffc5e3+45 added sometime"
properties: {}
foo:
script: foo
script_version: master
+
+has_job:
+ name: pipeline_with_job
+ state: Ready
+ uuid: zzzzz-d1hrv-1yfj6xkidf2muk3
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ components:
+ foo:
+ script: foo
+ script_version: master
+ script_parameters: {}
+ job: {
+ uuid: zzzzz-8i9sb-pshmckwoma9plh7,
+ script_version: master
+ }
created_at: 2014-04-21 15:37:48 -0400
modified_at: 2014-04-21 15:37:48 -0400
-in_afolder:
+in_aproject:
uuid: zzzzz-j58dm-7r18rnd5nzhg5yk
owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
created_at: 2014-04-21 15:37:48 -0400
modified_at: 2014-04-21 15:37:48 -0400
-in_asubfolder:
+in_asubproject:
uuid: zzzzz-j58dm-c40lddwcqqr1ffs
owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
created_at: 2014-04-21 15:37:48 -0400
modified_at: 2014-04-21 15:37:48 -0400
-in_afolder_linked_from_asubfolder:
+in_aproject_linked_from_asubproject:
uuid: zzzzz-j58dm-5gid26432uujf79
owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
created_at: 2014-04-21 15:37:48 -0400
modified_at: 2014-04-21 15:37:48 -0400
-owned_by_afolder_with_no_name_link:
+owned_by_aproject_with_no_name_link:
uuid: zzzzz-j58dm-ypsjlol9dofwijz
owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
created_at: 2014-05-05 04:11:52 -0400
@controller = Arvados::V1::GroupsController.new
authorize_with :admin
get :index, {
- filters: [ ['group_class', 'not in', ['folder']] ],
+ filters: [ ['group_class', 'not in', ['project']] ],
controller: 'groups',
}
assert_response :success
found = assigns(:objects)
assert_includes(found.collect(&:group_class), nil,
- "'group_class not in ['folder']' filter should pass null")
+ "'group_class not in ['project']' filter should pass null")
end
end
assert_response 403
end
- test "get list of folders" do
+ test "get list of projects" do
authorize_with :active
- get :index, filters: [['group_class', '=', 'folder']], format: :json
+ get :index, filters: [['group_class', 'in', ['project', 'folder']]], format: :json
assert_response :success
group_uuids = []
json_response['items'].each do |group|
- assert_equal 'folder', group['group_class']
+ assert_includes ['folder', 'project'], group['group_class']
group_uuids << group['uuid']
end
- assert_includes group_uuids, groups(:afolder).uuid
- assert_includes group_uuids, groups(:asubfolder).uuid
+ assert_includes group_uuids, groups(:aproject).uuid
+ assert_includes group_uuids, groups(:asubproject).uuid
assert_not_includes group_uuids, groups(:system_group).uuid
assert_not_includes group_uuids, groups(:private).uuid
end
- test "get list of groups that are not folders" do
+ test "get list of groups that are not projects" do
authorize_with :active
get :index, filters: [['group_class', '=', nil]], format: :json
assert_response :success
assert_equal nil, group['group_class']
group_uuids << group['uuid']
end
- assert_not_includes group_uuids, groups(:afolder).uuid
- assert_not_includes group_uuids, groups(:asubfolder).uuid
+ assert_not_includes group_uuids, groups(:aproject).uuid
+ assert_not_includes group_uuids, groups(:asubproject).uuid
assert_includes group_uuids, groups(:private).uuid
end
test 'get group-owned objects' do
authorize_with :active
get :contents, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
format: :json,
include_linked: true,
}
test 'get group-owned objects with limit' do
authorize_with :active
get :contents, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
limit: 1,
format: :json,
}
test 'get group-owned objects with limit and offset' do
authorize_with :active
get :contents, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
limit: 1,
offset: 12345,
format: :json,
test 'get group-owned objects with additional filter matching nothing' do
authorize_with :active
get :contents, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
format: :json,
}
end
test 'get group-owned objects without include_linked' do
- unexpected_uuid = specimens(:in_afolder_linked_from_asubfolder).uuid
+ unexpected_uuid = specimens(:in_aproject_linked_from_asubproject).uuid
authorize_with :active
get :contents, {
- id: groups(:asubfolder).uuid,
+ id: groups(:asubproject).uuid,
format: :json,
}
assert_response :success
end
test 'get group-owned objects with include_linked' do
- expected_uuid = specimens(:in_afolder_linked_from_asubfolder).uuid
+ expected_uuid = specimens(:in_aproject_linked_from_asubproject).uuid
authorize_with :active
get :contents, {
- id: groups(:asubfolder).uuid,
+ id: groups(:asubproject).uuid,
include_linked: true,
format: :json,
}
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
+ expected_name = links(:specimen_is_in_two_projects).name
found_specimen_name = false
assert(json_response['links'].any?,
"Expected a non-empty array of links in response")
# times within a test.
@json_response = nil
get :contents, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
include_linked: inc_ind,
limit: limit,
offset: offset,
owner_received[item['owner_uuid']] = true
offset += 1
if not inc_ind
- assert_equal groups(:afolder).uuid, item['owner_uuid']
+ assert_equal groups(:aproject).uuid, item['owner_uuid']
end
end
break if offset >= items_available
test "Raise error on bogus #{arg} parameter #{val.inspect}" do
authorize_with :active
get :contents, {
- :id => groups(:afolder).uuid,
+ :id => groups(:aproject).uuid,
:format => :json,
arg => val,
}
test 'get writable_by list for owned group' do
authorize_with :active
get :show, {
- id: groups(:afolder).uuid,
+ id: groups(:aproject).uuid,
format: :json
}
assert_response :success
end
test "refuse duplicate name" do
- the_name = links(:job_name_in_afolder).name
- the_folder = links(:job_name_in_afolder).tail_uuid
+ the_name = links(:job_name_in_aproject).name
+ the_project = links(:job_name_in_aproject).tail_uuid
authorize_with :active
post :create, link: {
- tail_uuid: the_folder,
+ tail_uuid: the_project,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: the_name,
assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
# Use the group as the new owner of an existing object
- s = specimens(:in_afolder)
+ s = specimens(:in_aproject)
s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
assert s.valid?, "ownership should pass validation"
assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
end
test 'name links with the same tail_uuid must be unique' do
- a = Link.create!(tail_uuid: groups(:afolder).uuid,
+ a = Link.create!(tail_uuid: groups(:aproject).uuid,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: 'foo')
assert a.valid?, a.errors.to_s
assert_raises ActiveRecord::RecordNotUnique do
- b = Link.create!(tail_uuid: groups(:afolder).uuid,
+ b = Link.create!(tail_uuid: groups(:aproject).uuid,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: 'foo')
end
test 'name links with different tail_uuid need not be unique' do
- a = Link.create!(tail_uuid: groups(:afolder).uuid,
+ a = Link.create!(tail_uuid: groups(:aproject).uuid,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: 'foo')
assert a.valid?, a.errors.to_s
- b = Link.create!(tail_uuid: groups(:asubfolder).uuid,
+ b = Link.create!(tail_uuid: groups(:asubproject).uuid,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: 'foo')
[nil, '', false].each do |name|
test "name links cannot have name=#{name.inspect}" do
- a = Link.create(tail_uuid: groups(:afolder).uuid,
+ a = Link.create(tail_uuid: groups(:aproject).uuid,
head_uuid: specimens(:owned_by_active_user).uuid,
link_class: 'name',
name: name)
end
end
- ['users(:active)', 'groups(:afolder)'].each do |ofixt|
+ ['users(:active)', 'groups(:aproject)'].each do |ofixt|
test "delete #{ofixt} that owns other objects" do
o = eval ofixt
assert_equal(true, Specimen.where(owner_uuid: o.uuid).any?,