summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-18 04:33:53 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-18 04:33:53 +0200
commit49de2e14972030a7ef0ca36a94ab55a23a2f3596 (patch)
tree7b05419a805e9bac01a4acea3ba6d0348af50ee6
parent10bd6ba5cc640f0a85a3adb3641a65ce5edf831d (diff)
Annotate the History list with each revision's lifecycle
Each row in nodes#show's History section now carries terse badges from the action log: created, published, and restored (rollback re-promotion), with date and actor, rendered from entry metadata alone. Backfilled entries wear the inferred marker, so reconstructed provenance stays distinguishable from witnessed history. A revision that was published and later restored shows both badges chronologically -- its true biography. Only create and publish entries carry page_id; trash, restore, and destroy annotate the node's own log zoom instead of any single revision, by design.
-rw-r--r--app/controllers/nodes_controller.rb3
-rw-r--r--app/helpers/node_actions_helper.rb26
-rw-r--r--app/views/nodes/show.html.erb5
-rw-r--r--config/locales/de.yml3
-rw-r--r--config/locales/en.yml3
-rw-r--r--public/stylesheets/admin.css5
-rw-r--r--test/controllers/nodes_controller_test.rb12
-rw-r--r--test/models/helpers/node_actions_helper_test.rb19
8 files changed, 75 insertions, 1 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index 021f8ff..6caa827 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -62,6 +62,9 @@ class NodesController < ApplicationController
62 def show 62 def show
63 @page = @node.draft || @node.head 63 @page = @node.draft || @node.head
64 @translations = @page.translation_summary 64 @translations = @page.translation_summary
65 @page_actions = NodeAction.where(:page_id => @node.pages.select(:id))
66 .order(:occurred_at, :id)
67 .group_by(&:page_id)
65 end 68 end
66 69
67 def edit 70 def edit
diff --git a/app/helpers/node_actions_helper.rb b/app/helpers/node_actions_helper.rb
index c829dff..eb761e3 100644
--- a/app/helpers/node_actions_helper.rb
+++ b/app/helpers/node_actions_helper.rb
@@ -59,6 +59,32 @@ module NodeActionsHelper
59 end 59 end
60 end 60 end
61 61
62 def revision_lifecycle_badges actions
63 return "" if actions.blank?
64
65 badges = actions.map do |action|
66 key = case action.action
67 when "create" then "node_actions.revision_created"
68 when "publish" then action.metadata["via"] == "revision" ?
69 "node_actions.revision_restored" :
70 "node_actions.revision_published"
71 end
72 next unless key
73
74 badge = h(t(key, :date => action.occurred_at.strftime("%Y-%m-%d"),
75 :actor => action.actor_name))
76 if action.inferred_from
77 badge = safe_join([badge, content_tag(:span, t("node_actions.backfilled"),
78 :class => "node_action_inferred",
79 :title => action.inferred_from)], " ")
80 end
81 badge
82 end.compact
83
84 return "" if badges.empty?
85 safe_join(["— ", safe_join(badges, " · ")])
86 end
87
62 private 88 private
63 89
64 def revision_ref action, key 90 def revision_ref action, key
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index 4d4f6d5..af05778 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -229,7 +229,10 @@
229 </summary> 229 </summary>
230 <ul> 230 <ul>
231 <% @node.pages.order(:revision).each do |page| %> 231 <% @node.pages.order(:revision).each do |page| %>
232 <li><%= link_to "##{page.revision} — #{page.title} (#{page.editor.try(:login)}, #{page.updated_at})", node_revision_path(@node, page) %></li> 232 <li>
233 <%= link_to "##{page.revision} — #{page.title} (#{page.editor.try(:login)}, #{page.updated_at})", node_revision_path(@node, page) %>
234 <span class="revision_lifecycle"><%= revision_lifecycle_badges(@page_actions[page.id]) %></span>
235 </li>
233 <% end %> 236 <% end %>
234 </ul> 237 </ul>
235 </details> 238 </details>
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 289e5cc..f64f6cd 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -113,3 +113,6 @@ de:
113 trash: "%{actor} hat %{subject} in den Papierkorb verschoben (vorher unter %{from})" 113 trash: "%{actor} hat %{subject} in den Papierkorb verschoben (vorher unter %{from})"
114 restore_from_trash: "%{actor} hat %{subject} aus dem Papierkorb nach %{to} wiederhergestellt" 114 restore_from_trash: "%{actor} hat %{subject} aus dem Papierkorb nach %{to} wiederhergestellt"
115 destroy: "%{actor} hat %{subject} endgültig gelöscht (zuletzt unter %{path})" 115 destroy: "%{actor} hat %{subject} endgültig gelöscht (zuletzt unter %{path})"
116 revision_created: "angelegt am %{date} von %{actor}"
117 revision_published: "veröffentlicht am %{date} von %{actor}"
118 revision_restored: "wiederhergestellt am %{date} von %{actor}"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index b356cc1..8a8acc1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -65,3 +65,6 @@ en:
65 trash: "%{actor} moved %{subject} to the Trash (was at %{from})" 65 trash: "%{actor} moved %{subject} to the Trash (was at %{from})"
66 restore_from_trash: "%{actor} restored %{subject} from the Trash to %{to}" 66 restore_from_trash: "%{actor} restored %{subject} from the Trash to %{to}"
67 destroy: "%{actor} permanently deleted %{subject} (last at %{path})" 67 destroy: "%{actor} permanently deleted %{subject} (last at %{path})"
68 revision_created: "created %{date} by %{actor}"
69 revision_published: "published %{date} by %{actor}"
70 revision_restored: "restored %{date} by %{actor}"
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 88fd84b..15e753d 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -1544,6 +1544,11 @@ div#image_browser ul li {
1544 color: #777; 1544 color: #777;
1545} 1545}
1546 1546
1547.revision_lifecycle {
1548 font-size: 0.85rem;
1549 color: #777;
1550}
1551
1547/* ============================================================ 1552/* ============================================================
1548 Trash section 1553 Trash section
1549 ============================================================ */ 1554 ============================================================ */
diff --git a/test/controllers/nodes_controller_test.rb b/test/controllers/nodes_controller_test.rb
index 6e2ddb3..ddc4565 100644
--- a/test/controllers/nodes_controller_test.rb
+++ b/test/controllers/nodes_controller_test.rb
@@ -745,4 +745,16 @@ class NodesControllerTest < ActionController::TestCase
745 assert_select "td", /quentin/ 745 assert_select "td", /quentin/
746 assert_select "form[action=?]", node_path(node), count: 1 746 assert_select "form[action=?]", node_path(node), count: 1
747 end 747 end
748
749test "show annotates history rows with their lifecycle" do
750 login_as :quentin
751 node = Node.root.children.create!(:slug => "history_annotation_test")
752 Globalize.with_locale(I18n.default_locale) { node.draft.update!(:title => "Annotated") }
753 node.publish_draft!(users(:quentin))
754
755 get :show, params: { :id => node.id }
756
757 assert_response :success
758 assert_select "span.revision_lifecycle", /quentin/
759 end
748end 760end
diff --git a/test/models/helpers/node_actions_helper_test.rb b/test/models/helpers/node_actions_helper_test.rb
index 16c833b..ef6035d 100644
--- a/test/models/helpers/node_actions_helper_test.rb
+++ b/test/models/helpers/node_actions_helper_test.rb
@@ -114,4 +114,23 @@ class NodeActionsHelperTest < ActionView::TestCase
114 assert_includes action_summary(restore), "club/new" 114 assert_includes action_summary(restore), "club/new"
115 assert_includes action_summary(doomed), "trash/old" 115 assert_includes action_summary(doomed), "trash/old"
116 end 116 end
117
118 test "revision lifecycle badges cover create, publish, rollback, and inference" do
119 created = entry("create", { "title" => "x", "path" => "a/b" })
120 published = entry("publish", { "via" => "draft", "title" => { "from" => nil, "to" => "x" } })
121 restored = entry("publish", { "via" => "revision", "title" => { "from" => "y", "to" => "x" } })
122 restored.update!(:inferred_from => "from_page_revision")
123
124 out = revision_lifecycle_badges([created, published, restored])
125
126 assert_includes out, "created"
127 assert_includes out, "published"
128 assert_includes out, "restored"
129 assert_includes out, "quentin"
130 assert_includes out, "node_action_inferred"
131 assert_includes out, "<span"
132 assert_not_includes out, "&lt;span"
133
134 assert_equal "", revision_lifecycle_badges(nil)
135 end
117end 136end