RequestT­ra­cker : telle­ment puis­sant

Disclai­mer : J’ai reco­pié cet article sur mon wiki, histoire qu’il soit plus facile de le mettre à jour. Je vous encou­rage donc à aller sur https://wiki.fiat-tux.fr/admin:logi­ciels:request­tra­cker pour avoir la dernière version en date 🙂

RequestT­ra­cker (RT) est un outil de tickets extrê­me­ment puis­sant et flexible. Telle­ment flexible qu’on en vient à se prendre la tête pour faire des trucs de oufs.

Je suis en train d’en mettre un en place pour Frama­soft depuis quelques semaines. Je mets long­temps à le mettre en place pour plusieurs raisons :

  • j’ai d’autres projets à fouet­ter
  • il a fallu que je fasse des aller/retours avec Pouhiou pour cerner au mieux les besoins de notre équipe de support
  • je fais faire des trucs rigo­los à RT qui sont assez galère à mettre en place

Je vais faire un petit tour des trucs que j’ai mis en place sur un RT 4.4.0.

Utili­ser le plus addres­sing

De façon simple, pour que RT traite les tickets qui arrivent sur l’adresse email dédiée, on la met dans le /etc/aliases de sa machine. Ça fait un truc comme ça :

rt:         "|/opt/rt4/bin/rt-mailgate --queue general --action correspond --url https://rt.example.org/"
rt-comment: "|/opt/rt4/bin/rt-mailgate --queue general --action comment --url https://rt.example.org/"

Vous note­rez que cela met les mails à desti­na­tion de cette adresse dans la queue (ou file) general. Or on utilise géné­ra­le­ment plus d’une queue dans un système de ticket : cela permet de diri­ger auto­ma­tique­ment vers les personnes les plus à même d’y répondre.

Le problème avec ce système, c’est qu’il faudrait ajou­ter une adresse dédiée à chaque fois que l’on crée une nouvelle file. Ça peut vite deve­nir usant.

On va donc utili­ser le plus addres­sing. Cette tech­nique, toute bête, consiste à ajou­ter un discri­mi­nant à une adresse mail, précédé géné­ra­le­ment d’un + (mais on peut confi­gu­rer son serveur de mail pour utili­ser n’im­porte quel carac­tère). rt@example.org aura alors pour alias (par exemple) rt+file_bidule@example.org.

Pour que RT puisse utili­ser cette tech­nique, il faut ajou­ter --extension=queue dans la commande du /etc/aliases :

rt:         "|/opt/rt4/bin/rt-mailgate --extension=queue --queue general --action correspond --url https://rt.example.org/"
rt-comment: "|/opt/rt4/bin/rt-mailgate --extension=queue --queue general --action comment --url https://rt.example.org/"

Voilà ! Il ne vous reste plus qu’à créer vos files via l’in­ter­face web. Atten­tion, créez-les avec un nom qui passera dans une adresse mail. Pas d’es­pace par exemple.

RT récu­pé­rera le nom de la file kiva­bien dans la partie entre le + et le @ et placera auto­ma­tique­ment le ticket dans cette file, tout en gardant la file general par défaut.

Les articles

Quoi de plus casse-pieds que de se répé­ter encore et encore en donnant toujours la même réponse ? Heureu­se­ment il y a les articles qui peuvent vous servir de réponses pré-enre­gis­trées đŸ™‚

Créa­tion des classes et des articles

Allez dans le menu Administration > Articles > Classes > Ajouter, créez vos classes (j’en ai créé une par file, n’ayant pas réussi à assi­gner auto­ma­tique­ment des articles aux files), cochez Tous les articles de cette classe doivent être disponibles sous forme de liste sur la page de réponse d'un ticket, déco­chez Inclure le résumé de l'article et Inclure le nom de l'article et cochez Include le champs personnalisé 'Content' > Value (oh la belle typo de traduc­tion) qui appa­raî­tra après avoir enre­gis­tré la classe (pour ces trois derniers, vous faites comme vous le sentez hein).

Liez la classe à une file via le menu S'applique à.

Voilà, vous n’avez plus qu’à créer vos articles dans la classe que vous venez de créer via le menu Articles > Ajouter.

Et là, magie, lorsque vous répon­dez via l’in­ter­face web, vous pour­rez choi­sir une réponse pré-enre­gis­trée.

