From 180b5feba092db1339f7bb3217e91f0b22239bc5 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Mon, 27 Jul 2026 02:53:21 +0200 Subject: Surface held locks in the dashboard's work-in-progress widget A lock with no draft or autosave behind it was invisible to everyone including its holder, until the next editor collided with it. work_in_progress now admits those nodes, and the widget names the holder rather than the last editor on any locked row. Also flips the ordering to stalest-first, so a backlog reads as a queue rather than a feed, and gives the heading a real total: the count came from the already-limited relation and could never exceed five. --- app/controllers/admin_controller.rb | 4 +++- app/controllers/nodes_controller.rb | 2 +- app/models/node.rb | 11 +++++++---- app/views/admin/index.html.erb | 10 ++++++++-- config/locales/de.yml | 7 ++++--- config/locales/en.yml | 7 ++++--- test/models/node_test.rb | 20 +++++++++++++++----- test/models/node_trash_test.rb | 2 +- 8 files changed, 43 insertions(+), 20 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index d9cf1be6..2b35fd20 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -5,7 +5,9 @@ class AdminController < ApplicationController before_action :login_required def index - @drafts = Node.drafts_and_autosaves(current_user_id: current_user.id).limit(5) + scope = Node.work_in_progress(current_user_id: current_user.id) + @drafts_total = scope.reorder(nil).count + @drafts = scope.includes(:head, :draft, :lock_owner).limit(5) @actions = NodeAction.order(:occurred_at => :desc, :id => :desc).limit(5) end diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb index 50b30881..2a2cde33 100644 --- a/app/controllers/nodes_controller.rb +++ b/app/controllers/nodes_controller.rb @@ -231,7 +231,7 @@ class NodesController < ApplicationController # Filter functions for admin views def drafts - @nodes = index_matching(Node.drafts_and_autosaves) + @nodes = index_matching(Node.work_in_progress) end def mine diff --git a/app/models/node.rb b/app/models/node.rb index 02502f6c..1e61f9fe 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -593,12 +593,15 @@ class Node < ApplicationRecord .distinct end - def self.drafts_and_autosaves(current_user_id: nil) - scope = where("draft_id IS NOT NULL OR autosave_id IS NOT NULL").not_in_trash - return scope.order("updated_at DESC") unless current_user_id + # Nodes carrying unfinished work: a draft, an autosave, or a lock with + # neither behind it + def self.work_in_progress(current_user_id: nil) + scope = where("draft_id IS NOT NULL OR autosave_id IS NOT NULL OR locking_user_id IS NOT NULL") + .not_in_trash + return scope.order("nodes.updated_at ASC") unless current_user_id scope.order( - Arel.sql(sanitize_sql_array(["CASE WHEN locking_user_id = ? THEN 0 ELSE 1 END, updated_at DESC", current_user_id])) + Arel.sql(sanitize_sql_array(["CASE WHEN locking_user_id = ? THEN 0 ELSE 1 END, nodes.updated_at ASC", current_user_id])) ) end diff --git a/app/views/admin/index.html.erb b/app/views/admin/index.html.erb index 25bdbdc6..984858e5 100644 --- a/app/views/admin/index.html.erb +++ b/app/views/admin/index.html.erb @@ -23,7 +23,7 @@
-

<%= t(".drafts_widget") %>

+

<%= t(".drafts_widget", :total => @drafts_total) %>

    <% @drafts.each do |node| %>
  • @@ -32,7 +32,13 @@ <%= link_to_path("#{node.unique_name} ↗", node.unique_name) %>
