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

2 réflexions au sujet de “RequestT­ra­cker : telle­ment puis­sant”

  1. Bonjour,

    Je suis heureux d’être tombé sur cet article que m’a fait découvrir RT.
    Cela mériterait bien une séance (ou plusieurs) de présentation par un LUG !

  2. Puissant mais long à paramétrer et prendre en main.
    C’est marrant, j’en étais sûr pour le NB, je l’attendais tout le long de l’article. 😀

Les commentaires sont fermés.