From 5257d99306e915cc9bb5a65eca81f8517e6e222d Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Tue, 4 Nov 2014 19:25:08 -0500 Subject: [PATCH] 4084: Move all pane state into $pane, flatten panes with multiple entry points. --- .../app/assets/javascripts/event_log.js | 20 +- .../assets/javascripts/pipeline_instances.js | 34 ++- .../app/assets/javascripts/tab_panes.js | 222 +++++++++--------- .../assets/stylesheets/application.css.scss | 4 + .../pipeline_instances_controller.rb | 6 +- .../app/views/application/_content.html.erb | 22 +- .../views/jobs/_show_job_component.html.erb | 6 - .../app/views/jobs/_show_status.html.erb | 11 +- apps/workbench/app/views/jobs/show.html.erb | 11 +- .../_show_components.html.erb | 21 +- .../pipeline_instances/_show_log.html.erb | 9 +- 11 files changed, 189 insertions(+), 177 deletions(-) delete mode 100644 apps/workbench/app/views/jobs/_show_job_component.html.erb diff --git a/apps/workbench/app/assets/javascripts/event_log.js b/apps/workbench/app/assets/javascripts/event_log.js index 9e03dfff5e..8df099aebe 100644 --- a/apps/workbench/app/assets/javascripts/event_log.js +++ b/apps/workbench/app/assets/javascripts/event_log.js @@ -36,18 +36,20 @@ function onEventLogDispatcherOpen(event) { /* Trigger event for all applicable elements waiting for this event */ function onEventLogDispatcherMessage(event) { - parsedData = JSON.parse(event.data); - object_uuid = parsedData.object_uuid; - - if (object_uuid) { - // if there are any listeners for this object uuid or "all", trigger the event - matches = ".arv-log-event-listener[data-object-uuid=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuids~=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuid=\"all\"],.arv-log-event-listener[data-object-kind=\"" + parsedData.object_kind + "\"]"; - $(matches).trigger('arv-log-event', parsedData); - } + parsedData = JSON.parse(event.data); + object_uuid = parsedData.object_uuid; + + if (!object_uuid) { + return; + } + + // if there are any listeners for this object uuid or "all", trigger the event + matches = ".arv-log-event-listener[data-object-uuid=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuids~=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuid=\"all\"],.arv-log-event-listener[data-object-kind=\"" + parsedData.object_kind + "\"]"; + $(matches).trigger('arv-log-event', parsedData); } /* Automatically connect if there are any elements on the page that want to - received event log events. */ + receive event log events. */ $(document).on('ajax:complete ready', function() { var a = $('.arv-log-event-listener'); if (a.length > 0) { diff --git a/apps/workbench/app/assets/javascripts/pipeline_instances.js b/apps/workbench/app/assets/javascripts/pipeline_instances.js index 78fd195cdc..15134cbeed 100644 --- a/apps/workbench/app/assets/javascripts/pipeline_instances.js +++ b/apps/workbench/app/assets/javascripts/pipeline_instances.js @@ -47,7 +47,7 @@ $(document).on('ready ajax:complete', function() { run_pipeline_button_state(); }); -$(document).on('arv-log-event', '.arv-refresh-on-state-change', function(event, eventData){ +$(document).on('arv-log-event', '.arv-refresh-on-state-change', function(event, eventData) { if (eventData.event_type == "update" && eventData.properties.old_attributes.state != eventData.properties.new_attributes.state) { @@ -56,18 +56,30 @@ $(document).on('arv-log-event', '.arv-refresh-on-state-change', function(event, }); $(document).on('arv-log-event', '.arv-log-event-subscribe-to-pipeline-job-uuids', function(event, eventData){ - if (eventData.event_type == "create" || eventData.event_type == "update") { - if (eventData.object_kind == 'arvados#pipelineInstance') { - var objs = ""; - var components = eventData.properties.new_attributes.components; - for (a in components) { - if (components[a].job && components[a].job.uuid) { - objs += " " + components[a].job.uuid; - } - } - $(event.target).attr("data-object-uuids", eventData.object_uuid + objs); + if (!((eventData.object_kind == 'arvados#pipelineInstance') && + (eventData.event_type == "create" || + eventData.event_type == "update") && + eventData.properties && + eventData.properties.new_attributes && + eventData.properties.new_attributes.components)) { + return; + } + var objs = ""; + var components = eventData.properties.new_attributes.components; + for (a in components) { + if (components[a].job && components[a].job.uuid) { + objs += " " + components[a].job.uuid; } } + $(event.target).attr("data-object-uuids", eventData.object_uuid + objs); +}); + +$(document).on('ready ajax:success', function() { + $('.arv-log-refresh-control').each(function() { + var uuids = $(this).attr('data-object-uuids'); + var $pane = $(this).closest('[data-pane-content-url]'); + $pane.attr('data-object-uuids', uuids); + }); }); $(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(event, eventData){ diff --git a/apps/workbench/app/assets/javascripts/tab_panes.js b/apps/workbench/app/assets/javascripts/tab_panes.js index 4130488c93..3f1645b36d 100644 --- a/apps/workbench/app/assets/javascripts/tab_panes.js +++ b/apps/workbench/app/assets/javascripts/tab_panes.js @@ -2,26 +2,10 @@ // Fire when a tab is selected/clicked. $(document).on('shown.bs.tab', '[data-toggle="tab"]', function(event) { - // When we switch tabs, remove "active" from any refreshable panes within - // the previous tab content so they don't continue to refresh unnecessarily, and - // add "active" to any refreshable panes under the newly shown tab content. - - var tgt = $($(event.relatedTarget).attr('href')); - $(".pane-anchor", tgt).each(function (i, e) { - var a = $($(e).attr('href')); - a.removeClass("active"); - }); - - tgt = $($(event.target).attr('href')); - $(".pane-anchor", tgt).each(function (i, e) { - var a = $($(e).attr('href')); - a.addClass("active"); - }); - - if (!$(event.target).hasClass("pane-loaded")) { - // pane needs to be loaded - $(event.target).trigger('arv:pane:reload'); - } + // reload the pane (unless it's already loaded) + $($(event.target).attr('href')). + not('.pane-loaded'). + trigger('arv:pane:reload'); }); // Ask a refreshable pane to reload via ajax. @@ -51,128 +35,142 @@ $(document).on('shown.bs.tab', '[data-toggle="tab"]', function(event) { // 5. "pane-loaded pane-reload-pending" indicates a reload is scheduled (but has // not started yet), suppressing scheduling of any further reloads. // -$(document).on('arv:pane:reload', function(e) { - e.stopPropagation(); +$(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) { + // $pane, the event target, is an element whose content is to be + // replaced. Pseudoclasses on $pane (pane-loading, etc) encode the + // current loading state. + var $pane = $(e.target); + + var content_url = $pane.attr('data-pane-content-url'); + if (!content_url) { + // When reloadable elements are nested, we can receive + // arv:pane:reload events even though the selector in .on() + // does not match e.target. Ignore such events. + return; + } - // '$anchor' is the event target, which is a .pane-anchor or a bootstrap - // tab anchor. This is the element that stores the state of the pane. The - // actual element that will contain the content is pointed to in the 'href' - // attribute of etarget. - var $anchor = $(e.target); + e.stopPropagation(); - if ($anchor.hasClass('pane-loading')) { + if ($pane.hasClass('pane-loading')) { // Already loading, mark stale to schedule a reload after this one. - $anchor.addClass('pane-stale'); + $pane.addClass('pane-stale'); return; } - var throttle = $anchor.attr('data-load-throttle'); - if (!throttle) { - throttle = 15000; - } + // The default throttle (mininum milliseconds between refreshes) + // can be overridden by an .arv-log-refresh-control element inside + // the pane -- or, failing that, the pane element itself -- with a + // data-load-throttle attribute. This allows the server to adjust + // the throttle depending on the pane content. + var throttle = + $pane.find('.arv-log-refresh-control').attr('data-load-throttle') || + $pane.attr('data-load-throttle') || + 15000; var now = (new Date()).getTime(); - var loaded_at = $anchor.attr('data-loaded-at'); + var loaded_at = $pane.attr('data-loaded-at'); var since_last_load = now - loaded_at; if (loaded_at && (since_last_load < throttle)) { - if (!$anchor.hasClass('pane-reload-pending')) { - $anchor.addClass('pane-reload-pending'); + if (!$pane.hasClass('pane-reload-pending')) { + $pane.addClass('pane-reload-pending'); setTimeout((function() { - $anchor.trigger('arv:pane:reload'); + $pane.trigger('arv:pane:reload'); }), throttle - since_last_load); } return; } // We know this doesn't have 'pane-loading' because we tested for it above - $anchor.removeClass('pane-reload-pending'); - $anchor.removeClass('pane-loaded'); - $anchor.removeClass('pane-stale'); + $pane.removeClass('pane-reload-pending'); + $pane.removeClass('pane-loaded'); + $pane.removeClass('pane-stale'); - // $pane is the actual content area that is going to be updated. - var $pane = $($anchor.attr('href')); - if ($pane.hasClass('active')) { - $anchor.addClass('pane-loading'); + if (!$pane.hasClass('active')) { + // When the user selects e.target tab, show a spinner instead of + // old content while loading. + $pane.html('
'); + return; + } - var content_url = $anchor.attr('data-pane-content-url'); - $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}). - done(function(data, status, jqxhr) { - // Preserve collapsed state - var collapsable = {}; - $(".collapse", this).each(function(i, c) { - collapsable[c.id] = $(c).hasClass('in'); - }); - var tmp = $(data); - $(".collapse", tmp).each(function(i, c) { - if (collapsable[c.id]) { - $(c).addClass('in'); - } else { - $(c).removeClass('in'); - } - }); - this.html(tmp); - $anchor.removeClass('pane-loading'); - $anchor.addClass('pane-loaded'); - $anchor.attr('data-loaded-at', (new Date()).getTime()); - this.trigger('arv:pane:loaded'); + $pane.addClass('pane-loading'); - if ($anchor.hasClass('pane-stale')) { - $anchor.trigger('arv:pane:reload'); + $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}). + done(function(data, status, jqxhr) { + // Preserve collapsed state + var $pane = this; + var collapsable = {}; + $(".collapse", this).each(function(i, c) { + collapsable[c.id] = $(c).hasClass('in'); + }); + var tmp = $(data); + $(".collapse", tmp).each(function(i, c) { + if (collapsable[c.id]) { + $(c).addClass('in'); + } else { + $(c).removeClass('in'); } - }).fail(function(jqxhr, status, error) { - var errhtml; - if (jqxhr.getResponseHeader('Content-Type').match(/\btext\/html\b/)) { - var $response = $(jqxhr.responseText); - var $wrapper = $('div#page-wrapper', $response); - if ($wrapper.length) { - errhtml = $wrapper.html(); - } else { - errhtml = jqxhr.responseText; - } + }); + $pane.html(tmp); + $pane.removeClass('pane-loading'); + $pane.addClass('pane-loaded'); + $pane.attr('data-loaded-at', (new Date()).getTime()); + $pane.trigger('arv:pane:loaded'); + + if ($pane.hasClass('pane-stale')) { + $pane.trigger('arv:pane:reload'); + } + }).fail(function(jqxhr, status, error) { + var $pane = this; + var errhtml; + var contentType = jqxhr.getResponseHeader('Content-Type'); + if (contentType && contentType.match(/\btext\/html\b/)) { + var $response = $(jqxhr.responseText); + var $wrapper = $('div#page-wrapper', $response); + if ($wrapper.length) { + errhtml = $wrapper.html(); } else { - errhtml = ("An error occurred: " + - (jqxhr.responseText || status)). - replace(/&/g, '&'). - replace(//g, '>'); + errhtml = jqxhr.responseText; } - this.html('

' + - '' + - ' ' + - 'Reload tab

'); - $('.tab_reload', this).click(function() { - this.html('
'); - $anchor.trigger('arv:pane:reload'); - }); - // We want to render the error in an iframe, in order to - // avoid conflicts with the main page's element ids, etc. - // In order to do that dynamically, we have to set a - // timeout on the iframe window to load our HTML *after* - // the default source (e.g., about:blank) has loaded. - var iframe = $('iframe', this)[0]; - iframe.contentWindow.setTimeout(function() { - $('body', iframe.contentDocument).html(errhtml); - iframe.height = iframe.contentDocument.body.scrollHeight + "px"; - }, 1); - $anchor.removeClass('pane-loading'); - $anchor.addClass('pane-loaded'); + } else { + errhtml = ("An error occurred: " + + (jqxhr.responseText || status)). + replace(/&/g, '&'). + replace(//g, '>'); + } + $pane.html('

' + + '' + + ' ' + + 'Reload tab

'); + $('.tab_reload', $pane).click(function() { + $(this). + html('
'). + closest('.pane-loaded'). + attr('data-loaded-at', 0). + trigger('arv:pane:reload'); }); - } else { - // When the user selects e.target tab, show a spinner instead of - // old content while loading. - $pane.html('
'); - } + // We want to render the error in an iframe, in order to + // avoid conflicts with the main page's element ids, etc. + // In order to do that dynamically, we have to set a + // timeout on the iframe window to load our HTML *after* + // the default source (e.g., about:blank) has loaded. + var iframe = $('iframe', $pane)[0]; + iframe.contentWindow.setTimeout(function() { + $('body', iframe.contentDocument).html(errhtml); + iframe.height = iframe.contentDocument.body.scrollHeight + "px"; + }, 1); + $pane.removeClass('pane-loading'); + $pane.addClass('pane-loaded'); + }); }); // Mark all panes as stale/dirty. Refresh any 'active' panes. $(document).on('arv:pane:reload:all', function() { - $('.pane-anchor').trigger('arv:pane:reload'); + $('[data-pane-content-url]').trigger('arv:pane:reload'); }); -$(document).on('ready ajax:complete', function() { +$(document).on('arv-log-event', '.arv-refresh-on-log-event', function(e) { // Panes marked arv-refresh-on-log-event should be refreshed - $('.pane-anchor.arv-refresh-on-log-event').on('arv-log-event', function(e) { - $(e.target).trigger('arv:pane:reload'); - }); + $(e.target).trigger('arv:pane:reload'); }); // If there is a 'tab counts url' in the nav-tabs element then use it to get some javascript that will update them diff --git a/apps/workbench/app/assets/stylesheets/application.css.scss b/apps/workbench/app/assets/stylesheets/application.css.scss index fc7e462b88..f1de7d096d 100644 --- a/apps/workbench/app/assets/stylesheets/application.css.scss +++ b/apps/workbench/app/assets/stylesheets/application.css.scss @@ -267,3 +267,7 @@ span.editable-textile { .compute-summary-numbers td { font-size: 150%; } + +.arv-log-refresh-control { + display: none; +} diff --git a/apps/workbench/app/controllers/pipeline_instances_controller.rb b/apps/workbench/app/controllers/pipeline_instances_controller.rb index 46fc3d77b1..3d34c7b885 100644 --- a/apps/workbench/app/controllers/pipeline_instances_controller.rb +++ b/apps/workbench/app/controllers/pipeline_instances_controller.rb @@ -241,11 +241,9 @@ class PipelineInstancesController < ApplicationController end def show_pane_list - panes = %w(Components Graph Advanced) + panes = %w(Components Log Graph Advanced) if @object and @object.state.in? ['New', 'Ready'] - panes = %w(Inputs) + panes - else - panes.insert(1, {:name => "Log"}) + panes = %w(Inputs) + panes - %w(Log) end if not @object.components.values.any? { |x| x[:job] rescue false } panes -= ['Graph'] diff --git a/apps/workbench/app/views/application/_content.html.erb b/apps/workbench/app/views/application/_content.html.erb index d69e3582f5..7afc824ce9 100644 --- a/apps/workbench/app/views/application/_content.html.erb +++ b/apps/workbench/app/views/application/_content.html.erb @@ -7,12 +7,11 @@ <% pane_name = (pane.is_a?(Hash) ? pane[:name] : pane) %>
  • + > <%= pane_name.gsub('_', ' ') %>
  • @@ -23,21 +22,22 @@ <% pane_list.each_with_index do |pane, i| %> <% pane_name = (pane.is_a?(Hash) ? pane[:name] : pane) %>
    data-object-kind="arvados#<%= ArvadosApiClient.class_kind controller.model_class %>" <% else %> data-object-uuid="<%= @object.uuid %>" <% end %> + data-pane-content-url="<%= url_for(params.merge(tab_pane: pane_name)) %>" style="margin-top:0.5em;" - > -
    - <% if i == 0 %> - <%= render_pane pane_name, to_string: true %> - <% else %> -
    - <% end %> -
    + > +
    + <% if i == 0 %> + <%= render_pane pane_name, to_string: true %> + <% else %> +
    + <% end %> +
    <% end %> diff --git a/apps/workbench/app/views/jobs/_show_job_component.html.erb b/apps/workbench/app/views/jobs/_show_job_component.html.erb deleted file mode 100644 index 6ec6c7f7be..0000000000 --- a/apps/workbench/app/views/jobs/_show_job_component.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -<% pj = {} %> -<% pj[:job] = @object %> -<% pj[:name] = @object[:name] || "this job" %> -<% pj[:progress_bar] = render(partial: "job_progress", locals: {:j => @object }) %> -<% tasks = JobTask.filter([['job_uuid', '=', @object.uuid]]).results %> -<%= render partial: 'pipeline_instances/running_component', locals: {tasks: tasks, pj: pj, i: 0, expanded: true} %> diff --git a/apps/workbench/app/views/jobs/_show_status.html.erb b/apps/workbench/app/views/jobs/_show_status.html.erb index 976bf194f7..5bb37c3e6c 100644 --- a/apps/workbench/app/views/jobs/_show_status.html.erb +++ b/apps/workbench/app/views/jobs/_show_status.html.erb @@ -8,7 +8,16 @@ data-load-throttle="5000" >
    - <%= render 'show_job_component' %> + <%= + pj = {} + pj[:job] = @object + pj[:name] = @object[:name] || "this job" + pj[:progress_bar] = render(partial: "job_progress", + locals: {:j => @object }) + tasks = JobTask.filter([['job_uuid', '=', @object.uuid]]).results + render(partial: 'pipeline_instances/running_component', + locals: { tasks: tasks, pj: pj, i: 0, expanded: true}) + %>
    diff --git a/apps/workbench/app/views/jobs/show.html.erb b/apps/workbench/app/views/jobs/show.html.erb index 5f46505dc2..d83bc722a2 100644 --- a/apps/workbench/app/views/jobs/show.html.erb +++ b/apps/workbench/app/views/jobs/show.html.erb @@ -1,14 +1,9 @@ <% content_for :tab_line_buttons do %> -
    " data-object-uuid="<%= @object.uuid %>" - style="display: inline" - > -
    - <%= render partial: 'show_job_buttons', locals: {object: @object}%> -
    + style="display: inline"> + <%= render partial: 'show_job_buttons', locals: {object: @object}%>
    <% end %> diff --git a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb index f77379a33f..82f18e7c49 100644 --- a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb +++ b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb @@ -1,21 +1,16 @@ <% if !@object.state.in? ['New', 'Ready'] %> -<% job_uuids = @object.components.map { |k,j| j.is_a? Hash and j[:job].andand[:uuid] }.compact %> - -
    " - data-object-uuids="<%= @object.uuid %> <%= job_uuids.join(' ') %>" - data-load-throttle="5000" - > -
    + <% + job_uuids = @object.components.map { |k,j| j.is_a? Hash and j[:job].andand[:uuid] }.compact + throttle = @object.state == 'Running' ? 5000 : 15000 + %> +
    <%= render_pipeline_components("running", :json) %> -
    -
    - <% else %> <%# state is either New or Ready %>

    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.

    diff --git a/apps/workbench/app/views/pipeline_instances/_show_log.html.erb b/apps/workbench/app/views/pipeline_instances/_show_log.html.erb index 5bc7828648..2a71aefc49 100644 --- a/apps/workbench/app/views/pipeline_instances/_show_log.html.erb +++ b/apps/workbench/app/views/pipeline_instances/_show_log.html.erb @@ -1,4 +1,9 @@ <% log_uuids = [@object.uuid] + pipeline_jobs(@object).collect{|x|x[:job].andand[:uuid]}.compact %> <% log_history = stderr_log_history(log_uuids) %> -
    <%= log_history.join("\n") %> -
    +
    <%= log_history.join("\n") %>
    +
    + >
    -- 2.30.2