Embauchez-moi


Mon site pro : http://thibault.jouannic.fr

Mes appli Android : http://apps.miximum.fr/

mots-cles : Ingénieur web freelance Python Django Scrum

Dans la série, les offres d’emploi nous font rire

Au hasard de mes pérégrinations sur le web, je tombe sur une sympathique offre d’emploi, dont l’extrait suivant est tiré :

Profil recherché :

  • Savoir gérer son temps efficacement et établir ses priorités pour mener à bien plusieurs projets/demandes simultanés  ;
  • Être capable de travailler rapidement (courts délais) et avoir une bonne résistance au stress  ;
  • […]
  • Travailler dans le souci de la qualité et du détail  ;

Mmm… Je trouve ça assez cocasse.

Dialogue avec un client

Le téléphone sonne, je décroche.

«  Thibault Jouannic, développeur web freelance, j’écoute.

— Bonjour monsieur. Je vous contacte car dans le cadre du lancement de ma startup de location d’arrosoirs en terre cuite, je souhaite réaliser un devis pour un site communautaire.

— Bien entendu. Avez-vous une liste précise des fonctionnalités à développer ? Un cahier des charges détaillé ?

— Pas vraiment. En fait, je ne veux pas trop me lancer avant d’avoir une idée des prix. Je peux vous donner quelques liens vers des sites similaires à ce que je cherche.

— Désolé, mais je ne peux pas vous fournir d’estimation si vous même ne savez pas exactement ce que vous voulez.

— Disons que j’ai un budget, mais je ne sais pas s’il sera suffisant. Je cherche un prestataire qui s’engage à réaliser mon site sur ce budget. J’ai également une deadline assez serrée. C’est un projet sérieux mais urgent.

— Monsieur, dans ce contexte grandement incertain, je ne peux tout simplement pas m’engager à la fois sur un délai et un budget. Non, ce que je peux vous proposer, c’est une méthodologie de travail alternative.

— Je vous écoute.

scrum
Creative Commons License photo credit : darkmatter

— Je vous propose ce qu’on appelle une méthodologie agile. Dans un premier temps, nous allons définir ensemble, de façon assez grossière, toutes les fonctionnalités que devra comporter votre site. Cette liste sera nommée le « backlog de produit ».

— Ça me parait être un bon début. Ensuite ?

— Ensuite, je me chargerai d’estimer grossièrement chaque point, en leur donnant des notes de complexité.

— Jusque là, tout va bien.

— Puis, ce sera votre tour de travailler : vous allez devoir trier le backlog par ordre de priorité. Ainsi, les fonctionnalités au sommet de la pile seront vitales pour votre site, tandis que celles du fond ne seront pas forcément essentielles.

— Attendez, toutes les fonctionnalités de mon site sont essentielles ! Je veux des news, un forum, un blog, une mailing list, une synchronisation twitter, un réseau social, un…

— Hola ! Comme vous y allez ! Soit, mais je vous demanderai toutefois de faire un effort, et de mettre de côté ce qui n’est pas strictement essentiel à la naissance de votre site. Imaginons que je puisse avoir un accident à n’importe quel moment dans la vie du projet. Il y a forcément des points qui seront plus prioritaires à développer que d’autres. Notamment les aspects véritablement métier de votre site.

— Admettons.

— À partir de là, comme toutes les fonctionnalités auront été listées, et évaluées, vous aurez une idée générale du budget nécessaire.

— D’accord, mais quand aurai-je un devis précis ?

That's interesting
Creative Commons License photo credit : kevindooley

— J’y viens. À partir de ce moment, nous commençons à travailler par itérations de deux semaines. Je prends les fonctionnalités du sommet du backlog (les plus importantes, vous vous souvenez ?), et je travaille dessus pendant deux semaines. À la fin de l’itération, elles seront livrées, testées, documentées, et validées. Vous me payez, et nous pouvons embrayer sur une nouvelle itération. Quand vous estimez que les fonctionnalités de la prochaine itération n’en valent plus la peine, vous êtes libre d’arrêter. Vous contrôlez ainsi votre budget avec le maximum de précision.

— Attendez ! C’est bien beau, mais je vais devoir aller voir mon banquier, présenter un business-plan. Comment voulez-vous que j’obtienne des financements sans connaitre le prix exact au départ ?

— Dans ce cas, la variable d’ajustement ne sera pas le budget, qui sera fixé, ce sera le périmètre fonctionnel. Quand votre budget sera épuisé, nous arrêterons de travailler, et comme vous aurez vous-même trié le backlog par priorité, vous aurez la garantie d’obtenir le ROI maximal pour la somme investie.

— Pardon ? Vous voulez dire qu’en partant sur un budget précis, je ne sais pas ce que j’obtiendrai au final ? C’est aberrant !