Place­ment des articles dans la réponse

Je suis un grand fan du bottom-post­ing, mais RT place l’ar­ticle au-dessus de la cita­tion du message précé­dent. Remé­dions à cela.

cd /opt/rt4
mkdir -p local/html/Elements/
cp share/html/Elements/MessageBox local/html/Elements/
vi local/html/Elements/MessageBox

Cher­chez la ligne conte­nant

% $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle;

et rempla­cez-la par

% if ($IncludeArticle) {
%    my $article = $m->scomp('/Articles/Elements/IncludeArticle', %ARGS);
%    $article    =~ s{\n}{<br />}g;
%    $article    = RT::Interface::Email::ConvertHTMLToText($article);
%    $Default   .= $article unless ($Default =~ s/(.*)(-- .*)/$1$article$2/m);
% }

Hop ! votre article se trouve main­te­nant entre la cita­tion et votre signa­ture đŸ™‚

(un redé­mar­rage de RT est peut-être néces­saire pour que cela soit pris en compte)

Ajout des articles perti­nents dans le mail de noti­fi­ca­tion d’un nouveau message

Une des forces de RT est de permettre aux inter­ve­nants de répondre aux tickets par mail. Le problème est que cela empêche de piocher dans les réponses pré-enre­gis­trées.

Qu’à cela ne tienne, ajou­tons-les au mail de noti­fi­ca­tion envoyé aux membres du support.

Allez dans Administration > Global > Modèles > Choisir. Il faut modi­fier le modèle Notification de modification HTML (oui, j’ai traduit le nom de mes modèles, mais il est simple à repé­rer, il est utilisé par les scrips 8 et 11).

Ajou­tez ceci en bas du modèle :

{ my $hotlist = RT::Articles->new( RT->SystemUser );
  $hotlist->LimitHotlistClasses;
  $hotlist->LimitAppliedClasses( Queue => $Ticket->QueueObj );
  my $content   = "-- \n<p><b>Réponses pré-enregistrées pour cette catégorie de tickets:</b></p>";

  if ($hotlist->Count) {
    while (my $article = $hotlist->Next) {
      $content .= '<p><b>'.$article->Name.'</b><br/>';
      my $class   = $article->ClassObj;
      my $cfs     = $class->ArticleCustomFields;
      my %include = (Name => 1, Summary => 1);
      $include{"CF-Title-".$_->Id} = $include{"CF-Value-".$_->Id} = 1 while $_ = $cfs->Next;
      $include{$_} = not $class->FirstAttribute("Skip-$_") for keys %include;

      while (my $cf = $cfs->Next) {
        next unless $include{"CF-Title-".$cf->Id} or $include{"CF-Value-".$cf->Id};
        my $values = $article->CustomFieldValues($cf->Id);
        if ($values->Count == 1) {
          my $value = $values->First;
          if ($value && $include{"CF-Value-".$cf->Id}) {
            $content .= '<br/>';
            my $c     = $value->Content || $value->LargeContent;
            $c =~ s/\r?\n/<br\/>/g;
            $content .= $c;
          }
        } else {
          my $val = $values->Next;
          if ($val && $include{"CF-Value-".$cf->Id}) {
            $content .= '<br/>';
            my $c     = $value->Content || $value->LargeContent;
            $c =~ s/\r?\n/<br\/>/g;
            $content .= $c;
          }
          while ($val = $values->Next) {
            if ($include{"CF-Value-".$cf->Id}) {
              $content .= '<br/>';
              my $c     = $value->Content || $value->LargeContent;
              $c =~ s/\r?\n/<br\/>/g;
              $content .= $c;
            }
          }
        }
      }
      $content .= "<br/>-----------</p>\n";
    }
  }
  $content;
}
{$content}

C’est moche et long, je sais. Dites-vous que j’ai passé plus d’une après-midi pour trou­ver ça, la docu­men­ta­tion est inexis­tante pour faire ça.

Les inter­ve­nants n’au­ront plus qu’à copier-coller l’ar­ticle qui se trouve au bas de leur mail de noti­fi­ca­tion dans leur réponse đŸ™‚

Commandes par mail

C’est beau de répondre par mail, mais il faut encore se connec­ter à l’in­ter­face web pour effec­tuer certaines actions. Comme je suis fier d’être fainéant, j’ai créé un scrip pour auto­ri­ser certaines actions par mail.

