Configuration de Redmine pour Github
Posted by JB on 12/01/09 at 18:51
L’info est déjà disponible au fond d’une page du wiki Redmine, ici. Je vais faire exactement pareil, parce que c’est bien. Et comme j’ai envie de faire un article technique, je mets ça ici.
Voilà donc en très bref la config que j’ai utilisée pour linker mon Redmine (prochainement en ligne, dès que les bons DNS seront propagés) à mon dépôt github pour Simplelog (rien mis encore dessus, seulement la release officielle).
$ sudo mkdir -p /var/redmine/git_repositories
$ sudo chown rails:rails /var/redmine/git_repositories
$ cd /var/redmine/git_repositories
$ git clone —bare git://github.com/jbbarth/simplelog.git
$ crontab -e
#git repo for simplelog
*/15 * * * * git-pull /var/redmine/git_repositories/simplelog/ 2>&1 | grep -vE "^From|FETCH_HEAD|^Already up-to-date"
Synchronisation git pour Redmine
Posted by JB on 23/05/09 at 11:42
J’avais évoqué ici la synchronisation de dépôts Git (en l’occurrence Github mais ça n’a pas d’importance).
Mais voilà, ça ne fonctionnait plus tout à fait après migration de serveur, et ce n’était pas très clair pour moi honnêtement. Alors j’ai de nouveau regardé sur le wiki de Redmine et trouvé cet article qui décrit les choses très bien.
Seule différence, j’utilise les branches sous Git, et ce mode ne synchronise que la branche principale (“master”, ce que l’on appellerait le “trunk” sous subversion).
Initialisation de mon dépôt:
cd /var/redmine/git_repositories/ git clone —bare git@github.com:jbbarth/project.git project cd project git remote add origin git@github.com:jbbarth/project.git git fetch -v
Ajout de ceci en CRON:
#sync of my github repos for redmine */10 * * * * /var/redmine/git_repositories/sync_repositories.sh >/dev/null
Et le petit script qui va bien:
#!/bin/sh
base=$(dirname $0)
[ "$base" == "." ] && base=$(pwd)
cd $base
for repo in $(ls -F |fgrep "/"); do
cd $base/$repo
git fetch origin
for branch in $(ls refs/remotes/origin/); do
git reset —soft refs/remotes/origin/$branch
done
doneYoupi!
Need a wiki ?
Posted by JB on 24/05/09 at 08:57
En ce moment j’ai trop d’articles techniques à publier, souvent trop en bazar, ou trop long pour être publiés dans une demi-colone de ce blog. De plus je risque de quitter mon boulot dans quelques mois, et tout ce que j’ai pû documenter là-bas sera perdu pour moi si je ne le récupère pas avant. Bref, j’ai besoin d’un wiki, simple d’utilisation, avec suivi des révisions, formatage en Textile, un peu comme celui de Remine.
Une contrainte supplémentaire, qu’il soit en Ruby on Rails, éventuellement Merb. Pas par idéologie, croyance en REST, ou quelconque connerie de ce genre (et certains en tiennent une couche à ce niveau quand on voit la liste rails-france), mais plutôt parce que j’en ai marre de maintenir 50 technos hétérogènes sur mon serveur. Mon blog, mon gestionnaire de projets tournent déjà en Rails. Ce sera bientôt le cas du site chanmasters.com que je réécris en ce moment. Bientôt le cas également de ma gallerie de photos, qui tourne déjà avec des scripts ruby mais pas administrable en mode web. Donc dommage pour Dokuwiki qui a priori me convenait parfaitement…
J’ai essayé Instiki sans être réellement convaincu, je ne saurais pas bien expliquer pourquoi. Et puis, j’aurais des modifs à faire dessus pour qu’il me convienne (templates trop épurées à mon goût, affichage “geeky” de certains champs, etc…). Ce qui implique un petit peu d’exploration du code, cf la suite. Je n’ai pas trouvé d’autres projets très actifs en Rails et qui me convienne (liste ici)…
En fait, le wiki de Redmine me convient parfaitement. Facile à “hacker” (je commence à connaître un peu le code même si je suis loin d’être un gourou comme les 3 ou 4 grands contributeurs réguliers), maintenu, communauté active, support de textile entre autres, jolis diff, etc. Nickel, à 4 petites choses près :
- le support Textile est un peu foireux parfois ; voir ici
- la coloration syntaxique est hideuse, j’aimerais bien remplacer CodeRay par autre chose ; voir ici
- j’aimerais bien que les hiérarchies soient faites automatiquement, quitte à rendre l’arborescence un peu profonde, pas grave ; voir ici
- j’aimerais bien un système de tags avec un nuage sur le côté ; voir ici, ici et ici
Bref, ça sent bon, et le choix est tout fait ; je documenterai dans le prochain article l’installation et la configuration de Redmine pour me servir de wiki :-)
Redmine as a wiki engine
Posted by JB on 24/05/09 at 10:27
Ce post fait suite à celui-ci.
Tout d’abord, on installe Redmine classiquement ; je passe volontairement les aspects DNS (création d’un sous-domaine, en l’occurence wiki.jbbarth.com), Apache (création du vhost), et “système” (script de démarrage de Mongrel, user et port adéquats, ce genre de choses) :
cd /home/app/jbbarth/ svn co http://redmine.rubyforge.org/svn/trunk _redmine-0.8-wiki ln -s _redmine-0.8-wiki wiki cd wiki/ rake db:migrate rake redmine:load_default_data rake config/initializers/session_store.rb
Après démarrage, on procède à une configuration basique de Redmine :
- modification du user/pass admin
- configuration générale dans Administration > Settings
- création d’un projet public “wiki”, identifiant “wiki” ; tous les trackers décochés, et tous les modules sauf le module “wiki”
- dans ce projet, on configurera la “Start page”, et surtout on la créera/remplira (sous peine d’avoir des erreurs 404 dans la suite)
Là commencent les choses “sérieuses”. Que voulons-nous ?
1) que l’accueil de Redmine se fasse sur la page de démarrage du wiki du projet “wiki”
Pour cela, nous allons éditer config/routes.rb, et remplacer l’accueil défini à la ligne “map.home” par :
#map.home '', :controller => 'welcome' map.home '', :controller => 'wiki', :id => 'wiki'
Après redémarrage de l’instance, ça fonctionne !
2) suppression des liens inutiles pour un wiki ; en particulier la première tab “Overview/Aperçu”, et “Projects/Projets”, “My page/Ma page” et “Help/Aide” dans le menu en haut à gauche (nous n’aurons qu’un seul projet “wiki”)
Pour cela nous allons créer un thème à nous et cacher ces liens via du CSS, ce qui me parait bien suffisant : ils ne représentent aucun “danger”, c’est juste qu’ils perturbent la navigation dans le cadre d’une utilisation wiki-only. Voir donc ici pour la création d’un nouveau thème, et éventuellement ici pour des exemples de thèmes.
Nous créons donc un thème “wiki”, puis quelques lignes suffisent à la fin de public/themes/wiki/stylesheets/application.css :
/* Specific to wiki */
#top-menu .my-page, #top-menu .projects, #top-menu .help { display:none; }
#main-menu .overview { display:none; }3) passage des patches cités dans l’article précédent
cd /home/app/jbbarth/wiki/ mkdir patches
a) passage de CodeRay à UltraViolet :
wget -P patches http://www.redmine.org/attachments/download/1698/syntax_highlighting.diff patch -p0 < patches/syntax_highlighting.diff wget -P patches http://www.redmine.org/attachments/download/1699/redcloth.diff patch -p0 < patches/redcloth.diff wget -P patches http://www.redmine.org/attachments/1700/ultraviolet_highlighter.zip cd patches/ unzip ultraviolet_highlighter.zip cat ultraviolet_highlighter/README.txt apt-get install libonig-dev gem install ultraviolet cp -a ultraviolet_highlighter ../vendor/plugins/ cd ..
Les traductions ne sont pas bien passées, donc on édite à la main config/locales/en.yml et fr.yml, et on supprime les fichiers “.rej” correspondants.
b) pages parent automatiques :
wget -P patches http://www.redmine.org/attachments/download/2082/3108_automatic_parent_with_tests.diff patch -p0 < patches/3108_automatic_parent_with_tests.diff
c) correction d’un petit bug de Redcloth :
wget -P patches http://www.redmine.org/attachments/download/1728/redcloth_arobas.diff patch -p0 < patches/redcloth_arobas.diff
d) système de tagging :
Il y avait un patch proposé ici, mais il ne correspond pas vraiment à ce que je veux. Voici quand même une méthode pour l’appliquer sur une copie de travail SVN :
wget -P patches http://www.redmine.org/attachments/download/2060/wiki_page_categories_20090520.patch sed -i -e 's#- redmine.orig/#- #g' -e 's#\+ redmine/#+ #g' -e 's#diff.*\.orig/\([^ ]*\).*#Index \1\n===============================================#g' patches/wiki_page_categories_20090520.patch patch -p0 < patches/wiki_page_categories_20090520.patch rake db:migrate
Après toutes ces modifs, on se rend compte que certains patches ne sont pas bien passés :
find . -iname "*.rej"
Normalement avec cet ordre de passage des patches, il n’y a que lib/redcloth3.rb dont on résoud les conflits à la main.
Voilà, hormis le système de tagging on a un wiki fonctionnel. Je ferai un nouveau post si je trouve quelque chose pour le support des tags. Il ne reste plus qu’à le remplir maintenant !
EDIT
- pour que l’activité du projet prenne en compte les changements du wiki par défaut : éditer lib/redmine.rb, et modifier autour de la ligne 155/156:
- activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false + activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => true
- la même option permet que les recherches prennent en compte les entrées du wiki sur le projet courant
- pour cacher la liste déroulante de sélection des projets dans la recherche : édition de public/themes/wiki/stylesheets/application.css, ajout de :
select#scope { display:none; }EDIT2
J’ai changé l’ordre de passage des patches pour avoir le moins de choses possibles à résoudre à la main.
Redmine Plugins #1 : Ajouter une option vrai/faux à un plugin Redmine
Posted by JB on 20/02/10 at 15:42
Je travaille en ce moment sur des plugins Engines pour Redmine. Ces plugins me serviront au boulot, et permettront de laisser une situation un peu plus propre que les bidouillages actuels à mon départ. En particulier en avançant sur le plugin de gestion d’un datacenter (site et dépôt github), j’apprends énormément de choses sur le fonctionnement de Rails/Redmine/Engines, et j’entame donc une série d’articles sur ces découvertes. Ces articles supposent d’avoir déjà lu le tutoriel du site, et je repartirai souvent de cet exemple.
Cela donnera certainement lieu à des entrées dans le wiki redmine.org ou des suggestions dans les tickets. Et puis ça m’astreindra à publier un peu, comme le fait Eric dans ses Daily Refactor du core Redmine depuis 3 semaines et pour les mêmes raisons, et aussi suite à cet article de Damien.
Allons-y.
init.rb : on y ajoute la clé et la valeur par défaut du paramètre qu’on veut introduire, par exemple ici “boolean_parameter”. On précise également un partial qui permettra de gérer les paramètres du plugin :
settings :default => { :boolean_parameter => true }, :partial => 'settings/my_plugin'
app/views/settings/_my_plugin.html.erb : on place ici un formulaire pour gérer nos paramètres. Il sera automatiquement accessible dans la partie Administration > Plugins > lien “Configurer” sur votre plugin. “plugin_my_plugin” est à remplacer dans ce qui suit par “plugin_[nom de votre plugin]” :
<p> <label>Paramètre booléen</label> <%= check_box_tag 'settings[boolean_parameter]', 1, Setting[:plugin_my_plugin][:boolean_parameter] %> </p>
Et voilà ! Ensuite, n’importe où dans votre plugin, vous pourrez utiliser :
Setting[:plugin_my_plugin][:boolean_parameter]
En réalité en mettant “1” comme deuxième paramètre, vous ne stockerez pas un booléen, mais “1” (coché) ou “nil” (décoché). Si vous souhaitez obtenir “true” ou “false” absolument, vous pouvez utiliser :
!!Setting[:plugin_my_plugin][:boolean_parameter]
A voir en vrai ici
Déplacer une base Rails
Posted by JB on 17/03/10 at 14:19
Au boulot nous avons une instance Redmine qui tourne avec une base Sqlite3 pour nos tickets internes. Pratique, mais nous avons aussi développé une offre d’hébergement Redmine ouverte à la demande sur l’intranet, sous Mysql. D’où passage de l’instance Sqlite sous Mysql.
On ne peut bien sûr pas se contenter d’un export SQL de Sqlite à réimporter sous Mysql : ces deux moteurs ne respectent pas exactement la même syntaxe SQL, et ne stockent pas leurs types primitifs de la même manière (exemple: les booléens, stockés en “1/0” sous Mysql, et en “t/f” sous Sqlite).
C’est là qu’arrive yaml_db , une biblitothèque à installer comme une gem ou comme un plugin dans une appli Rails, qui permet de réaliser des exports ou imports de sa base sous un format neutre, YAML (wikipedia).
Pour une migration “one shot”, le plus simple sera de cloner la lib dans le répertoire plugins de vos applis et de suivre les instructions proposées dans le README :
cd /path/to/my/app cd vendor/plugins git clone http://github.com/ludicast/yaml_db.git cd - rake db:dump #modifications éventuelles du fichier db/data.yml (chez nous l'appli change d'adresse, donc on a remplacé toutes les anciennes URLs) #changement de database.yml rake db:load
Un outil simple, comme on aime :)
EDIT: j’ai titré “déplacer une base Rails” car ce plugin fonctionne bien surtout avec une base ActiveRecord, ORM de Rails par défaut. Pour une base quelconque rien de garanti :)
Il peut arriver qu’une classe de Redmine ne se comporte pas exactement comme vous le voudriez, ou que vous souhaitiez lui ajouter des propriétés.
C’est décrit en anglais sur la page Plugin Internals / Extending the Redmine Core du wiki officiel, qui renvoie vers la lecture de certains plugins d’Eric Davis pour des exemples.
Petit apparté, je partage assez l’analyse selon laquelle il est quasi inutile de maintenir une API pour surcharger les modèles / controlleurs. Cela dit, parfois les méthodes sont extrêmement longues et/ou sujettes à de fréquents changements. Toute surcharge dans un plugin induit donc un risque pour les futures versions du core…
Retour à nos moutons : admettons qu’on veuille ajouter au modèle Issue une méthode d’instance whoami qui retournerait “Je suis le ticket #XXX”. Exemple bidon, c’est pour la science.
Si on applique ce que préconise Eric, ça donne quelque chose de ce genre :
#init.rb require_dependency 'issue_patch' Dispatcher.to_prepare do Issue.send(:include, IssuePatch) unless Issue.included_modules.include? IssuePatch end #lib/issue_patch.rb require_dependency 'issue' module IssuePatch def self.included(base) base.extend(ClassMethods) base.send(:include, InstanceMethods) base.class_eval do unloadable #permet de décharger la classe en mode dev end end #ici nos méthodes de classe module ClassMethods end #ici nos méthodes d'instance module InstanceMethods def whoami "Je suis le ticket ##{self.id}" end end end
Classique, mais comme diraient certains amis “on voit pas trop ce que ça fait”.
Personnellement je préfère réouvrir la classe Issue, et ça a l’air de marcher tout aussi bien (en dev et en prod) :
#init.rb config.to_prepare do require_dependency 'issue_patch' end #lib/issue_patch.rb require_dependency 'issue' class Issue def whoami "Je suis le ticket ##{self.id}" end end
Différences :
- utilisation de “config” au lieu de “Dispatcher” ; sans importance à mon avis. C’est discuté un peu ici.
- ré-ouverture de la classe plutôt qu’inclusion d’un module ; je trouve ça plus lisible pour ce coup-ci
Attention, je ne dis pas que ce que fait Eric fonctionne moins bien. Au contraire, c’est peut-être plus “propre”, mais n’étant pas un développeur confirmé, si je ne comprends pas au premier coup d’oeil ce que j’ai fait, j’ai plus de mal à maintenir mon code.
Au passage, c’est une mauvaise idée d’appeler son patch “lib/issue_patch.rb”. Si tout le monde fait ça, on ne pourra pas faire fonctionner 2 plugins qui patchent la même classe en même temps. Beurk. D’ailleurs, c’était le cas pour des plugins à moi, donc autant utiliser des noms a priori uniques : commit redmine_drafts/ec06b8
Redmine supporte à ce jour 37 langues. Si vous souhaitez diffuser votre plugin, c’est une bonne idée de respecter les mêmes conventions que le core, pour en faciliter les traductions, voire proposer plusieurs traductions de votre plugin directement. C’est ce que je fais par exemple pour mon plugin “Datacenter” que je livre en anglais et en français (voir la page de wiki française).
Pour cela, Redmine utilise l’internationalisation de Rails. Chaque mot ou groupe de mot qui doit être traduit est associé à une clé unique. Chaque langue a son fichier YAML dans le dossier config/locales/, et dans ce fichier on indique que telle clé correspond à telle chaine de caractères. Par exemple, plutôt que d’écrire “Mon super plugin” directement dans vos vus et helpers, vous allez lui associer une clé de votre choix, mettons text_my_super_plugin.
Dans la vue, vous pourrez utiliser le helper l() (un L minuscule) de cette façon :
<%= l(:text_my_super_plugin) %>Ensuite vous devrez associer cette clé à sa valeur pour chaque langue. Pour le français, le fichier config/locales/fr.yml de votre plugin ressemblera à ça :
fr: text_my_super_plugin: Mon super plugin
Et vous pouvez traduire votre appli en anglais, en ajoutant un fichier config/locales/en.yml contenant :
en: text_my_super_plugin: My great plugin
Pour un texte accentué ou comportant des caractères spéciaux, il suffira de mettre la chaine entre quotes pour éviter toute confusion lors de l’analyse du fichier. Attention à ce que votre fichier reste bien en UTF8 tout de même.
Si la traduction n’existe pas (fichier de langue manquant ou clé inexistante dans la langue de l’utilisateur), Redmine affichera une erreur. C’est la que le helper l_or_humanize peut être utile :
<%= l_or_humanize(:super_plugin) %>Si la clé existe, elle sera remplacée par sa traduction. Si non, Rails tentera d’en faire une chaine pour humain (remplacement des underscores par des espaces, majuscule à la première lettre, etc.). En l’occurrence Super plugin.
Pour les affichages de dates, heures, temps ou intervalles de temps, il existe des helpers beaucoup plus évolués que ceux présentés ci-dessus. Ils sont définis dans lib/redmine/i18n.rb. En voici une liste, ainsi que comment les tester dans une console Rails :
% ruby script/console Loading production environment (Rails 2.3.5) >> include Redmine::I18n => Object >> set_language_if_valid('fr') => :fr >> l_hours(5) => "5.00 heures" >> format_date(Time.now) => "26/04/2010" >> format_time(Time.now) => "26/04/2010 19:55" >> day_name(1) => "lundi" >> month_name(3) => "mars"
A des fins de test, le helper ll() permet de préciser d’abord la locale avant la clé et ainsi de tester une clé dans une locale particulière :
>> ll("fi", :field_mail) => "Sähköposti"
Dernière chose, il est possible d’utiliser des variables dans vos fichiers de langue. Ils seront interpolés lors du rendu de la vue. Si vous n’avez qu’une variable à mettre, vous pouvez utiliser le nom “value” et passer la valeur dans votre vue directement en 2e argument de votre l(). Si vous avez 2 variables ou plus, il faut leur donner un nom et passer un hash en 2e argument de l() dans votre vue. Evidemment ces valeurs peuvent elles-même faire appel à vos traductions pour éviter de dupliquer des traductions.
Un exemple vaut mieux qu’un long discours. Avec ce fichier de langue :
fr: label_draft_saved_time: "Brouillon sauvegardé à {{value}}" label_draft_pending: "Brouillon en attente, sauvegardé il y a {{time}} : {{restore}} ou {{delete}}" label_draft_restore: "restaurer" label_draft_delete: "supprimer"
Je peux faire appel à ceci dans mes vues (les valeurs de temps sont bidon) :
<%= l(:label_draft_saved_time, format_time(Time.now)) %> <%= l(:label_draft_pending, {:time => format_time(Time.now), :restore => l(:label_draft_restore), :delete => l(:label_draft_delete)}) %>
J’essaierai de documenter tout ça en anglais dans le wiki Redmine un de ces 4.