— Dites-vous bien que même dans un projet au forfait, le résultat final reste incertain. Sur les 6 ou 8 mois du développement, il peut se passer bien des choses : vous aller changer d’avis, demander des modifications, des nouvelles fonctionnalités, etc. C’est courant, voire systématique. Dans un forfait, vous n’avez qu’une seule possiblité : signer un avenant pour une nouvelle fonctionnalité. Au final, votre site vous coûtera plus cher, et sera plus long à sortir, augmentant d’autant le risque d’échec. Saviez-vous que l’impossibilité de se mettre d’accord sur un jeu fixe et fini de fonctionnalités à développer était une des principales causes d’échec des projets de développement ?

— Je l’ignorais.

— Grâce à cette méthodologie de travail, vous gardez à tout moment la possibilité de changer d’avis. Bien entendu, les fonctionnalités qui ont été commencées devront être terminées. On ne modifie pas le contenu d’une itération en cours. En revanche, dans le backlog, vous pouvez effectuer toutes les modifications que vous souhaitez : changer les priorités des tâches, en ajouter, en supprimer, etc. Vous êtes le maître de vos besoins, et de ce qui sera développé. Vous aurez contractuellement et pratiquement la possibilité de changer d’avis en plein milieu du projet. Imaginez que votre budget soit réduit ou rallongé : vous pouvez vous adapter à tout moment.

— Ce que vous me dites est séduisant.

— Oui, c’est une méthode gagnant-gagnant. Dans un projet au forfait, c’est le prestataire qui assume tous les risques, en s’engageant sur tous les paramètres : le périmètre fonctionnel, le délai et le budget. Dés qu’il y a le moindre imprévu (et on sait qu’il y en a toujours), il ne peut que jouer sur la seule variable d’ajustement qui lui reste : la qualité. On n’a pas le temps de réfléchir, on travaille dans l’urgence, et tout ce qui n’est pas essentiel passe à la trappe. Exit, les tests, la documentation, les refactoring. Alors certes, le client obtiendra probablement un produit qui correspond au contrat, mais il ne verra pas la piètre qualité technique sous-jacente.

— Oui, bon, à la limite, tant que le site tourne, où est le problème ?

— Le problème monsieur, c’est qu’un site, comme tout logiciel, nécessite de l’entretien. Un site bien fait, correctement développé, bien documenté, bien testé, sera facile à maintenir. On dit que sa maintenabilité est bonne. Les bugs seront rares, et quand il y en aura, ils seront faciles à corriger. Mais quand le site est développé « à l’arrache », les vices cachés apparaissent rapidement, les performances s’effondrent vite, l’entretien est fastidieux, et chaque correction de bug en génère trois nouveaux. J’aime autant vous le dire : maintenir un site, ça coûte cher. Et maintenir un site inmaintenable, je prèfère vous laisser imaginer.

— Effectivement, vu sous cet angle…

— Et il n’y a pas que l’entretien courant. Viendra un moment ou vous souhaiterez faire évoluer votre site, lui ajouter de nouvelles fonctionnalités. Si le site a été bien pensé et bien développé, pas de problèmes. Mais si la moitié du projet a été réalisé dans l’urgence, vous pouvez être certain que l’architecture technique sera désastreuse. Chaque évolution sera un bricolage, bâtie sur tous les bricolages précédents. Et quand tout finira par s’effondrer inévitablement, vous n’aurez pas le choix : il vous faudra tout reprendre depuis le départ.

— …

Morning
Creative Commons License photo credit : h.koppdelaney

— Vous savez, la situation suivante s’est déjà produite : un client potentiel m’appelle pour un devis. Il le juge trop onéreux, et préfère confier son développement à un étudiant pas cher. 18 mois plus tard, le même client revient, et me demande de réparer son site, qui est une catastrophe innommable. Au final, la réparation coûte plus cher que la refonte, et je recommence le site de zéro. Bilan ? Mon client a perdu 18 mois, et à payé deux fois plus cher que prévu.

— J’entends bien vos arguments, et ils me paraissent raisonnables et cohérents. Mais tout de même, les tarifs que vous m’annoncez sont assez élevés. On ne peut pas faire un petit quelque chose ?

— Monsieur, je ne vais pas vous mentir : oui, m’embaucher coûte cher. En contrepartie, je m’engage à être intransigeant sur le paramètre de la qualité. Dites-vous bien que si vous voulez un site pas cher, vous obtiendrez exactement ça : un site pas cher. Vous trouverez toujours du monde pour vous proposer ce genre de prestation, mais travailler comme ça ne m’intéresse pas. D’un autre côté, si vous voulez quelque chose qui tienne la route, il faudra y mettre le prix, c’est comme ça, on n’a rien sans rien. Vous y gagnerez sur le long terme.

— Ce que vous dites mérite réflexion. Malheureusement, j’ai bien peur de ne pouvoir m’offrir vos services…