Mais avant ça, préci­sions :

  • RT permet aux inter­ve­nants de discu­ter du ticket sans que cela soit vu par le créa­teur du ticket : c’est le but de l’adresse rt-comment@example.org du début de l’ar­ticle. On va utili­ser cette adresse pour pilo­ter RT par mail
  • un scrip est une action effec­tuée par RT en réponse à un évène­ment, en utili­sant de façon option­nelle un modèle. Typique­ment, il y a un scrip qui envoie (action) un mail (d’après un modèle) aux membres du support lorsqu’un ticket est créé (évène­ment).

Créons donc un scrip. Menu Administration > Scrips > Ajouter.

  • Condi­tion (évène­ment) => Lors d’un commen­taire
  • Action => défi­nie par l’uti­li­sa­teur
  • Modèle => Modèle vide

Dans le Programme de préparation d'action personnalisé: :

if ($self->TransactionObj->Attachments->First->Content =~ m/#(JePrends|Fermeture|Spam|Move:.*)/i) {
    return 1;
} else {
    return 0;
}

Oui, j’au­rais pu faire un one-liner, mais il faut que ça reste lisible faci­le­ment, et quand on passe des heures à faire des bidouilles comme ça, on appré­cie les codes lisibles en un coup d’œil.

Dans le Code d'action personnalisée (commit): :

if ($self->TransactionObj->Attachments->First->Content =~ m/#JePrends/i) {
    if ( $self->TicketObj->OwnerAsString eq '' ) {
        my $id = $self->TransactionObj->Creator;
        $RT::Logger->info("Setting owner to ".$id);
        $self->TicketObj->SetOwner($id, 'SET');
    }
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Fermeture/i) {
    $RT::Logger->info("Closing ticket");
    $self->TicketObj->SetStatus('resolved');
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Spam/i) {
    my $ticket = $self->TicketObj;
    my ($status, $msg) = $ticket->SetStatus('rejected');
    $RT::Logger->error("Couldn't delete ticket: $msg") unless $status;

    my $requestors = $ticket->Requestor->UserMembersObj;
    while (my $requestor = $requestors->Next) {
        $requestor->SetDisabled(1);
        $RT::Logger->info("Disabling user ".$requestor->Format." because he's likely a spammer");
    }
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Move:(.*)/i) {
    my $new_queue = $1;
    my $ticket    = $self->TicketObj;

    my ($result, $msg) = $ticket->SetQueue($new_queue);
    if ($result) {
        $RT::Logger->info("Moving ticket to queue ".$new_queue);
    } else {
        $RT::Logger->error("Error while moving ticket to queue ".$new_queue.": ".$msg);
    }
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Merge:(.*)/i) {
    my $target = $1;
    my $ticket = $self->TicketObj;

    $ticket->MergeInto($target);
    $RT::Logger->info("Merging ticket into ticket ".$target);
}

return 1;

Voilà, enre­gis­trez et c’est bon.

Lorsqu’un commen­taire contien­dra une commande, elle sera exécu­tée :

  • #JePrends => l’in­ter­ve­nant s’as­signe le ticket
  • #Fermeture => le ticket est marqué comme résolu
  • #Spam => le ticket est supprimé et son auteur ne pourra plus ouvrir de tickets, son adresse mail sera black­lis­tée
  • #Move:file_de_tickets => le ticket est basculé vers la file de tickets spéci­fiée
  • #Merge:no_ticket => fusionne le ticket avec le ticket dont on donne le n°

Et le spam alors ?

Pour le spam, prépa­rez d’abord un spamassassin pour votre serveur de mails. Ce n’est pas l’objet de cet article, il n’y a qu’à fouiller un peu le web pour trou­ver des tutos.

On va recréer un scrip, mais avant cela on va créer une nouvelle file nommée spam (menu Administration > Files > Ajouter).

Pour notre nouveau scrip :

  • Condi­tion (évène­ment) => Lors d’une créa­tion
  • Action => défi­nie par l’uti­li­sa­teur
  • Modèle => Modèle vide

Dans le Programme de préparation d'action personnalisé: :

if ( $self->TicketObj->Subject !~ /\[ .* \]/i ) {
  my $inMessage = $self->TransactionObj->Attachments->First;

  # if no message attachment - assume web UI
  return 0 if (!$inMessage);

  # exit if not email message
  return 0 if (!$inMessage->GetHeader('Received'));

  return ($inMessage->GetHeader('X-Spam-Level') =~ m/\*+/) ? 1 : 0;
} else {
  return 1;
}

