Category Archives: Blog

Article de blog

31Juil/17

Reprise du tutoriel officiel de création d’un plugin RedMine

Cet article a pour objectif de reprendre le tutoriel officiel de création de plugin RedMine, tout en y apportant les corrections et modifications nécessaires pour le faire fonctionner correctement et avec la version 3.3 de RedMine. De plus, il y liste une partie des erreurs et problèmes rencontrés au cours de sa réalisation et les solutions apportées.

NOTE : Mon conseil pour la réalisation d’un plugin RedMine est d’utiliser l’article [CREATION_PLUGIN_REDMINE] qui contient de plus amples explications, ainsi que plus d’exemples pour des cas différents (surcharges de méthodes déjà existantes dans le cœur de l’application par exemple).

1 . Déroulement du développement

Source(s) utilisée(s) :

Partant du principe que RedMine est installé sur la machine et correctement configuré.

1.1  Création des fichiers du plugin

Un script est disponible pour créer automatiquement les répertoires / fichiers nécessaire à la création d’un plugin (y compris des répertoires optionnels). Se placer dans le répertoire RedMine :

$ export RAILS_ENV="production"
$ RAILS_ENV=production
$ bundle exec ruby bin/rails generate redmine_plugin <nom_plugin>

Modifier le fichier plugins/<nom_plugin>/init.rb  pour y modifier les informations qui conviennent.

Redémarrer l’application RedMine, dans l’onglet ‘Administration/plugins’ le plugin apparaît (mais ne sert actuellement à rien).

Problème(s) rencontré(s) : Aucun

Solution(s) apportée(s) : Aucune

Source(s) solution(s) : Aucune

1.2   ‘Model’, ‘View’, ‘Controller’ et premières actions

L’objectif du plugin développé dans ce tutoriel est de créer un système de sondage pouvant être répondus par ‘oui’ ou ‘non’.

La suite de commande qui permet de faire cela n’est pas très explicite à première vue, elle utilise des scripts pré construits pour créer une table contenant des objets ‘poll’ (sondage) dans la base de donnée du RedMine. Ceux-ci possèdent une question ainsi qu’un nombre de réponses ‘oui’ et ‘non’.

De plus, cela créé également un ‘model’, c’est une classe où sont répertoriée les méthodes qui interagissent directement avec notre objet.

$ bundle exec ruby bin/rails generate redmine_plugin_model mon_plugin poll question:string yes:integer no:integer

Syntaxe:

bundle exec ruby bin/rails generate redmine_plugin_model <nom_plugin> <nom_model> [<nom_attribut>[:type]] [<nom_attribut2>[:type2]] [...]

<nom_model> correspond au nom de la “classe” générée, ici par exemple “poll” (sondage)

<nom_attribut> est le nom de la variable, ex : question,oui,non,…

type est la type de la variable, ex : string,integer,boolean,…

Mettre à jour la base de données :

$ bundle exec rake redmine:plugins:migrate
$ bundle exec ruby bin/rails console

NOTE : Erreur possible lors de l’appel à la console, voir les solutions ci-dessous

Création de deux sondages (depuis la console) :

Poll.create(:question => "Suis-je une première question")

Poll.create(:question => "Et maintenant")

exit

Créer la fonction de vote pour le sondage (qui incrément le score de ‘oui’ ou ‘non’ de chaque sondages).

Dans le fichier plugins/<nom_plugin>/app/models/poll.rb  écrire :

class Poll < ActiveRecord::Base

def vote(answer)

increment(answer == ‘yes’ ? :yes : :no)

end

end

Cependant pour interagir avec notre model il nous faut un contrôleur qui fasse la liaison entre les actions de l’utilisateur et les répercussions sur les objets. Génération du contrôleur qui permet d’interagir avec les sondages :

$ bundle exec ruby bin/rails generate redmine_plugin_controller mon_plugin polls index vote

Syntaxe :