— En fait, j’ai bien une proposition pour vous permettre de réduire vos coûts de développement

— Vous m’intéressez ! Dites moi.

Pour réduire vos coûts, réduisez votre périmètre fonctionnel. Cela revient à développer moins, mais mieux.

— Vous voulez dire que mon site sera moins bien ?

— Et non, justement. Sachez qu’en règle général, mieux vaut un site qui fasse peu mais bien, qu’une usine à gaz fournissant des centaines de fonctionnalités inutilisées. C’est une constante dans les projets web : on veut en faire trop. On veut absolument une intégration au dernier réseau social à la mode, ou le dernier gadget web 2.0 dont on a entendu parlé sur le blog du neveu de sa belle-sœur. Si je vous dis qu’en règle générale, 20% des fonctionnalités amènent 80% des bénéfices, je pense ne pas être loin de la vérité.

— Ça me parait un peu exagéré !

Introducing Miss Lullaby !
Creative Commons License photo credit : Bonnaf

— Laissez-moi vous raconter une histoire. Un jour, un client voulait absolument implémenter une fonctionnalité complexe que je jugeais inutile. Il n’en démordait pas, jusqu’à ce qu’arrive à lui prouver que cette fonctionnalité serait utilisée par deux personnes exactement, sur toute sa base client. Dix jours de développement pour un gadget utile à deux clients ? L’idée à été abandonnée.

— Fichtre !

— Comme vous dites. Croyez-moi, sur toutes les «  killer-features  » qui vous viendront à l’esprit, beaucoup pourront être simplifiées voire abandonnées, sans perte de revenu aucune. Et vous pouvez compter sur moi pour vous conseiller au mieux dans la sélection de ce qui est essentiel. Au final, votre site aura un périmètre fonctionnel plus réduit, mais ce qui existera fonctionnera bien, et sera vraiment utile. Le superflu coûte cher, laissons-le de côté.  »

Quelques précisions :

Indexer Wikipédia dans Solr

Drôle d’époque. Après deux jours magiques à ParisWeb, et un retour chez moi dans une ambiance de guerre civile, le retour à la réalité est… difficile.