Dans le Code d'action personnalisée (commit): :

my $spamlevel = $self->TransactionObj->Attachments->First->GetHeader('X-Spam-Level');
if ($spamlevel =~ m/\*\*\*+/) {
  if ($spamlevel =~ m/\*\*\*\*\*/) {
    $RT::Logger->info("This mail seems to be a spam => deleting");
    $self->TicketObj->Delete();
  } else {
    $RT::Logger->info("This mail seems to be a spam => queue spam");
    $self->TicketObj->SetQueue('spam');
  }
}
return 1;

Avec cela, les mails ayant un score de 5 ou plus au spamLe­vel seront suppri­més, et ceux qui ont entre 3 et 5 vont au purga­toire, dans la file spam.

Prenez soin de dépla­cer ce scrip tout en haut de la liste pour qu’il soit le premier exécuté.

Plugins

En vrac, les plugins que j’uti­lise :

Les deux premiers sont main­te­nant inté­grés à RT, il n’y a pas besoin de les instal­ler, juste de les confi­gu­rer. Ils servent respec­ti­ve­ment à assu­rer l’au­then­ti­fi­ca­tion LDAP à l’in­ter­face web, et à impor­ter en masse les comptes du LDAP pour permettre à l’ad­mi­nis­tra­teur de mettre les colla­bo­ra­teurs dans les bons groupes sans attendre qu’ils se soient logués une première fois.

Le dernier plugin ajoute un S dans le menu des tickets, permet­tant de les décla­rer comme spam d’un simple clic.

Conclu­sion

On peut faire de merveilleuses choses avec RT, pour peu que l’on ait le temps de fouiller dans la docu­men­ta­tion (offi­cielle ou non)… et dans le code !

Une fois bien confi­guré, il devrait permettre d’al­lé­ger la charge de travail du groupe de support et je peux vous dire que pour en faire depuis plus de deux ans pour Frama­soft et bien plus pour mon ancien boulot, ce n’est pas quelque chose à négli­ger đŸ™‚

NB : bien évidem­ment, ce superbe logi­ciel est en Perl đŸ˜€

EDIT 8 janvier 2019 : ajout de la commande #Move: EDIT 10 janvier 2019 : ajout de la commande #Merge:

Crédits de la photo d’illus­tra­tion : CC-BY GotC­re­dit, trou­vée sur Flickr

#WeMa­keSei­tan

J’es­saye de manger moins de viande.

Pour une raison fort simple : l’éco­lo­gie. Le bétail consomme de l’eau, pète des gaz à effet de serre, néces­site géné­ra­le­ment la culture de céréales et autre four­rage, etc (voir par là). Tout ça pour un rende­ment éner­gé­tique (pour l’homme) moindre que si on mangeait direc­te­ment l’équi­valent de la nour­ri­ture des bêtes.

Bref, c’pas top. Et puis entre les salo­pe­ries chimiques qu’on balance sur les champs et les gavages aux médi­ca­ments des bestiaux, ben c’est pas trop la fête.

Manger unique­ment des végé­taux, ça enlève déjà la dose d’an­ti­bio­tiques qu’on ingère via la viande.

Reste les pesti­cides et autres cochon­ne­ries sur les végé­taux. Bon, là faut manger bio, et encore : « la plupart des fermes biolo­giques évitent en grande partie les pesti­cides de synthèse » (source : Wiki­pé­dia).

Même si on n’évite pas toutes les salo­pe­ries possibles en cessant de manger de la viande, réduire leur nombre dans notre alimen­ta­tion ne peut être que béné­fique, non ?

Bon. Du coup, j’es­saye de manger le moins de viande possible. Je n’em­bête cepen­dant pas mes amis et ma famille quand je vais manger chez eux, et j’adore toujours autant la viande, mais chez moi, j’évite.

Ce n’est parfois pas facile, tant je suis habi­tué à ma pièce de viande dans l’as­siette. Le plus simple est donc de trou­ver un substi­tut.

Du coup j’ai d’abord pensé au tofu. On en trouve main­te­nant même dans les super­mar­chés. Résul­tat mitigé. Soit je suis infoutu de le prépa­rer comme il faut, soit c’est vrai­ment fade avec une texture bizar­roïde.