bundle exec ruby bin/rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>] […]

La fonction Index sert à lister les sondages.

LA fonction Vote sert à voter sur un sondage et incrémenter le nombre de réponses correspondant.

Dans le fichier plugins/mon_plugin/app/controllers/polls_controller.rb  écrire :

class PollsController < ApplicationController

unloadable

def index

@polls = Poll.all

end

def vote

poll = Poll.find(params[:id])

poll.vote(params[:answer])

if poll.save

flash[:notice] = 'Vote saved.'

end

redirect_to :action => 'index'

end

end

Puis écrire dans le fichier plugins/mon_plugin/app/views/polls/index.html.erb :

<h2>Polls</h2>

<% @polls.each do |poll| %>

<p>

<%= poll.question %>?

<%= link_to 'Yes', {:action => 'vote', :id => poll[:id], :answer => 'yes'}, :method => :post %> (<%= poll.yes %>) /

<%= link_to 'No', {:action => 'vote', :id => poll[:id], :answer => 'no'}, :method => :post %> (<%= poll.no %>)

</p>

<% end %>

On peut supprimer le fichier plugins/mon_plugin/app/views/polls/vote.html.erb (car la méthode vote n’a pas de rendu à l’écran)

Enfin afin d’établir un lien entre les boutons, et l’adresse URL de sondage, il est nécessaire de déclarer des ‘routes’ pour que l’application comprenne ce que l’on veut faire.

Écrire dans le fichier plugins/mon_plugin/config/routes.rb :

get 'polls', :to => 'polls#index'

post 'post/:id/vote', :to => 'polls#vote'

Redémarrer le RedMine (probablement via la commande bundle exec rails server webrick -e production)

Dans un navigateur : localhost:3000/polls devrait permettre d’afficher les sondages créés précédemment.

Problème(s) rencontré(s) :

  1. Lors de l’appel à rails console
/usr/local/lib/ruby/2.3.0/irb/completion.rb:10:in `require': cannot load such file -- readline (LoadError)

[…]

from bin/rails:4:in `<main>'

Solution(s) apportée(s) :

  1. Dans le fichier Gemfile, rajouter la ligne :
gem "rb-readline"

Source(s) solution(s) :

  1. http://stackoverflow.com/questions/22915676/rails-console-in-require-cannot-load-such-file-readline-loaderror

1.3  Gérer les onglets

Actuellement les plugins sont accessibles uniquement depuis l’adresse ‘/polls’ ce qui n’est pas très pratique. Nous devons donc rajouter la possibilité d’accéder à notre index depuis un onglet.

Pour afficher l’accès au plugin dans un onglet,

Dans plugins/mon_plugin/init.rb écrire :

Redmine::Plugin.register :mon_plugin do

[...]

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

end

Syntaxe :

menu <zone_menu>, <nom_model>, { <action> }, <nom_menu>

<zone_menu> peut être :

:top_menu – En haut à gauche

:account_menu – En haut à droite (près de my account, sign out, …)

:application_menu – Le menu principal

:project_menu – Le menu dans un projet

:admin_menu – Le menu de la page administrateur (entre plugin et settings)

NOTE : pour utiliser l’option :project_menu, il est utile d’écrire la ligne :

permission :polls, { :polls => [:index, :vote] }, :public => true

Juste au-dessus de l’autre pour permettre aux utilisateurs de voir l’onglet.

<nom_model> est le nom du model implémenté et utilisé, ici polls

<action> est l’action exécutée lors du clic sur l’onglet :

:controller => 'polls', :action => 'index'

Signifie que la méthode ‘index’ (qui renvoie tous les sondages) du controller de ‘polls’ est appelé au clic.

<nom_menu> sous la forme :caption => ‘<nom>’ donne simplement un nom brut à l’onglet

De plus vastes options peuvent être trouvées sur : http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial#Extending-the-application-menu

Nous allons nous placer dans le contexte de l’affichage par projet (:project_menu).