La reprise du quotidien après un tel événement est toujours une période cafardogène. Pour éviter de sombrer dans la déprime la plus grise, je vous propose de nous fixer un objectif un tantinet ambitieux : et si nous indexions la plus grande base de connaissance au monde dans le meilleur moteur de recherche ? (Si ça ne vous plait pas, vous pouvez plutôt vous abonner au tag #sudweb.)

Chercher dans Wikipédia grâce à Solr ? Si, c’est possible.

Des données, des données, des données…

Wikipédia fournit régulièrement des fichiers de dumps permettant de récupérer toutes les données du site. Nous nous contenterons de récupérer les articles complets (sans les révisions, ni les commentaires), et en français uniquement. Je vous mâche le travail, il n’y a qu’un seul fichier à récupérer.

Configurer le schéma

La documentation de Solr fournit un exemple de schéma pour indexer Wikipedia. Nous allons l’optimiser pour prendre en compte la langue française (je pars ici d’une install vierge de Solr).

Commençons par ajouter un nouveau fieldtype : text_fr, dans le fichier schema.xml.

<fieldtype name="text_fr" class="solr.TextField">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms_fr.txt" ignoreCase="true" expand="true"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords_fr.txt"/>
    <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange=
    <filter class="solr.ISOLatin1AccentFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SnowballPorterFilterFactory" language="French" protected="protwords_fr.txt" />
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms_fr.txt" ignoreCase="true" expand="true"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords_fr.txt"/>
    <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange=
    <filter class="solr.ISOLatin1AccentFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SnowballPorterFilterFactory" language="French" protected="protwords_fr.txt" />
  </analyzer>
</fieldtype>

Vous remarquerez que ce type de champ nécessite trois fichiers pour fonctionner.

  1. synonyms_fr.txt, pour gérer les synonymes dans l’indexation. Exemple :
    aiki => aikido
    resto => restaurant
    
  2. stopwords_fr.txt, contient la liste des mots basiques qui ne doivent pas être indexés  ;
  3. protwords_fr.txt, contient la liste des mots qui ne doivent pas être retraités par le stemmer français  ;

Ensuite, toujours dans le même fichier, configurons nos fields :

<fields>
  <field name="id"        type="string" indexed="true" stored="true" required="true"/>
 
  <field name="title"     type="string"     indexed="false" stored="true"/>
  <field name="search_title"     type="text_fr"     indexed="true" stored="false"/>
  <field name="body"    type="text_fr"    indexed="true" stored="true" termVectors="true"/>
 
  <field name="revision"   type="sint"    indexed="true" stored="true"/>
  <field name="user"        type="string"  indexed="true" stored="true"/>
  <field name="userId"     type="int" indexed="true" stored="true"/>
  <field name="timestamp" type="date"    indexed="true" stored="true"/>
 </fields>
 
 <uniqueKey>id</uniqueKey>
 <defaultSearchField>search_title</defaultSearchField>
 <copyField source="title" dest="search_title"/>

Notez que l’on stocke le corps de l’article. C’est parce que nous allons utiliser les results highlighting. Si vous n’avez pas besoin de cette fonctionnalité, remplacez « stored=true » par « stored=false ».

Et on importe

Nous aurions pu décider de convertir le fichier xml de wikidédia en sql, de l’importer dans une base, et d’utiliser un sql import handler. Mais pourquoi se compliquer la tâche ? Solr peut importer directement du xml. Configurons notre import de données dans le fichier data-config.xml :

<dataConfig>
    <dataSource type="FileDataSource" encoding="UTF-8" />
    <document>
    <entity name="page"
        processor="XPathEntityProcessor"
        stream="true"
        forEach="/mediawiki/page/"
        url="/var/www/solrdemo/dumps/frwiki-latest-pages-articles.xml"
        transformer="RegexTransformer,DateFormatTransformer"
        >
        <field column="id"    xpath="/mediawiki/page/id" />
        <field column="title"     xpath="/mediawiki/page/title" />
        <field column="body"      xpath="/mediawiki/page/revision/text" />
        <field column="revision"  xpath="/mediawiki/page/revision/id" />
        <field column="user"      xpath="/mediawiki/page/revision/contributor/username" />
        <field column="userId"    xpath="/mediawiki/page/revision/contributor/id" />
        <field column="timestamp" xpath="/mediawiki/page/revision/timestamp" dateTimeFormat="yyyy-MM-dd'T'hh:mm:ss'Z'" />
        <field column="$skipDoc"  regex="^(?i)#redirect.*" replaceWith="true" sourceColName="text"/>
       </entity>
    </document>
</dataConfig>

Redémarrez Solr, lancez l’indexation, attendez (une heure ou deux), et paf ! 2400000 document indexés, ce qui vous en conviendrez est plus que suffisant pour s’amuser.

Un peu de result highlighting

Étant donné que nous disposons d’un sacré paquet de texte, autant en profiter un peu, non ? (oui, j’ai des loisirs de geeks). Au hasard, je vous propose de mettre en place un peu de result highlighting[1] .

Bon, alors pour ceux qui aiment bien avoir un retour visuel, ça pourrait ressembler à ça :

Démo Result highlighting

Pour ce faire, nous allons utiliser les paramètres qui vont bien, et que je vous propose ci-dessous sous forme de tableau php (et tant pis pour les pythoneux, faudra s’en contenter[2] ).

$req = array(
  'q' => 'Ma recherche',
  'qt' => 'dismax', // Nous envoyons directement le contenu du formulaire à Solr, par conséquent dismax est plus adapté
  'qf' => 'title^2 body', // On cherche dans title et body, avec une priorité plus importante pour le champ title
  'hl' => 'true', // Activitation de l'highlighting
  'hl.fl' => 'title,body',  // Seuls ces champs seront pris en compte pour la surbrillance
  'f.body.hl.alternateField' => 'body',  // Si aucun snippet n'est trouvé dans le champ body, on renvoie le champ complet
  'hl.fragsize' => 150,  // Les snippets font 150 caractères…
  'hl.snippets' => 3,  // … et on renvoie 3 snippets maxi 
  'hl.maxAlternateFieldLength' => 200,  // Si on renvoie le champ body comple, on limite à 200 caractères
  'hl.simple.pre' => '<strong>',  // On veut que notre surbrillance soit encadrée par des balises 'strong'
  'hl.simple.post' => '</strong>',
));

Si vous exécutez une requête contenant ces paramètres, vous noterez que la réponse est séparée en deux parties : d’un côté, les résultats, et de l’autre, les snippets générés. Vous remarquerez également que les snippets en question sont a peu près illisibles à l’œil nu, mais ça, c’est une autre histoire.

<?xml version="1.0" encoding="UTF-8"?>
<response>
<responseHeader>
  <bla>bla</bla>
</responseHeader>
<result name="response" numFound="50" start="0">
  <doc>
    <str name="id">9</str>
    <int name="revision">2338009</int>
    <date name="timestamp">2002-10-31T09:16:01Z</date>
    <str name="title">Algèbre de boole</str>
  </doc></result>
<lst name="highlighting">
  <lst name="9">
    <arr name="body">
      <str>
