diff --git a/apps/dashboard/app/helpers/recently_used_apps_helper.rb b/apps/dashboard/app/helpers/recently_used_apps_helper.rb index 37b8664980..db467e4d7e 100644 --- a/apps/dashboard/app/helpers/recently_used_apps_helper.rb +++ b/apps/dashboard/app/helpers/recently_used_apps_helper.rb @@ -27,7 +27,8 @@ def load_recently_used_apps # These apps variables are initialized in the ApplicationController class for all requests (@sys_apps + @dev_apps + @usr_apps).select(&:batch_connect_app?).each do |ood_app| ood_app.sub_app_list.each do |batch_connect_app| - sys_apps_index[batch_connect_app.cache_file] = batch_connect_app if batch_connect_app.valid? + # Skip apps that are not valid (e.g., have ERB parsing errors) + sys_apps_index[batch_connect_app.cache_file] = batch_connect_app if valid_batch_connect_app?(batch_connect_app) end end @@ -35,11 +36,29 @@ def load_recently_used_apps next unless sys_apps_index.key?(file_path) matched_batch_connect_app = sys_apps_index.fetch(file_path) - session_context = matched_batch_connect_app.build_session_context - # Set cacheable to true to ensure update_session_with_cache sets the cached value - session_context.each { |attribute| attribute.opts[:cacheable] = true } - cache_file = BatchConnect::Session.cache_root.join(matched_batch_connect_app.cache_file) - matched_batch_connect_app.tap { |app| app.update_session_with_cache(session_context, cache_file) } + begin + session_context = matched_batch_connect_app.build_session_context + # Set cacheable to true to ensure update_session_with_cache sets the cached value + session_context.each { |attribute| attribute.opts[:cacheable] = true } + cache_file = BatchConnect::Session.cache_root.join(matched_batch_connect_app.cache_file) + matched_batch_connect_app.tap { |app| app.update_session_with_cache(session_context, cache_file) } + rescue StandardError => e + Rails.logger.info("Skipping recently used app #{matched_batch_connect_app.token} due to error: #{e.message}") + nil + end end.compact end + + # Check if a batch connect app is valid and safe to use + # Caches the validity check to avoid re-parsing form.yml.erb on every call + def valid_batch_connect_app?(app) + return false unless app.batch_connect_app? + + # Use the app's built-in valid? method which now properly memoizes + app.valid? + rescue StandardError => e + # If we can't determine validity (e.g., ERB error), treat as invalid + Rails.logger.info("Cannot determine validity of app #{app.token}: #{e.message}") + false + end end diff --git a/apps/dashboard/app/models/batch_connect/app.rb b/apps/dashboard/app/models/batch_connect/app.rb index b037469cfc..59334a4166 100644 --- a/apps/dashboard/app/models/batch_connect/app.rb +++ b/apps/dashboard/app/models/batch_connect/app.rb @@ -212,7 +212,9 @@ def preset? # Whether this is a valid app the user can use # @return [Boolean] whether valid app def valid? - if form_config.empty? + return @valid if defined?(@valid) + + @valid = if form_config.empty? false elsif !form_config.fetch(:form, []).is_a?(Array) @validation_reason = I18n.t('dashboard.batch_connect_invalid_form_array') @@ -432,7 +434,7 @@ def render_erb_file(path:, contents:, binding:) # Hash describing the full form object def form_config(binding: nil) - return @form_config if @form_config + return @form_config if defined?(@form_config) raise AppNotFound, "This app does not exist under the directory '#{root}'" unless root.directory? @@ -452,10 +454,10 @@ def form_config(binding: nil) @form_config = hsh rescue AppNotFound => e @validation_reason = e.message - {} + @form_config = {} rescue StandardError, Exception => e @validation_reason = "#{e.class.name}: #{e.message}" - {} + @form_config = {} end # Hash describing the full submission properties diff --git a/apps/dashboard/app/views/batch_connect/shared/_saved_settings_menu.html.erb b/apps/dashboard/app/views/batch_connect/shared/_saved_settings_menu.html.erb index 670d41d5cd..42428c27d0 100644 --- a/apps/dashboard/app/views/batch_connect/shared/_saved_settings_menu.html.erb +++ b/apps/dashboard/app/views/batch_connect/shared/_saved_settings_menu.html.erb @@ -8,7 +8,8 @@ <%= t('dashboard.bc_saved_settings.no_settings_title') %> <% end %> - <% all_settings.sort.each_with_index do | (app_token, app_saved_settings), index| + <% valid_settings = all_settings.select { |app_token, _| BatchConnect::App.from_token(app_token.to_s).valid? } %> + <% valid_settings.sort.each_with_index do | (app_token, app_saved_settings), index| app = BatchConnect::App.from_token(app_token.to_s) link = app.link %>