- <% if (editor = node_last_editor(node)) %><%= editor %>, <% end %><%= relative_time_phrase(node.updated_at) %> + <% if node.locked? %> + <%= icon("lock", library: "tabler", "aria-label": t(".held_by")) %> + <%= node.lock_owner.login %>, + <% elsif (editor = node_last_editor(node)) %> + <%= editor %>, + <% end %> + <%= relative_time_phrase(node.updated_at) %> <% end %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 6267fcff..30e2f1b5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -285,8 +285,9 @@ de: find_chapter: "Chapter finden" upload_asset: "Asset hochladen" add_in_tree: "Seite im Seitenbaum anlegen" - drafts_widget: "Entwürfe und Autosaves" - see_all_drafts: "Alle Entwürfe →" + drafts_widget: "In Arbeit (%{total})" + held_by: "Gesperrt von" + see_all_drafts: "Alle Nodes in Arbeit →" see_all_changes: "Alle Änderungen →" housekeeping: "Verwaltung" my_account: "Mein Konto" @@ -424,7 +425,7 @@ de: abstract_locale: "Abstract (%{lang})" body_locale: "Text (%{lang})" drafts: - title: "Nodes mit Entwürfen oder Autosaves" + title: "Nodes mit Entwürfen, Autosaves oder Sperren" mine: title: "Meine Arbeit" tags: diff --git a/config/locales/en.yml b/config/locales/en.yml index e5a4182f..f76dfecb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -238,8 +238,9 @@ en: find_chapter: "Find a chapter" upload_asset: "Upload asset" add_in_tree: "Add a page in the page tree" - drafts_widget: "Drafts and autosaves" - see_all_drafts: "See all drafts →" + drafts_widget: "In progress (%{total})" + held_by: "Locked by" + see_all_drafts: "See all WIP nodes →" see_all_changes: "See all recent changes →" housekeeping: "Housekeeping" my_account: "My account" @@ -376,7 +377,7 @@ en: abstract_locale: "Abstract (%{lang})" body_locale: "Body (%{lang})" drafts: - title: "Nodes with drafts or autosaves" + title: "Nodes with drafts, autosaves or locks" mine: title: "My work" tags: diff --git a/test/models/node_test.rb b/test/models/node_test.rb index 8bdb90ee..c5b0d6a2 100644 --- a/test/models/node_test.rb +++ b/test/models/node_test.rb @@ -522,17 +522,17 @@ class NodeTest < ActiveSupport::TestCase assert_equal 0, Node.editor_search("Backspace Nonexistentstreet").count end - test "drafts_and_autosaves without a user sorts by recency only" do + test "work_in_progress without a user sorts by oldest-first" do older = Node.root.children.create!(:slug => "drafts_order_older") find_or_create_draft(older, @user1) newer = Node.root.children.create!(:slug => "drafts_order_newer") find_or_create_draft(newer, @user1) - result = Node.drafts_and_autosaves.to_a - assert result.index(newer) < result.index(older) + result = Node.work_in_progress.to_a + assert result.index(older) < result.index(newer) end - test "drafts_and_autosaves with a user puts their own locked nodes first, regardless of recency" do + test "work_in_progress with a user puts their own locked nodes first, regardless of recency" do mine = Node.root.children.create!(:slug => "drafts_order_mine") mine.lock_for_editing!(@user1) mine.autosave!({:title => "mine"}, @user1) @@ -542,10 +542,20 @@ class NodeTest < ActiveSupport::TestCase someone_elses_newer.lock_for_editing!(other_user) someone_elses_newer.autosave!({:title => "theirs"}, other_user) - result = Node.drafts_and_autosaves(current_user_id: @user1.id).to_a + result = Node.work_in_progress(current_user_id: @user1.id).to_a assert result.index(mine) < result.index(someone_elses_newer) end + test "work_in_progress includes a node that is only locked" do + locked = create_node_with_published_page + locked.lock_for_editing!(@user1) + locked.reload + + assert_nil locked.draft + assert_nil locked.autosave + assert_includes Node.work_in_progress.to_a, locked + end + test "autosave! carries over the current related assets to the newly created autosave row" do node = Node.root.children.create!(:slug => "autosave_asset_carryover_test") user = User.find_by_login("quentin") diff --git a/test/models/node_trash_test.rb b/test/models/node_trash_test.rb index 52069d75..3947f20e 100644 --- a/test/models/node_trash_test.rb +++ b/test/models/node_trash_test.rb @@ -161,7 +161,7 @@ class NodeTrashTest < ActiveSupport::TestCase node = create_node_with_published_page node.trash!(@user1) - ids = Node.drafts_and_autosaves.pluck(:id) + ids = Node.work_in_progress.pluck(:id) assert_not_includes ids, Node.trash.id assert_not_includes ids, node.id end -- cgit v1.3