L' &lt;em&gt;alg&amp;#232;bre&lt;/em&gt; g&amp;#233;n&amp;#233;rale, ou &lt;em&gt;alg&amp;#232;bre&lt;/em&gt; abstraite, est la branche des math&amp;#233;matiques qui porte principalement sur l'&amp;#233;tude des structures &lt;em&gt;alg&amp;#233;briques&lt;/em&gt; et de leurs relations. L'appellation &lt;em&gt;alg&amp;#232;bre&lt;/em&gt; g&amp;#233;n&amp;#233;rale s'oppose &amp;#224; celle dalg&amp;#232;bre &amp;#233;l&amp;#233;mentaire ; cette derni&amp;#232;re enseigne le calcul &lt;em&gt;alg&amp;#233;brique&lt;/em&gt;, c'est-&amp;#224;-dire les r&amp;#232;gles de manipulation des formules et des expressions &lt;em&gt;alg&amp;#233;briques&lt;/em&gt;.Historiquement, les structures &lt;em&gt;alg&amp;#233;briques&lt;/em&gt; sont apparues dans diff&amp;#233;rents</str>
    </arr>
  </lst>

Parser la réponse sera finalement assez simple. Allez, je vous donne le code que j’utilise (pour les curieux, c’est du twig, mais ce serait pareil avec n’importe quel autre moteur de template).

<section id="results">
  {% block results %}
    <h3>{{ results.numFound }} résultats trouvés</h3>
    <ol>
    {% for doc in results.docs %}
      {% block doc %}
        <li>
          <a href="http://fr.wikipedia.org/wiki/{{ doc.title }}" />{{ doc.title }}</a>
          <p>
          {% for snippet in highlighting[doc.id].body %}
            {{ snippet }}…
          {% endfor %}
          </p>
        </li>
      {% endblock %}
    {% endfor %}
    </ol>
  {% endblock %}
</section>
{% endif %}
</section>

Mais… Mais… Attendez ! C’est tout crade !

Ok, j’avoue, si vous avez vous même testé tout ça jusqu’ici, vous avez remarqué que le résultat n’est pas vraiment exploitable. Le texte que nous indexons, en effet, n’est pas du texte brut : il contient toutes les balises de formattage spécifiques du langage wiki de Wikipédia.

Dans notre cas, nous n’aurons jamais besoin de ces informations aussi l’idéal serait de pouvoir filtrer ces balises dés l’import des données.

Si vous aussi, vous lisez la documentation de Solr en famille le soir au coin du feu[3], vous savez déjà que Solr propose des transformers (rien à voir avec les robots), qui permettent de filtrer les données lors de l’import.

D’ailleurs, si vous regardez le code du fichier data-config.xml que nous avons utilisé, il utilise déjà deux transformers. Tout ce que nous allons faire, c’est créer notre transformer custom. Allez hop ! Un peu de java ! Créez un fichier WikiTransformer.java

import java.util.*;
import java.io.*;
import info.bliki.wiki.filter.PlainTextConverter;
import info.bliki.wiki.model.WikiModel;
 
public class WikiTransformerAlt {
  public Object transformRow(Map<String, Object> row) {
    String body = (String)row.get("body");
 
    StringWriter writer = new StringWriter();
 
    WikiModel wikiModel = new WikiModel("http://www.mywiki.com/wiki/${image}", "http://www.mywiki.com/wiki/${title}");
    String plainStr = wikiModel.render(new PlainTextConverter(), body);
 
    row.put("body", plainStr);
 
    return row;
  }
}

Deux choses à noter : ce transformer n’est pas optimal, le nom du champ à parser (body) est codé en dur. Mais bon, ça ira pour cette fois (mais ne recommencez pas). Deuxième chose, j’utilise la librairie java gwtwiki pour faire le boulot de traduction wiki -> texte brut. Cette librairie est pour le moment lacunaire (ou alors je m’en sert mal), et le résultat ne sera pas parfait. Il faudra faire avec.

La compilation du fichier en .jar et son installation sortent du scope de cet article, aussi je vous laisse utiliser votre moteur de recherche favori… Bon, ok, je vous file les commandes :

javac -cp "/path/vers/bliki/info.bliki.wiki/bliki-core/target/*" WikiTransformer.java
jar -cf WikiTransformer.jar WikiTransformer.class
cp WikiTransformer.jar path/vers/solr/work/Jetty_0_0_0_0_8983_solr.war__solr__k1kf17/webapp/WEB-INF/lib/

Notez le chemin de destination pas trés conventionnel : c’est le seul endroit ou l’autoload semble capter le jar. Si un expert java passe par là et peut m’expliquer…

Il ne nous reste plus qu’à mettre à jour notre fichier de configuration d’import, et à relancer l’indexation :