Pour que l’onglet du plugin, affiche les autres onglets liés au projet :

Dans le fichier plugins/mon_plugin/app/controller/polls_controller.rb modifier :

def index

@project = Project.find_by_identifier(params[:project_id])

@polls = Poll.all

end

Pour supprimer des éléments de menu :

Dans le fichier plugins/mon_plugin/init.rb ecrire :

delete_menu_item <zone_menu>, <nom_element>

<zone_menu> comme vu précédemment

<nom_element> le nom du menu à supprimer

Par exemple pour supprimer l’onglet Gantt des projets :

delete_menu_item :project_menu, :gantt

Problème(s) rencontré(s) :

  1. Erreur 404 not found lors de la création de la méthode index pour les sondages.
  2. Après vote, les menus ne reviennent pas.

Solution(s) apportée(s) :

  1. remplacer
@project = Project.find(params[:project_id])

Par

@project = Project.find_by_identifier(params[:project_id])
  1. Dans plugins/mon_plugin/app/controllers/polls_controller.rb :

Changer

redirect_to :action => 'index'

Par

redirect_to :action => 'index', :project_id => params[:project_id]

Dans plugins/mon_plugin/app/polls/index.html.erb :

après   :answer => ‘yes’ et :answer => ‘no’   ajouter :   , :project_id => @project[:identifier]   de cette manière :

<%= link_to 'Yes', {:action => 'vote', :id => poll[:id], :answer => 'yes', :project_id => @project[:identifier]}, :method => :post %> (<%= poll.yes %>)

Source(s) solution(s) :

  1. http://www.redmine.org/boards/3/topics/48536
  2. http://www.redmine.org/boards/3/topics/11999

1.4  Définir des Permissions

Toutes les actions ne sont pas bonnes à être utilisées par tout le monde, il peut être nécessaire de définir des permissions pour ces actions. Dans le fichier plugins/mon_plugin/init.rb :

Commenter la ligne

permission :polls, { :polls => [:index, :vote] }, :public => true

Ajouter les lignes :

permission :view_polls, :polls => :index

permission :vote_polls, :polls => :vote

Syntaxe :

permission :<nom_permission>, :<nom_model> => :<nom_methode>

<nom_permission> est le nom choisi par l’utilisateur pour cette permission (espaces remplacés par des _ )

<nom_model> le nom du model auquel appartient la méthode à gérer

<nom_méthode> le nom de la méthode qui doit avoir une permission

Dans le fichier plugins/mon_plugin/app/controllers/polls_controller.rb modifier le fichier de la manière suivante :

class PollsController < ApplicationController

unloadable

before_filter :find_project, :authorize, :only => [:index, :vote]

def index

#@project = Project.find_by_identifier(params[:project_id])

@polls = Poll.all

end

[...]

private

def find_project

#@project variable must be set before calling the authorize filter

@project = Project.find_by_identifier(params[:project_id])

end

end

Pour renommer proprement les permissions, aller dans plugins/mon_plugin/config/locales/ et créer (ou éditer) le fichier .yml de la langue voulue (ex : en.yml) pour y ajouter

en:

permission_view_polls: View Polls

permission_vote_polls: Vote Polls

Pour permettre d'utiliser ce plugin dans certain projet (pas tous) :

Éditer le fichier plugins/mon_plugin/init.rb pour le modifier de la sorte :

project_module :polls do

permission :view_polls, :polls => :index

permission :vote_polls, :polls => :vote

end

Problème(s) rencontré(s) :

  1. Les votes ne semblent pas être affectés par les permissions.

Solution(s) apportée(s) :

  1. Modification du code

Source(s) solution(s) :

  1. http://www.redmine.org/boards/2/topics/17002

1.5  Amélioration du rendu visuel

Afin de mettre un peu de couleur et d’améliorer le rendu du plugin :

Créer un fichier <nom>.css dans plugins/mon_plugin/assets/stylesheets/

