Project

General

Profile

Actions

Patch #44660

open

More view hooks and registries for plugins

Added by Jimmy Westberg 2 days ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
Hook requests
Target version:
-

Description

More view hooks and registries for plugins

Redmine's view hooks let a plugin add to a page without touching core. Where a hook exists, this works well. But the hooks are uneven: some pages have hooks in every part, others have none, and for tabs, action menus, journal details and list rows there is no hook at all, only helpers and views to override.

Where a hook is missing, a plugin has to replace a whole view or partial (and only one plugin can do that), prepend to a core helper (and several plugins then chain over the same helper), or find core's markup in the browser and move its element there with JavaScript. All of these break when core changes, and every new Redmine version means checking them again. In a set of 28 plugins we maintain, three core views are replaced, IssuesHelper#show_detail and ProjectsHelper#project_settings_tabs are each overridden by six plugins, and about 80 places in JavaScript depend on core's markup.

Proposal

Only additions: nothing existing changes, and a plugin that does not use them sees no difference. The attached patch (against trunk) covers seven items. Items 2 and 3 give the most for the smallest change, if you prefer to take them first.

  1. Hooks for the regions of every page.
    • In the layout: view_layouts_base_header_top and _header_bottom, view_layouts_base_sidebar_top (the sidebar shows when only this hook has content), view_layouts_base_content_top, and view_layouts_base_footer_top and _footer_bottom. These sit next to the existing view_layouts_base_sidebar and view_layouts_base_content.
    • In the title row: view_layouts_base_title_after, from the title helper.
    • In the contextual area of 16 more pages: view_<controller>_<action>_contextual, the pattern core already uses for versions, repositories and My page. It comes after core's own links and before the "..." actions menu.
    • Inside each of the 8 "..." actions menus, after core's items: view_<controller>_<action>_actions_menu (view_issues_index_actions_menu, view_issues_show_actions_menu, view_projects_show_actions_menu, view_timelog_index_actions_menu, view_users_index_actions_menu, view_versions_index_actions_menu, view_wiki_show_actions_menu and view_repositories_navigation_actions_menu). A menu with only hook content is shown too.
    • On the issue page: view_issues_show_top, view_issues_show_details_end and view_issues_show_bottom.
  2. A registry for tabs on project settings, administration settings, the user and group edit pages, and an issue's history (Redmine::Tabs). A plugin registers a tab instead of overriding the helper that builds the list. It gives the tab's partial, label, permission and module (project settings), an :if condition, and its place (:after/:before). Project settings keep core's filtering by permission and module; a tab without a permission needs :edit_project, as the project tab does.
  3. A registry for journal details (Redmine::JournalDetails). A plugin registers a renderer for its own property (optionally per prop_key). show_detail asks the registry first, so the plugin's line is rendered as HTML, as text and in mail; everything else is unchanged.
  4. Action menus as menus. issue_actions (the "..." menu of an issue), issue_context_menu, project_actions, user_actions and version_actions are Redmine::MenuManager menus that plugins push items to, as they already can for the top, application, project and admin menus. For these menus, the :if and :caption Procs and a Proc url receive the object (the issue, the selected issues, the project, the user or the version), and a Hash url gets the object as its :param.
  5. Form hooks at the top, the bottom and after each section:
    • issues: after the subject, after the description, after the attributes, and before and after the custom fields
    • projects, versions, groups and trackers: top and bottom
    • users: top, after the authentication, after the mail notifications, and bottom
  6. List hooks in the issue, project and user lists:
    • view_<list>_list_row_css_classes adds classes to a row; they are escaped.
    • view_<list>_list_row_after adds rows after a row, with the colspan given.
  7. Hooks as a contract. doc/HOOKS lists every hook with its context keys, the file it is called in and the version it appeared in. test/unit/lib/redmine/hook_list_test.rb fails when a hook is called but not listed, or listed but no longer called. test/integration/lib/redmine/view_hooks_test.rb checks that each new hook is called on its page, that the contextual hook comes before the actions menu and the menu hook inside it, and that hook output leaves each page's HTML as valid as it was (Nokogiri HTML5, same errors with and without the hooks). A hook is removed only after a deprecation notice. The HTML check found one place that broke: the user edit page put the avatar at the last space of the title's HTML, which with view_layouts_base_title_after is inside the hook's output; it now looks for the last space before </h2>.

Where the registries live

In core, like the registries core already has (menus, permissions, field formats, activity providers, search, wiki macros): a plugin declares in init.rb, the registry is filled when plugins load and lives in memory only.

Redmine::Plugin.register :a_plugin do
  project_settings_tab :a_tab, :label => :label_a_tab,
    :partial => 'a_plugin/settings', :permission => :manage_a_plugin, :after => :members
  issue_history_tab :a_tab, :remote => true, :onclick => "getRemoteTab('a_tab', ...)" 
  journal_detail :property => 'a_plugin', :renderer => APlugin::JournalDetail
  menu :issue_actions, :a_action, {:controller => 'a_plugin', :action => 'run'},
       :caption => :label_a_action, :icon => 'add', :if => Proc.new {|issue| issue.closed?}
end

A renderer responds to call(detail, view, no_html, options) and returns the line.

Tested with the existing test suite and the new tests against trunk at the commit named in the patch.

Green marks new hook placements:



Files

view_hooks.patch (81.2 KB) view_hooks.patch Jimmy Westberg, 2026-10-22 18:00
issue_show_menu.png (108 KB) issue_show_menu.png Jimmy Westberg, 2026-10-22 18:01
issues_index.png (103 KB) issues_index.png Jimmy Westberg, 2026-10-22 18:02
tracker_edit.png (92.3 KB) tracker_edit.png Jimmy Westberg, 2026-10-22 18:04

No data to display

Actions

Also available in: Atom PDF