<dataConfig>
    <dataSource type="FileDataSource" encoding="UTF-8" />
    <document>
    <entity name="page"
        processor="XPathEntityProcessor"
        stream="true"
        forEach="/mediawiki/page/"
        url="/var/www/solrdemo/dumps/frwiki-latest-pages-articles.xml"
        transformer="RegexTransformer,DateFormatTransformer,WikiTransformer"
        >
        <field column="id"    xpath="/mediawiki/page/id" />
        <field column="title"     xpath="/mediawiki/page/title" />
        <field column="body"      xpath="/mediawiki/page/revision/text" />
        <field column="revision"  xpath="/mediawiki/page/revision/id" />
        <field column="user"      xpath="/mediawiki/page/revision/contributor/username" />
        <field column="userId"    xpath="/mediawiki/page/revision/contributor/id" />
        <field column="timestamp" xpath="/mediawiki/page/revision/timestamp" dateTimeFormat="yyyy-MM-dd'T'hh:mm:ss'Z'" />
        <field column="$skipDoc"  regex="^(?i)#redirect.*" replaceWith="true" sourceColName="body"/>
       </entity>
    </document>
</dataConfig>

Et voilà, vous disposez de votre propre moteur de recherche Wikipedia. Les raffinements possibles sont nombreux, je laisse votre imagination vous guider. En attendant, rendez-vous au prochain SudWeb.

Notes :

  1. Pour les francophones acharnés, surbrillance des résultats, mais c’est moche [retour]
  2. Notez comme s’extériorise ma haine et ma jalousie envers les gens qui ont la chance de travailler avec un vrai langage de programmation [retour]
  3. Allez, avouez, je sais que vous le faites [retour]

Autocomplete avec Solr

Voilà la reprise, les premiers froids, les feuilles qui tombent, la nostalgie des vacances, etc. Mais la rentrée est également pour moi l’opportunité de dépoussiérer un peu mon cerveau et ce blog, avec du temps consacré à l’indispensable… Veille techno. Parce que la veille, c’est comme le sport. Quand on ne pratique pas, on s’encrasse vite. Surtout quand on est freelance.

Bref, dans cet article, nous allons utiliser Solr, excellent moteur de recherche dont je vous ai déjà parlé, et implémenter l’autocomplétion de la recherche.

Solr pour l’autocomplétion, ou sortir la Grosse Bertha pour tuer une mite

Les plus attentifs d’entre vous se demanderont sans doute pourquoi utiliser Solr, puissant moteur de recherche full-text en java, pour une bête autocomplétion. Et effectivement, l’outil serait disproportionné si nous n’étions pas dans un des cas suivants :

  1. Vous utilisez déjà Solr pour votre moteur de recherche. Dans ce cas, l’utiliser pour l’autocomplétion ne rajoutera pas une grosse charge  ;
  2. Vous souhaitez mettre en place une autocomplétion un peu avancée, avec séparation par facettes, etc  ;

Solr, depuis la version 1.4, propose un nouveau module de recherche : Terms Component. Alors que Solr est généralement utilisé pour effectuer des recherches au niveau d’un document, ce module permet d’accéder au infos au niveau des termes présents dans les champs, permettant de répondre à la requête : « Quels sont les termes présent dans tel(s) champ(s), et à combien de document correspondent-ils ? ».

Par ailleurs, notez que nous allons créer une autocomplétion, et pas une autosuggestion. Pour bien cerner la différence, je vous recommande la lecture de cet excellent article sur les différents types d’assistance à la recherche.

autocomplete

Pour être précis, nous allons construire ça. Ceux qui lisent via un lecteur rss et qui n’auraient pas l’image, vous n’avez qu’à aller sur le site.

Pour les autres, vous remarquerez un bête champ de recherche tout ce qu’il y a de plus classique. L’autocomplétion, en revanche, est un tantinet évoluée, puisqu’elle s’effectue sur deux champs différents. Vous êtes prêts ? C’est parti !

Choisir les bons outils

À l’heure ou j’écris, la version stable de Solr est la 1.4.1. Elle présente toutefois un bug dans le formatage json des résultats. Nous utiliserons donc Solr dans sa version nightly build.

L’autocomplete sera assuré par jQuery, librairie javascript que l’on ne présente plus, et par son trés bon complément jQuery UI.

Notez l’existance d’une trés complète (mais assez complexe) librairie ajax pour Solr. Elle constitue pratiquement un framework à elle toute seule, et pour le coup, nous la laisserons de côté.

Le code ! Le code !

Commençons par le HTML. Ici, rien que du classique.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
  <head>
    <script type="text/javascript" src="./js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="./js/jquery-ui-1.8.5.custom.min.js"></script>
    <script type="text/javascript" src="./js/solr.js"></script>
 
    <link rel="stylesheet" type="text/css" media="screen" href="./css/ui-lightness/jquery-ui-1.8.5.custom.css" />
 
  </head>
  <body>
    <form action="" method="get">
      <input type="text" id="search" name="q" />
      <input type="submit" value="Search" />
    </form>
 
    <div id="result">
      <p>Results go here</p>
    </div>
  </body>
</html>

Un banal champ texte, l’inclusion des scripts nécessaires, et c’est tout. Circulez, ya rien à voir !