Et puis au détour d’une conver­sa­tion avec Bram, j’ai (re)décou­vert l’exis­tence du seitan (redé­cou­vert parce que j’en avait déjà entendu parler il y a long­temps) et surtout décou­vert à quel point c’est simple d’en faire et adap­table à ses envies : du gluten, tu mélanges avec ce que tu veux et du liquide (géné­ra­le­ment du bouillon de légumes). Pouf, voilà.

En plus, géné­ra­le­ment, le gluten qu’on trouve est bio, donc c’est tout bénéf.

Ma recette (mais y en a plein d’autres sur les Inter­netz)

(les doses sont à la louche, je suis plutôt instinc­tif quand je fais à manger)

  • du gluten (moi j’ai trouvé celui-ci chez BioCoop)
  • une boîte de hari­cots rouges en conserve (poids total 400g, poids égoutté 250g)
  • de l’huile d’olive
  • du jus de citron
  • de la sauce Worces­ter­shire
  • du cumin
  • du sel

Pour la suite, on aura besoin d’un bouillon de légumes, on verra ça après.

  • Réchauf­fer les hari­cots rouges avec le jus dans une casse­role
  • Verser les hari­cots rouge avec le jus dans un sala­dier
  • Écra­ser les hari­cots avec une four­chette (ou ce que vous voulez, j’ai pris ce que j’avais chez moi, le presse-purée ou le mixer seront sans doute plus effi­caces)
  • Mettre deux gros schlouks d’huile d’olive
  • Deux gros schlouks de jus de citron aussi
  • Mettre une bonne dose de sauce Worces­ter­shire
  • Bien mélan­ger
  • Goûter pour voir si on sent bien la sauce Worces­ter­shire, en rajou­ter au besoin
  • Balan­cer du cumin et du sel selon ses goûts (ne pas hési­ter à regoû­ter)
  • Re-bien mélan­ger
  • Mettre du gluten en remuant bien. Quand il n’est plus possible de remuer avec la four­chette, pétrir le tout comme de la pâte à pain
  • Le mélange final doit avoir une consis­tance un peu comme de la pâte à mode­ler, le modèle qui rebon­dit un peu

Ensuite, il faut faire des boules avec votre pâte, genre de la taille d’un poing. Cela fera une portion pour une ou deux personnes.

seitan

C’est là qu’on en vient au bouillon de légumes évoqué plus tôt. On va faire cuire les boules de seitan dans ce bouillon de légumes pendant ± 45 minutes. Il est possible que votre boule se délite un peu dans le bouillon, mais ce n’est pas grave : on conser­vera le bouillon qui reste au congé­la­teur pour la prochaine four­née de seitan đŸ™‚

Une fois les boules cuites, on les égoutte, et on les congèle pour quand on voudra les utili­ser (ou pas, si vous souhai­ter les manger rapi­de­ment (je dirais quelques jours, mais disons le lende­main au plus tard pour plus de sûreté)).

Comment servir son seitan ?

Person­nel­le­ment, je découpe mes boules en tranches pas trop épaisses et je les fais reve­nir à la poêle avec de l’huile d’olive. D’autres personnes l’uti­lisent comme n’im­porte quelle pièce de viande : en bœuf bour­gui­gnon, dans une soupe, etc.

Moi je mange les tranches en accom­pa­gne­ment, comme de la viande, ou alors je m’en fais un sand­wich (j’adore :D).

Bref, le seitan, c’est bon, c’est facile à faire, vous en faites au goût que vous aimez et ça remplace quand même bien la viande. Pour ce qui est du prix, 500g de gluten de blé revienne à plus ou moins 6€, ce qui n’est pas très cher, sachant que je fais envi­ron 9 boules de seitan avec (donc 9 repas), soit ± 66 centimes de gluten pour une boule. Compa­rez au prix de la viande ! (alors oui, je rajoute une boîte de hari­cots, mais on n’est pas obligé hein !)

Ce chat mange un sandwich vegan :P
Ce chat mange un sand­wich vegan đŸ™‚

Crédit de la photo du chat vegan : Ariel Ophe­lia, CC-BY-NC-ND

EDIT on me souffle dans l’oreillette une recette libre de seitan : http://www.cuisine-libre.fr/seitan?lang=fr