Tutoriel du plugin Polls

Remarque : Pour suivre ce tutoriel, vous devez avoir installé Redmine devel r1786 ou supérieur.

Créer un nouveau plugin

Vous devez préalablement initialiser la variable RAILS_ENV sur "production" dans votre session shell pour continuer le tutotiel, pour ce faire, tapez :

$ export RAILS_ENV="production" 

Pour créer un plugin Redmine, il existe des generateur pour les taches essentielles.

La syntaxe des générateurs est :

ruby script/generate redmine_plugin <plugin_name>

pour commencer, on va se positionner dans le répertoire de l'application Rails Redmine, puis créer la structure du plugin :

% ruby script/generate redmine_plugin Polls

La structure du plugin est créé dans le repertoire vendor/plugins/redmine_polls:

      create  vendor/plugins/redmine_polls/app/controllers
      create  vendor/plugins/redmine_polls/app/helpers
      create  vendor/plugins/redmine_polls/app/models
      create  vendor/plugins/redmine_polls/app/views
      create  vendor/plugins/redmine_polls/db/migrate
      create  vendor/plugins/redmine_polls/lib/tasks
      create  vendor/plugins/redmine_polls/assets/images
      create  vendor/plugins/redmine_polls/assets/javascripts
      create  vendor/plugins/redmine_polls/assets/stylesheets
      create  vendor/plugins/redmine_polls/lang
      create  vendor/plugins/redmine_polls/README
      create  vendor/plugins/redmine_polls/init.rb
      create  vendor/plugins/redmine_polls/lang/en.yml

Editer vendor/plugins/redmine_polls/init.rb pour adjuster les informations (attributs) du plugin (nom, auteur, description et version):

require 'redmine'

Redmine::Plugin.register :redmine_polls do
  name 'Polls plugin'
  author 'John Smith'
  description 'A plugin for managing polls'
  version '0.0.1'
end

Redemarrez l'application Redmine et ouvrez un navigateur sur l'URL http://localhost:3000/admin/plugins.

Après vous être loggué avec les droits d'administrateur Redmine, vous devriez voir la liste des plugins tel que :

Générer un modèle Rails de plugin Redmine

pour le moment le plugin ne persiste aucuns résultats
Nous allons donc créer un modèle Poll simple, tel que :

   ruby script/generate redmine_plugin_model <plugin_name> <model_name> [<fields>]

La commande exacte pour notre cas sera :

   ruby script/generate redmine_plugin_model polls poll question:string yes:integer no:integer

cette commande crée le modèle Rails Poll et les fichiers de migration correspondant

*Remarque : Il est possible que vous deviez renommer les fichiers de migration, les migrations avec des timestamps ne sont pas supportée par le moteur Redmine Actuelle, Si les migrations portent une numérotation avec des timestamps, renommez-les avec "001" "002", etc...

cd vendor/plugins/redmine_polls/db/migrate
mv  20091009211553_create_polls.rb 001_create_polls.rb
cd ../../../../..

Si vous avez déja executé une migration, vous devez mettre à jour les informations dans la table plugin_schema_info en adequation avec la nouvelle numérotation.

Migrer la base de données avec la commande :

rake db:migrate_plugins

Il est à noter que chaque plugin à son propre jeux de migration.

Nous allons ajouter des Votes via la console rails pour pouvoir faire des tests avec.
La console Rails permet l'ajout interactif d'enregistrement sur les modèles rails, c'est une shell Ruby Irb, qui pré-charge l'environnement de l'application rails depuis laquelle il est lancé.
Pour le moment nous allons juste créer un objet Poll

script/console
>> Poll.create(:question => "Pouvez-vous voir ce vote ?")
>> Poll.create(:question => "Pouvez-vous voir cet autre vote ?")
>> exit

Editez vendor/plugins/redmine_polls/app/models/poll.rb dans le répertoire de votre plugin et ajoutez une méthode #vote qui sera appelée par votre controller:

class Poll < ActiveRecord::Base
  def vote(answer)
    increment(answer == 'yes' ? :yes : :no)
  end
end

Generer un controller

Pour le moment le plugins ne fait rien, donc nous allons créer un controller Rails pour celui-ci.
Nous pouvons utiliser le générateur Redmine prévue à cet effet :

ruby script/generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]

la commande exacte pour notre cas est :

% ruby script/generate redmine_plugin_controller Polls polls index vote
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/polls
      create  test/functional/
      create  app/controllers/polls_controller.rb
      create  test/functional/polls_controller_test.rb
      create  app/helpers/polls_helper.rb
      create  app/views/polls/index.html.erb
      create  app/views/polls/vote.html.erb

un controlleur Rails PollsController avec 2 actions (#index et #vote) vient d'être créé.

Editez vendor/plugins/redmine_polls/app/controllers/polls_controller.rb dans le répertoire redmine_polls pour implémenter ces deux actions.

class PollsController < ApplicationController
  unloadable

  def index
    @polls = Poll.find(:all)
  end

  def vote
    poll = Poll.find(params[:id])
    poll.vote(params[:answer])
    if poll.save
      flash[:notice] = 'Vote saved.'
      redirect_to :action => 'index'
    end
  end
end

Ensuite, editez la vue vendor/plugins/redmine_polls/app/views/polls/index.html.erb qui va afficher les votes existants.

<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 %>

Vous pouvez supprimer la vue vendor/plugins/redmine_polls/app/views/polls/vote.html.erb comme il n'y a pas de rendu pour l'action correspondante.

Redemarreez le serveur Rails de Redmine et ouvrez votre navigateur sur l'URL http://localhost:3000/polls.
Vous devriez voir deux votes et vous pouvez les essailler :

Remarque : les resultats des votes sont remis à zero à chaque requête si vous ne lancez pas votre application en environnement "production", vue que le modèle Poll est stocké dans une variable de classe dans notre exemple.

Traductions

L'emplacement des fichiers YAML d'internationalisation peut varier en fonction de votre version de Redmine :

Version Path
< 0.9 .../redmine_polls/lang
>= 0.9 .../redmine_polls/config/locales

Si vous voulez que votre plugin marche dans les deux cas vous devez ajouter les fichiers dans les deux cas au bon format.

Gestion des menus

Notre controller marche bien, mais nos utilisateurs n'en connaissent pas l'existence, il faut donc utilser l'API Redmine pour etendre les menus
Nous allons ajouter un onglet au menu Application
So let's add a new item to the application menu.

Gestion du menu application

Editez le fichier vendor/plugins/redmine_polls/init.rb à la racine de votre plugin et ajoutez la ligne suivante dans le bloc ""registration"" :

Redmine::Plugin.register :redmine_polls do
  [...]

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

la syntaxe est :

menu(menu_name, item_name, url, options={})

Il y a cinq menu dans Redmine que vous pouvez étendre :

  • :top_menu - le menu en haut à gauche
  • :account_menu - le menu en haut à droite avec la gestion des comptes et connexions
  • :application_menu - Le menu affiché quand un utilisateur n'est dans aucuns projets.
  • :project_menu - Le menu affiché quand un utilisateur est dans un projet.
  • :admin_menu - le menu d'administration

les options disponibles sont :

  • :param - la clef parametre utilisée pour les id de projet (valeur par défaut :id)
  • :if - un Proc appelé avant de faire le rendu du menu,l'item est affiché que si le Proc retourne true
  • :caption - Le label du menu du type:
    • un symbole de chaine de caratères localisée
    • une chaine de caratères
    • un Proc qui peut pendre le project_id en argument
  • :before, :after - specifie ou l'item du menu doit être inséré (ex. :after => :activity)
  • :first, :last - si ils sont true, l'item est positionné au début ou a la fin du menu (ex. :last => true)
  • :html - un hachage d'options html passés à link_to pour le rendu du menuitem

Dans notre exemple, nous avons ajoutez un item au menu application, qui est vide par defaut.

Redemarrez l'application et allez à l'URL http://localhost:3000:

Maintenant vous pouvez accéder aux votes en cliquant sur l'onglet Polls depuis l'ecran d'accueil de Redmine

Etendre le menu projet

On va considérer, mantenant que les votes sont définis au niveau des projets (même si ce n'est pas le cas dans le modèle de notre exemple)
Donc, on veut ajouter l'onglet au menu Projet et plus au menu Application.

Editez le fichier init.rb et replacez la dernière ligne que nous avons ajoutez par les deux lignes suivantes :

Redmine::Plugin.register :redmine_polls do
  [...]

  permission :polls, {:polls => [:index, :vote]}, :public => true
  menu :project_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
end

la deuxième ligne ajoute l'onglet Polls dans le menu Projet juste derrière l'onglet Activité.
la première ligne sert à déclarer que nos deux actions du PollsController sont publiques.
Nous reviendrons plus tard sur les permissions.

Redémarrez encore l'application et entrez dans un de vos projets:

If you click the Polls tab, you should notice that the project menu is no longer displayed.
To make the project menu visible, you have to initialize the controller's instance variable @project.

Si vous cliquez sur l'onglet Polls, vous constatez que le menu projet n'est plus affiché.
Pour continuer à l'afficher, nous devons initialiser la variable d'instance @project du controlleur.

Editez votre PollsController tel que:

def index
  @project = Project.find(params[:project_id])
  @polls = Poll.find(:all) # @project.polls
end

L'id du projet est disponible dans :project_id grace à l'option :param => :project_id dans la déclaration du menu item précédante.

Maintenant, vous devriez continuer à voir le menu projet quand vous visualisez un vote :

Ajouter des nouvelles permissions

Pour le moment, tout le monde peut voter.
On peut rendre ceci configurable en ajoutant des permissions Redmine.
Nous allons definir deux permissions au niveau Projet, une pour visualiser et une pour voter.
Ces permissions ne seront donc plus publiques (l'option :public => true disparait)

Editez vendor/plugins/redmine_polls/init.rb pour remplacer la permission précedante avec les deux lignes suivantes :


  permission :view_polls, :polls => :index
  permission :vote_polls, :polls => :vote

Redemarrez l'application et allez à l'URL http://localhost:3000/roles/report:

on voit donc deux permissions de plus ajoutable à votre role.
Biensur, il va falloir ajouter du code au PollsController, donc ces actions sont pour le moment protégé par les permissions de l'utilisateur courant.

Pour cela, nous avons juste à ajouter le filtre :authorize et faire en sorte que la variable d'instance @project est initialisée avant d'appeler ce filtre

Voici à quoi ça doit ressembler dans l'action #index :

class PollsController < ApplicationController
  unloadable

  before_filter :find_project, :authorize, :only => :index

  [...]

  def index
    @polls = Poll.find(:all) # @project.polls
  end

  [...]

  private

  def find_project
    # @project variable must be set before calling the authorize filter
    @project = Project.find(params[:project_id])
  end
end

Récupérer le projet courant avant le #vote peut-être fait de façon très similaire.
Après ça, voir un vote et voter ne sera accessible qu'aux administrateur ou aux utilisateurs que on un rôle approprié dnas le projet.

Pour traduire les symboles de vos permissions dans plusieurs langues, vous devez ajouter les labels de textes necessaires dans vos fichiers de langues tel que :
labels like this:


  permission_view_polls: Voir les votes
  permission_vote_polls: Voter

Dans cette exemple, on a créé le fichier fr.yml, mais on peut créer des fichiers pour toutes les langues que l'on souhaite.

On voit dans cette exemple, que le lable est le symbole de la permission :view_polls et :vote_polls préfixé de permission_

Redemarrez votre application et pointer sur la section permissions.

Créer un module de projets

Pour le moment, la fonctionnalité de vote est ajoutée à tous vos projets, mais on souhaite la rendre activable ou désactivable pour chaque projet.
On va donc créer un module de projet 'Polls'.
On fait ceci en inclant les permissions dans un bloc #project_module.

Editez init.rb et changez la déclaration des permissions :

  project_module :polls do
    permission :view_polls, :polls => :index
    permission :vote_polls, :polls => :vote
  end

Redemarrez votre application et pointer sur un de vos projet dans sa configuration.
Cliquez sur l'onglet Modules, vous y verez le module Polls à la fin de la liste de modules, désactivé par defaut.

Vous pouvez l'activer ou le désactiver pour chaque projet.

Améliorer la vue du plugin

Ajouter des Styles CSS

On va commencer par ajouter un CSS à notre vue

Créez un fichier nommé voting.css dans le répertoire vendor/plugins/redmine_polls/assets/stylesheets :

a.vote { font-size: 120%; }
a.vote.yes { color: green; }
a.vote.no  { color: red; }

Quand on redemarrer l'application, les plugin assets sont copié dans public/plugin_assets/redmine_polls/
par le moteur de Rails pour les rendrent disponibles via votre serveur Web.
Donc toute modification des CSS des plugins impliques un redemarrage de Rails.
Ensuite, on va devoir ajouter les lignes suivantes dans la vue vendor/plugins/redmine_polls/app/views/polls/index.html.erb pour que nos CSS soient pris en compte et incluent dans le header de cette vue par Redmine :

<% content_for :header_tags do %>
    <%= stylesheet_link_tag 'voting', :plugin => 'redmine_polls' %>
<% end %>

Notez que l'option :plugin => 'redmine_polls' est requise quand on appelle le helper stylesheet_link_tag.

Les Javascripts peuvent être inclut dans le plugin en utilisant le helper javascript_include_tag de la même manière.

Modifier le titre de la page

on peut modifier le titre HTML de la page depuis le vue elle-même grace au helper html_title
Exemple:

<% html_title "Polls" -%>

Tester votre plugin

test/test_helper.rb:

Voici le contenu de mon fichier de tests :

require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')

Exemple de test:

Contenu de requirements_controller_test.rb:

require File.dirname(__FILE__) + '/../test_helper'
require 'requirements_controller'

class RequirementsControllerTest < ActionController::TestCase
  fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
           :attachments, :custom_fields, :custom_values, :time_entries

  def setup
    @skill = Skill.new(:skill_name => 'Java')
    @project = Project.find(1)
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    User.current = nil
  end

  def test_routing
    assert_routing(
      {:method => :get, :path => '/requirements'},
      :controller => 'requirements', :action => 'index'
    )
  end

Initialisation de la base de test :

Il semble plus facile d'initialiser la base de tests directement par la commande rake :

rake db:drop db:create db:migrate db:migrate_plugins redmine:load_default_data RAILS_ENV=test

Executer les tests:

Pour executer the requirements_controller_test.rb on utilise la commande suivante :

rake test:engines:all PLUGIN=redmine_requirements

Tester avec des utilisateurs et des projets

If your plugin requires membership to a project, add the following to the beginning of your functional tests:

Si votre plugin requière de appartenances à un projet, ajouter les lignes suivantes pour que ce soit opérationnel :

def setup
  @request    = ActionController::TestRequest.new
  @response   = ActionController::TestResponse.new
  User.current = nil
end

def test_index
  @request.session[:user_id] = 2
  get :index, :project_id => 1
  assert_response :success
  assert_template :index
end

project_menu.png (17.3 KB) Romain GEORGES, 2011-03-23 12:36

project_menu_pools.png (16.9 KB) Romain GEORGES, 2011-03-23 12:36

application_menu.png (9.22 KB) Romain GEORGES, 2011-03-23 12:36

modules.png (10 KB) Romain GEORGES, 2011-03-23 12:36

permissions1.png (8.43 KB) Romain GEORGES, 2011-03-23 12:36

plugins_list1.png (3.23 KB) Romain GEORGES, 2011-03-23 12:36

pools1.png (16.5 KB) Romain GEORGES, 2011-03-23 12:36