La configuration Solr n’est pas trés intéressante non plus. J’ai pris la première install de Solr qui m’est passée sous la main. Vous pouvez vous réferer au schema.xml d’un précédent article.

Le seul fichier vraiment intéressant est le javascript :

$(document).ready(function() {
  $('#search').autocomplete({
    source: function(request, response) {
      $.ajax({
        url: 'http://localhost:8983/solr/terms?terms.fl=name',
        dataType: 'jsonp',
        data: {
          wt: 'json',
          'json.nl': 'arrarr',
          'terms.prefix': request.term,
          'terms.sort': 'index',
          'terms.limit': 5,
          omitHeader: 'true'
        },
        jsonp: 'json.wrf',
        success: function(data) {
          response($.map(data.terms.name, function(item) {
            return {
              label: item[0] + ' (' + item[1] + ')',
              value: item[1]
            };
          }));
        }
      })
    }
  });
});

Quelques explications. Dans l’url utilisée par le widget d’autocomplétion, terms.fl désigne le champ qui sera utilisé pour la requête (ici : « name »).

Notez les options « datatype : jsonp », « wt : json », « json.nl : arrarr », « jsonp : wrf ». Elles permettent de faire fonctionner l’appel ajax en cross-domain (via jsonp au lieur de json), et d’obtenir un formatage correct du résultat de la part de Solr.

Enfin, dans le callback « result », nous formattons le résultat en utilisant la fonction $.map de jquery.

Nous avons maintenant un widget de recherche qui réalise une autocomplétion sur le champ « name ». C’est pas mal, mais nous souhaitions une autocomplétion à facette sur différents champs.

L’autocomplétion à facette

Pour ce faire, nous allons modifier deux choses. D’abord, il va falloir définir un template de widget spécifique pour afficher le champ recherché. Ensuite, la fonction qui parse le résultat renvoyé par Solr va devoir prendre en compte les différents champs.

$(document).ready(function() {
 
  // Widget autocomplete spécifique
  // cf. http://jqueryui.com/demos/autocomplete/#categories
  $.widget( "custom.catcomplete", $.ui.autocomplete, {
    _renderMenu: function( ul, items ) {
      var self = this,
        currentCategory = "";
      $.each( items, function( index, item ) {
        if ( item.category != currentCategory ) {
          ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
          currentCategory = item.category;
        }
        self._renderItem( ul, item );
      });
    }
  });
 
  $('#search').catcomplete({
    source: function(request, response) {
      $.ajax({
        url: 'http://localhost:8983/solr/terms?terms.fl=name&terms.fl=tag_fr',
        dataType: 'jsonp',
        data: {
          wt: 'json',
          'json.nl': 'arrarr',
          'terms.prefix': request.term,
          'terms.sort': 'index',
          'terms.limit': 5,
          omitHeader: 'true'
        },
        jsonp: 'json.wrf',
        success: function(data) {
 
          answer = new Array();
 
          $.each(data.terms, function(facet, terms) {
            answer = answer.concat($.map(terms, function(item) {
              return {
                label: item[0] + ' (' + item[1] + ')',
                value: item[1],
                category: facet
              };
            }));
          });
          response(answer);
        }
      })
    }
  });
});

Notez l’url utilisée dans le widget autocomplete : il y a maintenant deux options « terms.fl », mais il pourrait y en avoir beaucoup plus.

La fonction d’analyse des résultats est un peu plus complexe. Pour chaque champ renvoyé par solr, elle construit un tableau de termes, en y adjoignant la catégorie, c’est à dire le champ courant. Tous les tableaux sont ensuite concaténés, en renvoyés en réponse.

La catégorie est utilisée par le template de rendu du widget, pour être affichée dans le menu d’autocomplétion.

Dans nos styles, il faudra ajouter ceci :

.ui-autocomplete-category {
  font-weight: bold;
  padding: .2em .4em;
  margin: .8em 0 .2em;
  line-height: 1.5;
}

Et voilà ! Un widget d’autocomplétion un poil moins que basique. Partant de cette base, je vous laisse imaginer les améliorations les plus raffinées. Si vous avez des idées de démo, je suis preneur.

– Edit –

J’ai oublié de mentionner le problème de la sécurité. Bien entendu, c’est une trés mauvaise idée de laisser Solr en accès libre depuis l’extérieur. Donc ou bien vous créez un proxy, ou bien vous créez un requesthandler spécifique en lecture seule. C’est tout.

Montpellier web : parlons-en !

Salut tout le monde,

Suite au premier post sur Montpellier web, quelques mesures concrètes ont été mises en place.

  • La mise en place d’un groupe de discussion, histoire de permettre aux bonnes volontés d’échanger ailleurs que via twitter.
  • Mise en place d’un blog, pour centraliser les actus et infos sur l’événement. (La propagation dns est en cours, vous n’y aurez peut-être pas accès immédiatement)

