Powered by AppSignal & Oban Pro

Un outil interne en trente lignes

Tips/outil_interne_livebook.livemd

Retour vers le sommaire des tips Accueil

Un outil interne en trente lignes

Mix.install([
  {:kino, "~> 0.19"},
  {:req, "~> 0.5"}
])

Le problème

On écrit tous, un jour, un petit script pour interroger une API ou une base : dix lignes dans iex, un IO.inspect, on lit la réponse, on ferme le terminal. Le lendemain, il faut le réécrire. Le collègue à qui on voudrait le passer, lui, n'a rien.

Ce notebook fait la même chose, mais il se garde, se partage et s'utilise sans lire une ligne de code.

Étape 1 — Voir quelque chose de lisible

Interrogeons l'API publique de Hex. Sans mise en forme, la réponse est un mur de données :

"https://hex.pm/api/packages"
|> Req.get!(params: [search: "liveview"])
|> Map.fetch!(:body)
|> length()

Cent paquets. IO.inspect serait illisible. Kino.DataTable en fait un tableau triable :

paquets = Req.get!("https://hex.pm/api/packages", params: [search: "liveview"]).body

paquets
|> Enum.map(fn p ->
  %{
    nom: p["name"],
    version: p["latest_version"],
    telechargements: get_in(p, ["downloads", "all"]) || 0,
    description: p["meta"]["description"]
  }
end)
|> Enum.sort_by(& &1.telechargements, :desc)
|> Enum.take(20)
|> Kino.DataTable.new(name: "Paquets Hex")

Étape 2 — Le rendre interactif

Un tableau figé sur "liveview" n'est pas un outil. Ajoutons un formulaire, et une zone de rendu qu'on redessinera à chaque recherche.

Kino.Control.form/2 construit le formulaire, Kino.Frame réserve la zone, et Kino.listen/2 réagit aux soumissions — sans jamais réexécuter la cellule.

defmodule OutilHex do
  def chercher(terme) do
    "https://hex.pm/api/packages"
    |> Req.get!(params: [search: terme])
    |> Map.fetch!(:body)
    |> Enum.map(fn p ->
      %{
        nom: p["name"],
        version: p["latest_version"],
        telechargements: get_in(p, ["downloads", "all"]) || 0,
        description: p["meta"]["description"]
      }
    end)
    |> Enum.sort_by(& &1.telechargements, :desc)
    |> Enum.take(20)
  end
end
formulaire = Kino.Control.form([terme: Kino.Input.text("Rechercher")], submit: "Chercher")
zone = Kino.Frame.new()

Kino.listen(formulaire, fn %{data: %{terme: terme}} ->
  cond do
    String.trim(terme) == "" ->
      Kino.Frame.render(zone, Kino.Markdown.new("_Saisissez un terme._"))

    true ->
      case OutilHex.chercher(terme) do
        [] ->
          Kino.Frame.render(zone, Kino.Markdown.new("Aucun paquet pour **#{terme}**."))

        lignes ->
          Kino.Frame.render(zone, Kino.DataTable.new(lignes, name: "Résultats"))
      end
  end
end)

Kino.Layout.grid([formulaire, zone], boxed: true)

Tapez un terme et validez : le tableau se remplace. Aucune cellule n'est réexécutée — c'est le processus derrière Kino.listen qui redessine la zone.

Ce que ça ne remplace pas

Ce n'est pas une application. L'état vit dans un processus attaché au notebook : fermez-le, tout disparaît. Ce n'est pas non plus un tableau de bord partagé — chaque personne qui ouvre le notebook exécute le sien.

C'est un outil personnel qu'on peut envoyer par courriel, versionner à côté du code, et relancer dans six mois avec les mêmes dépendances grâce à Mix.install.

Pour aller plus loin

Le même formulaire branché non plus sur une API publique mais sur une application Elixir en cours d'exécution — voilà où Livebook devient vraiment intéressant. C'est le sujet du prochain article.


Retour vers le sommaire des tips · Accueil