Redmine Plugin Development - Part2: Journal, Customization of Redmine Menu and Core

Posted by Sergey Enin 20 April 2011 at 23:36

Read previous part: "Redmine Plugin Development - Part1: Generation, Enumerations, Hooks"

 

In first part of the guide I reviewed Posibilities to add new functionality to Redmine, but what you if you would need/like to modify exists Redmine functionality.

As I wrote in previous part, I will not covert details described in at Redmine official plugin guide.

 

Menu

In redmine MenuManager is responsible to manage Menu, so you can direct access to it, for example for account_menu:

Redmine::MenuManager.map :account_menu do |menu|

   menu.delete(:my_account)

   menu.delete(:logout)

   menu.push 'Dashboard', { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }

   menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }

end

There are five menus that you can extend:

  • :top_menu - the top left menu
  • :account_menu - the top right menu with sign in/sign out links
  • :application_menu - the main menu displayed when the user is not inside a project
  • :project_menu - the main menu displayed when the user is inside a project
  • :admin_menu - the menu displayed on the Administration page (can only insert after Settings, before Plugins)

You can also add point to menu with standart way:

Redmine::Plugin.register :redmine_polls do

  [...]

  menu :application_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls'

end

There was 1 problem I faced with, when I wanted to implement my plugin menu view as project menu - there is not standart way to do it, so I implement it by myself.

1) You need to require proper files in init.rb of your plugin:

require 'redmine'
require 'menu_manager_patch' #this is your library

2) You need to define lib/menu_manager_patch.rb:

require 'action_view/helpers/capture_helper'

module Redmine
  module MenuManager
    module MenuHelper     
      
      include ActionView::Helpers::CaptureHelper #content_for Class
      # Renders the application main menu
      def render_main_menu_with_whistles(project)
        result = ""
        if ( @content_for_custom_menu )
          result = instance_variable_get(:@content_for_custom_menu)
        else
          result = render_main_menu_without_whistles(project)
          result = process_result_special_types(result, project)         
        end
        result
      end
      alias_method_chain :render_main_menu, :whistles

      def process_result_special_types(result, project)
        result_value = result
        return if ( (project.nil?) || (project.id.nil?) )       
        result_value
      end

      def display_main_menu_with_whistles?(project)       
        result = true
        if ( @content_for_custom_menu )
          result = true
        else
          result = display_main_menu_without_whistles?(project)
        end
        result
      end
      alias_method_chain :display_main_menu?, :whistles    
    end
  end
end

Redmine core customization

Views

You can re define existed view redmine views simply create file with same path in your plugin folder, for example - ../vendor/plugins/my_plugin/app/views/projects/index.rhtml will be processed instead of standart projects/index.rhtml.

Redmine views lifecycle:

  1. Rails bootstraps and loads all it's framework
  2. Rails starts to load code in the plugins
  3. Rails finds a views directory in ../vendor/plugins/my_plugin/app/views and pre-pends it to the views path
  4. Rails loads all the other plugins
  5. Rails then loads the application from ../app
  6. Rails finishes loading and serves up requests
  7. Request comes in, and a view needs to be rendered
  8. Rails looks for a matching template and loads the plugin's template since it was pre-pended to the views path
  9. Rails renders the plugins'view

Redmine controllers(models) lifecycle:

  1. Rails bootstraps and loads all it's framework
  2. Rails starts to load code in the plugins
  3. Rails finds IssueController in MyPlugin and see it defines a show action
  4. Rails loads all the other plugins
  5. Rails then loads the application from ../app
  6. Rails finds IssueController again and see it also defines a show action
  7. Rails (or rather Ruby) overwrites the show action from the plugin with the one from ../app
  8. Rails finishes loading and serves up requests

The right way to add/modify redmine controller is

  • wrap an existing method in your code with alias_method_chaing:

alias_method :foo_without_feature, :foo
alias_method :foo, :foo_with_feature

  • add new method defining to controller(model);


Adding new method to controller(mode)

Example from Budget plugin:

  • In init.rb of your plugin:

require 'issue_patch'
unless Issue.included_modules.include? IssuePatch
  Issue.send(:include, IssuePatch)
end

  • in lib/issue_path.rb

module IssuePatch
  def self.included(base) # :nodoc:
    base.send(:include, InstanceMethods)
    #base.extend(ClassMethods)
    base.class_eval do
      #the same as typing in Class itself
      validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on, :comments

      alias_method_chain :doing_something, :my_features
     end
  end



  module InstanceMethods
    # Wraps the association to get the Deliverable subject.  Needed for the
    # Query and filtering
    def deliverable_subject
      unless self.deliverable.nil?
        return self.deliverable.subject
      end
    end
    def  doing_something_with_my_feature
       doing_something_without_my_feature if (sky==”blue”)      
    end
      def update_journal(attrs)
        journal = Journal.new
        attrs.each do |attr|
          journal.details.build(:property=>"attr", :prop_key=>attr[:prop_key].to_s, :old_value=>attr[:old_value], :value=>attr[:new_value])
        end
        journal.journalized_id = id
        journal.journalized_type = "Issue"
        journal.user_id = User.current.id
        journal.notes = ""
        journal.save
      end
  end   
end

Using journal

In previous example was also code part to use redmine journal:

      def update_journal(attrs)
        journal = Journal.new
        attrs.each do |attr|
          journal.details.build(:property=>"attr", :prop_key=>attr[:prop_key].to_s, :old_value=>attr[:old_value], :value=>attr[:new_value])
        end
        journal.journalized_id = id
        journal.journalized_type = "Issue"
        journal.user_id = User.current.id
        journal.notes = ""
        journal.save
      end

 

Thats all I wanted to review. You can ask questions in comments.

Thank you for attention.

 

Links:

Posted in , ,  | Tags , , , , , ,

blog comments powered by Disqus