<nom> le nom que l’on souhaite ici par exemple : voting.css

Et y écrire :

a.vote { font-size: 120%; }

a.vote.yes { color: green; }

a.vote.no  { color: red; }

Modifier le fichier plugins/mon_plugin/app/views/polls/index.html.erb pour y ajouter

, :class => ‘vote yes’ après :post

soit :

<%= link_to 'Yes', {:action => 'vote', :id => poll[:id], :answer => 'yes' }, :method => :post, :class => 'vote yes' %> (<%= poll.yes %>)

<%= link_to 'No', {:action => 'vote', :id => poll[:id], :answer => 'no' }, :method => :post, :class => 'vote no' %> (<%= poll.no %>)

Et ajouter :

<% content_for :header_tags do %>

<%= stylesheet_link_tag 'voting.css', :plugin => 'mon_plugin' %>

<% end %>

à la fin du fichier.

(On peut également modifier le titre de la page avec <% html_title “Polls” %>)

NOTE : Il est possible que l’affichage du css ne fonctionne pas, se référer à l’erreur ci-dessous

Problème(s) rencontré(s) :

  1. Le fichier css ne fonctionne pas

Solution(s) apportée(s) :

  1. Doubler le 1er élément de chaque bloc css, de la sorte :
a.vote {

font-size: 120%;

font-size: 120%;

}

Source(s) solution(s) :

  1. Tests depuis l’éditeur du navigateur web.

1.6  Utiliser le principe des ‘Hooks’

La suite de ce tutoriel n’as donc plus rien à voir avec la partie des sondages, elle permet uniquement de donner des exemples d’utilisations divers. Pour ajouter du contenu à une page existante, on utilise le principe des hooks (accroches) qui permettent d’incruster du code dans une partie de code déjà existante. Créer un fichier plugins/mon_plugin/lib/hooks.rb contenant :

class Hooks < Redmine::Hook::ViewListener

def view_projects_show_left(context = {})

return content_tag("p", "Custom content added to the left")

end

def view_projects_show_right(context = {})

return content_tag("p", "Custom content added to the right")

end

end

Ajouter au fichier plugins/mon_plugin/init.rb la ligne suivante :

require_dependency 'hooks'

Méthode alternative :

Dans le fichier hooks.rb, écrire :

class Hooks < Redmine::Hook::ViewListener

render_on :view_projects_show_left, :partial => "polls/overview"

end

Créer un fichier plugins/mon_plugin/app/views/polls/_overview.html.erb et y rentrer du texte ex :

"Custom content added to the left"

Problème(s) rencontré(s) :

  1. Aucuns

Solution(s) apportée(s) :

  1. Aucunes

Source(s) solution(s) :

  1. Aucunes

1.7  Rendre le plugin configurable

Il peut être parfois intéressant de rendre une partie du plugin configurable, pour activer l’option de configuration :

Ajouter au fichier plugins/mon_plugin/init.rb la ligne :

settings :default => {'empty' => true}, :partial => 'settings/<mon_module>_settings'

<mon_module> est le nom attribué aux configurations, il doit être différent de celui de tout les autres plugins RedMine.

Créer le répertoire plugins/mon_plugin/app/views/settings/

Créer le fichier plugins/mon_plugin/app/views/settings/_<mon_module>_settings.html.erb et y inscrire du code html, tel que :

<table>

<tbody>

<tr>

<th>Notification Default Address</th>

<td>

<input type="text" id="settings_notification_default"

value="<%= settings['notification_default'] %>"

name="settings[notification_default]" >

</td>

</tr>

</tbody>

</table>

<br>

<% for status in IssueStatus.all do %>

<p>

<label for="settings_status-<%= status.id %>"><%= status.name %></label>

<%= select_tag "settings[status-#{status.id}]",

options_from_collection_for_select(

[[l(:label_no_change_option), '']] +

(0..10).to_a.collect{|r| ["#{r*10} %", r*10] },

:last, :first, @settings["status-#{status.id}"]

) %>