Premier débat : décider du nom (mtpweb vs sudweb). Le débat penche assez fortement vers mtpweb (d’où les noms de domaines choisis), j’ai toutefois créé un premier sujet de discussion, afin de permettre à tout le monde de s’exprimer correctement.

Avis à tous les gens qui avaient l’air motivés : la discussion précède l’action. Donnez votre avis.

Pour un Montpellier web en 2011 ?

Ni queue - Ni tête
Creative Commons License photo credit : littlepois

Suite au dernier sfPot Montpellierain, qui s’est déroulé entre autre en compagnie d’honorables membres du non moins honorable regroupement particul.es, l’idée a été lancée d’organiser un Montpellier web, pendant sudiste au désormais fameux Paris-web.

À voir la tempète de tweets qui s’ensuivit, il semble que l’idée soulève bel et bien l’enthousiasme des foules. En fait, on dirait bien que l’idée couvait dans pas mal de têtes, mais sans avoir réellement germé, par manque d’enthousiasme, de motivation, ou tout bêtement de temps.

En vrac, quelques pistes de motivations qui justifieraient d’organiser un tel événement :

  • Permettre au sud le la France de se doter de son propre événement, et par là même, contribuer à donner une visibilité aux professionnels du web de la région (et oui, il y a des gens qui travaillent en province)  ;
  • Paris-web, c’est à Paris :) . Montpellier, c’est quand même mieux pour un rendez-vous sur la plage après une journée de conférence  ;

L’idée n’est donc pas de concurrencer Paris-web, mais au contraire d’en être complémentaire. Autant que possible, il me semble intéressant de faire intervenir en priorité des gens de la région et des alentours, les compétences ne manquent pas. Le projet pourrait donc intéresser du monde, et recruter des bonnes volontés pour participer à l’organisation semble possible.

Histoire de lancer quelques pistes de réflexions, voici une liste d’idées de trucs à faire / à penser en vrac :

  • Lancer un appel aux bonnes volontés, et créer une équipe d’organisation. Définir des dates de réunions (autour de bonnes pizzas / bières, pour joindre l’utile à l’agréable), parce qu’en twitter, c’est bien, mais ce voir, c’est quand même mieux ;
  • Définir le format de l’évenement. Un jour ? Deux jours ? Dans l’idéal, pour une première édition, je verrai bien s’étaler sur deux jours des conférences, avec des workshops en parallèle, et un espace stands pour les sponsors.
  • Définir la date. Le printemps me semble une bonne date, pour faire un pendant aux conférence sur Paris en automne. En plus, Montpellier au printemps, qu’est-ce que c’est bien !
  • Définir la philosophie de l’événement. Quels sujets privilégier ? Qui inviter ? À qui veut-on s’adresser ? Comment se démarquer des événements similaires existants ?
  • Définir les modalités d’inscription. Est-ce un événement gratuit ? Payant ? Cher ? Pas cher ? Il me semble important d’en faire un événement accessible au plus grand nombre, et par conséquent de limiter le tarif au minimum nécessaire pour ne pas en être de notre poche.
  • Trouver un lieu. Voir avec les universités, la ville ou l’agglo si elles peuvent prêter un espace approprié.
  • Trouver des sponsors qui cautionneraient et financeraient l’événement. Définir les modalités d’adhésion des sponsors. Décider à quelle contrepartie leur sponsoring leur donne droit (stands ? Logos sur les affiches ?)
  • Définir et créer une structure juridique (association ?) pour servir d’interface avec les diverses et inévitables administrations  ;
  • Lancer un appel aux conférenciers, recruter les intervenants, définir le programme  ;
  • Démarcher les hôtels des alentours pour proposer des formules d’hébergements attractives aux visiteurs. S’organiser pour héberger chez l’habitant les conférenciers (couch-surfing power) ?
  • Mettre en place le site, avec backend et outils de communication pour les GO (gentils organisateurs). Montpellier-web.fr est déjà pris. À tout hasard, j’ai réservé mtp-web.fr :)
  • Créer un visuel pour des affiches et des tracts. S’occuper de la com’ de l’événement. Contacter la presse ;
  • Trouver une formule pour la bouffe lors de l’hébergement. Prévoir un buffet ? Un apéro ? Démarcher les restos aux alentours pour proposer des formules attractives ?

Je dresse une liste de tout ce à quoi je pense, en me projettant volontairement assez loin dans l’avenir. L’idée étant de reccueillir les premiers avis, lancer les discussions, et peut-être établir une liste des premières bonnes volontés intéressées à donner de leur temps pour participer à l’organisation.

Si vous avez des avis utiles, n’hésitez pas à les balancer en commentaire, où à les tweeter assortis du tag #mtpweb. Sur ce, bonne fin de week-end à tout le monde.