<% if @allowed_to[:submit_workflow] || @allowed_to[:export_data] %>
diff --git a/app/views/projects/samples/index.html.erb b/app/views/projects/samples/index.html.erb
index 5aa930ff74..37667e54f4 100644
--- a/app/views/projects/samples/index.html.erb
+++ b/app/views/projects/samples/index.html.erb
@@ -5,9 +5,8 @@
<%= turbo_frame_tag "selected" %>
<%= turbo_frame_tag "file_selector_dialog" %>
-<%= turbo_stream_from @project %>
-
+
<%= render Viral::PageHeaderComponent.new(title: t('.title')) do |component| %>
<% component.with_buttons do %>
<% if @allowed_to[:submit_workflow] && @has_samples && @pipelines_enabled %>
@@ -164,6 +163,8 @@
<% end %>
<% end %>
+ <%= render RefreshNoticeComponent.new(streamable: @project, stream_name: :samples, message: t(".refresh_notice.message")) %>
+
<% if @has_samples %>
<% if @allowed_to[:submit_workflow] || @allowed_to[:clone_sample] || @allowed_to[:transfer_sample] || @allowed_to[:export_data] %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 11505b31a6..053b13ef0a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -320,6 +320,10 @@ en:
expiration: Expiration
username: Username
never: Never
+ components:
+ refresh_notice:
+ default_link_text: Refresh
+ default_message: New data available
concerns:
attachment_actions:
destroy:
@@ -886,6 +890,8 @@ en:
index:
deselect_all_button: Deselect All
deselect_all_tooltip: Deselect all samples in this group
+ refresh_notice:
+ message: Samples table is out of date. Please refresh to view latest changes.
select_all_button: Select All
select_all_tooltip: Select all samples in this group
subtitle: These are the samples in %{namespace_type} %{namespace_name}
@@ -1562,6 +1568,8 @@ en:
deselect_all_tooltip: Deselect all samples in this project
no_associated_samples: There are no samples associated with this project.
no_samples: No Samples
+ refresh_notice:
+ message: Samples table is out of date. Please refresh to view latest changes.
select_all_button: Select All
select_all_tooltip: Select all samples in this project
title: Samples
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index c88a355ec7..a894e9d28a 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -320,6 +320,10 @@ fr:
expiration: Expiration
username: Nom d’utilisateur
never: Jamais
+ components:
+ refresh_notice:
+ default_link_text: Actualiser
+ default_message: Nouvelles données disponibles
concerns:
attachment_actions:
destroy:
@@ -888,6 +892,8 @@ fr:
index:
deselect_all_button: Désélectionner tout
deselect_all_tooltip: Désélectionner tout
+ refresh_notice:
+ message: Le tableau des échantillons est obsolète. Veuillez actualiser pour voir les dernières modifications.
select_all_button: Tout sélectionner
select_all_tooltip: Tout sélectionner
subtitle: Voici les échantillons dans %{namespace_type} %{namespace_name}
@@ -1566,6 +1572,8 @@ fr:
deselect_all_tooltip: Désélectionner tous les échantillons de ce projet
no_associated_samples: Il n’y a aucun échantillon associé à ce projet.
no_samples: Aucun échantillon
+ refresh_notice:
+ message: Le tableau des échantillons est obsolète. Veuillez actualiser pour voir les dernières modifications.
select_all_button: Tout sélectionner
select_all_tooltip: Sélectionner tous les échantillons de ce projet
title: Échantillons
diff --git a/test/components/refresh_notice_component_test.rb b/test/components/refresh_notice_component_test.rb
new file mode 100644
index 0000000000..c44884a20b
--- /dev/null
+++ b/test/components/refresh_notice_component_test.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'view_component_test_case'
+
+class RefreshNoticeComponentTest < ViewComponentTestCase
+ def setup
+ @project = namespaces_project_namespaces(:project1)
+ end
+
+ # 🔄 BASIC RENDERING - Ensure component renders with required elements
+
+ test 'renders with refresh controller and turbo stream' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ assert_selector '[data-controller="refresh"]', count: 1
+ assert_selector 'turbo-cable-stream-source', count: 1
+ assert_selector '[data-refresh-target="source"]', count: 1
+ end
+
+ test 'renders with viral alert component' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ assert_selector '[data-refresh-target="notice"]', count: 1
+ assert_selector '.hidden', count: 1
+ assert_selector '[aria-live="assertive"]', count: 1
+ end
+
+ test 'uses default message when not provided' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ assert_text I18n.t('components.refresh_notice.default_message')
+ end
+
+ test 'uses default link text when not provided' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ assert_text I18n.t('components.refresh_notice.default_link_text')
+ end
+
+ # 🎯 CUSTOM CONTENT - Test custom messages and links
+
+ test 'renders with custom message' do
+ custom_message = 'New data available'
+ render_inline(RefreshNoticeComponent.new(
+ streamable: @project,
+ stream_name: :samples,
+ message: custom_message
+ ))
+
+ assert_text custom_message
+ end
+
+ test 'renders with custom link text' do
+ custom_link_text = 'Load new data'
+ render_inline(RefreshNoticeComponent.new(
+ streamable: @project,
+ stream_name: :samples,
+ link_text: custom_link_text
+ ))
+
+ assert_text custom_link_text
+ end
+
+ test 'renders with both custom message and link text' do
+ custom_message = 'Updates are ready'
+ custom_link_text = 'Reload page'
+ render_inline(RefreshNoticeComponent.new(
+ streamable: @project,
+ stream_name: :samples,
+ message: custom_message,
+ link_text: custom_link_text
+ ))
+
+ assert_text custom_message
+ assert_text custom_link_text
+ end
+
+ # 🔌 TURBO STREAM CONFIGURATION - Test stream setup
+
+ test 'configures turbo stream with correct streamable and stream name' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ turbo_source = page.find('turbo-cable-stream-source')
+ # The channel attribute should include the project and stream name
+ assert turbo_source['channel'].present?
+ end
+
+ test 'works with different stream names' do
+ %i[samples members attachments].each do |stream_name|
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: stream_name))
+
+ assert_selector '[data-controller="refresh"]', count: 1
+ assert_selector 'turbo-cable-stream-source', count: 1
+ end
+ end
+
+ # 🎨 SYSTEM ARGUMENTS - Test custom styling and attributes
+
+ test 'accepts and applies additional system arguments' do
+ render_inline(RefreshNoticeComponent.new(
+ streamable: @project,
+ stream_name: :samples,
+ class: 'custom-class',
+ id: 'custom-id'
+ ))
+
+ assert_selector '#custom-id', count: 1
+ assert_selector '.custom-class', count: 1
+ assert_selector '[data-controller="refresh"]', count: 1
+ end
+
+ # 🔧 ACCESSIBILITY - Test accessibility attributes
+
+ test 'alert has proper accessibility attributes' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ assert_selector '[aria-live="assertive"]', count: 1
+ end
+
+ test 'alert is initially hidden' do
+ render_inline(RefreshNoticeComponent.new(streamable: @project, stream_name: :samples))
+
+ # The alert should have the hidden class initially
+ assert_selector '.hidden', count: 1
+ end
+
+ # 🧪 INTEGRATION - Test component structure
+
+ test 'complete component structure is correct' do
+ render_inline(RefreshNoticeComponent.new(
+ streamable: @project,
+ stream_name: :samples,
+ message: 'Test message',
+ link_text: 'Test link'
+ ))
+
+ # Wrapper with refresh controller
+ assert_selector '[data-controller="refresh"]', count: 1
+
+ # Turbo stream source
+ assert_selector 'turbo-cable-stream-source[data-refresh-target="source"]', count: 1
+
+ # Alert notice
+ assert_selector '[data-refresh-target="notice"]', count: 1
+ assert_selector '[aria-live="assertive"]', count: 1
+
+ # Content
+ assert_text 'Test message'
+ assert_text 'Test link'
+ end
+end