</p>

<% end %>

(Ceci est un exemple sans aucun rapport avec le reste).

Problème(s) rencontré(s) :

  1. Aucuns

Solution(s) apportée(s) :

  1. Aucunes

Source(s) solution(s) :

  1. Aucunes

1.8 Phases de Test

Enfin pour finir, nous testons le bon fonctionnement de notre application via des scripts de test pré exécutables par Ruby On Rails. On peut effectuer des tests Unitaires, Fonctionnels, … Ici nous ne verrons que les tests fonctionnels.

Les tests sont contenus dans le fichier plugins/mon_plugin/test/functional/polls_controller_test.rb

Y inscrire les tests à effectuer, ex :

require File.expand_path('../../test_helper', __FILE__)

class PollsControllerTest < ActionController::TestCase

def test_index

get :index, :project_id => 1

assert_response :redirect

assert_template 'index'

end

end

Initialiser la base de test :

$ rake db:drop db:create db:migrate redmine:plugins:migrate redmine:load_default_data RAILS_ENV=test

NOTE : Erreur 1

Lancer le test :

$ bundle exec ruby plugins/mon_plugin/test/functional/polls_controller_test.rb

NOTE : Multiples erreurs possibles … (2, 3, 4, 5, 6)

Après résolution des erreurs depuis la section Solutions Apportées (voir ci-dessous), la commande devrait dorénavant fonctionner

Problème(s) rencontré(s) :

1. Error: unknown attribute 'issues_visibility' for Role.
2. `require': cannot load such file -- mocha/setup (LoadError)

3. `rescue in block (2 levels) in require': There was an error while trying to load the gem 'mocha'. (Bundler::GemRequireError)
Gem Load Error is: method `run' not defined in Minitest::Unit::TestCase

4. /usr/local/lib/ruby/gems/2.3.0/gems/bundler-1.14.6/lib/bundler/runtime.rb:40:in `block in setup': You have already activated mocha 1.2.1, but your Gemfile requires mocha 0.12.10. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)

5. Warning : *** Mocha deprecation warning: Change `require 'mocha'` to `require 'mocha/setup'`.

6. expecting <"index"> but rendering with <[]>

Solution(s) apportée(s) :

  1. Aucunes (Pas trouvé de solutions)
  2. Lancer la commande
$ bundle install --with test
  1. Dans le Gemfile, écrire :
require "rubygems"

gem "mocha", "~> 0.13.0"

require 'minitest/autorun'

$ bundle install
  1. Désinstaller la mauvaise version de mocha (1.2.1) via la commande :
$ sudo gem uninstall mocha
  1. Modifier dans le Gemfile :
gem "mocha", "~> 0.13.0", :require => false
  1. Aucune solution pour le moment …

Source(s) solution(s) :

  1. Aucunes
  2. Aucunes
  3. https://github.com/freerange/mocha

https://github.com/freerange/mocha/issues/187

  1. http://stackoverflow.com/questions/6317980/you-have-already-activated-x-but-your-gemfile-requires-y
  2. http://jamesmead.org/blog/2013-01-24-using-mocha-with-rails-3-and-minitest
  3. Aucune

2. Durée de développement

En raison du peu d’explication et du manque d’adaptation du tutoriel, le développement de ce module a duré 2 jours.

3. Commentaires supplémentaires

Le tutoriel officiel de création de plugin RedMine n’est pas adapté à RedMine 3.3.3.
De plus, celui-ci contient de nombreuses erreurs, ce que n’a pas aidé son déroulement.

Il est fortement recommandé d’utiliser à la place l’article [CREATION_PLUGIN_REDMINE] qui contient de plus amples explications, ainsi que plus d’exemples pour des cas différents (surcharges de méthodes déjà existantes dans le cœur de l’application par exemple).

Auteur : Alexandre BOUDINE