Architecte de vos plateformes/produits et agitateur de séries temporelles

Conception, développement, déploiement et exploitation de vos plateformes, applications et données.

Contactez-nous !
Accueil > Blog

Ma solution pour le Warp 10 Code Contest - partie 2

timeseries warp10 geospatial challenge

Suite et fin de ma réponse au code contest après la première partie. Dans ce billet, nous allons voir comment calculer les émissions de CO2 pour la partie de trajet sur la route 66.

// Define points from the car journey on the US66 road
[
  // Here is the gts of the car datalogger
  @senx/dataset/route66_vehicle_gts

  // Here is the route 66 geoshape (+/- 20meters)
  @senx/dataset/route66_geoshape
  mapper.geo.within 0 0 0
] MAP
"onTheRoad" STORE

$onTheRoad
{
 'timesplit' 60 s
}
MOTIONSPLIT
0 GET
'sectionOnTheRoad' STORE

// Compute speed - result in m/s
[ $sectionOnTheRoad mapper.hspeed 1 0 0 ] MAP
// Convert in km/h so x3600 /1000 = 3.6 - mapper.mul expects a constant
[ SWAP 3.6 mapper.mul 0 0 0 ] MAP
'speedFrames' STORE

// Get distance between each points in km (first in meters, then in km)
[ $sectionOnTheRoad mapper.hdist 0 1 0 ] MAP
[ SWAP 0.001 mapper.mul 0 0 0 ] MAP
'distFrames' STORE

// fuel consumption approximation is  (8 liters/100km) × (speed (km/h) / 80) +1
// So it's Speed * 8 / 80 / 100 + 1 = V/10 + 1
// F = False => does not return the index
$speedFrames
<%
  0.1 *
  1.0 +
%> F LMAP
'hundredKmFuelConsumption' STORE

[ ] 'instantConsumption' STORE

<%
  'i' STORE // store index

  // Get each list and compute one by another
  // So we compute consumption for 100 km at given speed (computed previously)
  // with related distance
  // then we divide by 100 as first value is for 100 km
  $distFrames $i GET
  $hundredKmFuelConsumption $i GET
  *
  100 /
  'r' STORE
  $instantConsumption $r +!
%>
'C' STORE
0 7 $C FOR
CLEAR

// For each GTS, compute fuel consumption as 1 point
[
  $instantConsumption
  mapper.sum
  MAXLONG
  MAXLONG
  1
] MAP
// Sum all points to get total consumption
0 SWAP <% VALUES 0 GET + %> FOREACH
// 1L = 2392g CO2
2392 *
// Enjoy !

Le premier et le second bloc sont les mêmes que dans la premièr partie. Je vous y renvoie donc si besoin.

A ce stade, nous avons une liste de 8 séries correspondant à chaque section passée sur la route 66. Chaque série comporte un liste de timestamps et de points géospatiaux (lattitude, longitude, élévation).

Concernant le troisième bloc :

  • il s’agit de calculer la vitesse en m/s entre chaque point de la série (ce qui explique le 1 0 0 pour prendre le point précédent, aucun point suivant et appliquer cette opération sur l’ensemble de la liste - voir la tips 3 de 12 tips to apply sliding window algorithms like an expert). Pour cela, on utilise mapper.hspeed (doc) qui consomme une série et calcule la vitesse en m/s en tenant compte de la longitude/lattitude/élévation.
  • Ce résultat, on le convertit en km/h dans la foulée en utilisant mapper.mul (doc) en notant au passage qu’il lui faut une constante (on ne peut pas mettre 3600 * 1000 / mais 3.6)
  • On a donc une liste de 8 séries temporelles avec chacune un timestamp, les données géospatiales et une vitesse entre chaque point. C’est stocké dans la variable speedFrames.

Concernant le 4ème bloc :

  • Sur le même modèle que pour la vitesse, on calcul la distance entre chaque point des 8 séries via mapper.hdist que l’on a vu dans le premier billet. Cette fois-ci, plutôt que de calculer la distance totale, on la distance entre le point et le point suivant et on le fait pout tout les points de la liste, d’où le 0 1 0
  • La distance étant en mètres, on la divisie par 1000 pour avoir des kilomètres. Mais comme il n’y a pas de mapper de division, alors on utilise mapper.mulet la valeur 0.001
  • On a donc une liste de 8 séries temporelles avec chacune un timestamp, les données géospatiales et une distance entre chaque point. C’est stocké dans la variable distanceFrames.

Concernant le 5ème bloc :

  • j’ai voulu calculer la consommation d’essence sur la base de la formule:

(8 liters/100km) × (speed (km/h) / 80) +1

  • Cela se simplifie en Speed/10 + 1.
  • Si on multiplie ce coefficient par les vitesses entre deux points obtenues précédemment (dans speedFrames), on obtient une consommation pour 100km avec chaque vitesse. Il faudra dans un second temps le pondérer par la distance parcourue entre deux points (distanceFrames) pour avoir un instantané de consommation pour la vitesse et la distance parcourue.
  • Pour faire cette consommation au 100km non pondérée, on utilise LMAP (doc)pour appliquer une MACRO à chaque élément de la liste. Cette macro contient le coefficient de consommation d’essence. LMAP retourne normalement l’index et la valeur associée. Or l’index ne nous sert à rien, on met donc l’argument concernant l’index à False (abrégé F) pour qu’il ne soit pas retourné.
  • On stocke le résultat dans hundredKmFuelConsumption et on a donc une liste de 8 series avec la consommation pour 100km à la vitesse donnée. Il nous faut maintenant pondérée cette liste par la distance pour avoir un instantané de consommation.

Concernant le 6ème bloc :

  • On commence par créer une liste vide appelée instantConsumption.
  • On sait que l’on a une liste de 8 éléments, donc on peut faire un boucle FOR (doc) dessus avec un indice allant de 0 à 7. FOR prend comme dernier argument une MACRO que j’ai nommé C
  • Dans la MACRO définie au dessus, je commence par stocker l’index de la boucle. Mes deux listes de 8 séries temporelles sont identiques en terme de points, avec l’une contenant les consommations pour 100km hundredKmFuelConsumptionet la seconde les distances entre chaque point distFrames. L’idée est donc de multiplier chaque série de hundredKmFuelConsumption par la série équivalente dans distFrames et de diviser par 100 pour finir notre proportionnalité.
  • On stocke cet consommation instantanée dans la variable r.
  • On ajoute ce résultat r dans la liste instantConsumption, ce qui permet de reconstituer notre liste de 8 séries mais ayant pour valeur cette fois ci les instantanés de consommation entre chaque point de chaque série.

Un petit interlude visuel avant le dernier bloc :

warp10 - instantané de consommation

Concernant le 7ème bloc :

  • Le but est de faire la somme de chaque instantanné de consommation pour avoir la consommation totale.
  • Comme dans la première partie, on utilise cette fois-ci mapper.sum (doc) en prenant l’ensemble des données des listes capturées via MAXLONG et on récupère 1 seule valeur qui s’avère être le total. On a donc la consommation totale de chaque série
  • Comme vu aussi en fin de première partie, on fait alors la somme de chaque liste pour avoir la consommation totale (9.823366576601234)
  • On sait que 1L = 2392g CO2, il nous reste donc à faire cette multiplication.
  • On obtient alors : 23497.492851230152 ou 23,497 kg de CO2.

J’espère avoir été clair dans ces explications - si ce n’est pas le cas - dites le moi (via Twitter, Mail, LinkedIn, etc) et je préciserai les choses.

Bilan personnel de ce code contest :

  • Opportunité de découvrir une partie des fonctionnalités géospatiales de Warp 10 que je n’avais pas encore utilisé
  • Améliorer mon usage autour de MAP, les mapper, les MACRO et LMAP et plein de petites choses ici ou là.
  • MAP s’applique sur des GTS mais aussi des listes de GTS sans rien avoir à faire. Pas besoin de se rajouter des boucles supplémentaires !
  • MAXLONG utilisé dans les MAP permet de ne pas avoir à se soucier de la taille de l’élément sur laquelle on applique MAP. Cela ne fait pas non plus une erreur du style index out of range.
  • en bonus, obtenir quelques lots sympathiques 😎

warp10 - goodies recto

warp10 - goodies verso

J’espère néanmoins apprendre des choses du corrigé officiel : Working with GEOSHAPEs: code contest results.

Ma solution pour le Warp 10 Code Contest - partie 1

timeseries warp10 geospatial challenge

La société SenX a proposé un code contest suite à la publication de son article sur les formes géospatiales. L’objet du concours porte sur le trajet d’un véhicule aux USA et il consiste à déterminer :

  • la distance réalisée sur la fameuse route 66 durant ce trajet,
  • de déterminer les émissions de CO2 réalisées durant ce trajet sur la route 66.

Maintenant que le gagnant a été annoncé (TL;DR: moi 😎🎉) et en attendant le corrigé officiel, voici ma proposition de solution.

Distance parcourue sur la route 66

Les données de départ sont :

  • @senx/dataset/route66_vehicle_gts : le trajet réalisé par le véhicule
  • @senx/dataset/route66_geoshape : la route 66
// Define points from the car journey on the US66 road
[
  // Here is the gts of the car datalogger
  @senx/dataset/route66_vehicle_gts

  // Here is the route 66 geoshape (+/- 20meters)
  @senx/dataset/route66_geoshape
  mapper.geo.within 0 0 0
] MAP
"onTheRoad" STORE

$onTheRoad
{
 'timesplit' 60 s
}
MOTIONSPLIT
0 GET
'sectionOnTheRoad' STORE

// Compute distance for each GTS and output it as a single point
[ $sectionOnTheRoad mapper.hdist MAXLONG MAXLONG 1 ] MAP
// Sum all GTS
0 SWAP <% VALUES 0 GET + %> FOREACH
// Convert to km
1000 /
// Enjoy !

Explications :

  • Le premier bloc utilise le mapper mapper.geo.within (doc). Ce mapper compare deux zones géographiques et ne retient que les poits qui sont dans la zone voulue. Ici, je prends donc tous les points du trajet et les compare avec ceux de la route 66. Seuls les points sur la route 66 sont conservés. Le résultat est une aggrégation de points que l’on stocke dans la variable onTheRoad.
  • Pour le second bloc : dans le studio, lorsque l’on regarde la liste des points obtenus dans l’onglet “Tabular view”, on peut voir que les points sont espacés en général de minimum 10 secondes et jusqu’à une minute environ. Après avoir relu le billet “Use motion to automatically split GTS”, j’ai retenu ce seuil d’une minute et la fonction MOTIONSPLIT (doc) pour calculer la distance entre deux points. Obtenant une liste de 1 élément contenant une liste, j’ai rajouté le 0 GET pour supprimer la liste parente. On obtient alors une liste de 8 séries temporelles (GTS) correspondant à chaque tronçon sur la route. On stocke cela dans la variable sectionOnTheRoad.

warp10 - section on the road

  • Pour le dernier bloc - partie 1 : mapper.hdist (doc) permet de calculer la distance totale sur une fenêtre glissante de points. L’utilisation de MAXLONG permet d’avoir une valeur suffisamment grande pour notre cas d’espèce pour prendre l’ensemble des données de chacune des 8 listes - il n’est pas nécessaire de connaitre la taille exacte de la liste pour travailler dessus et cela ne crée pas d’erreur non plus ; ça peut déstabiliser !. Le 1 permet de n’avoir qu’une valeur en sortie. On a donc en sortie la distance de chacune des 8 sections.

warp10 - total distance of each section

  • Pour le dernier bloc - partie 2 : là, j’avoue la syntaxe est un peu cryptique 🤯. L’idée est donc de faire la somme de toutes les distances totales obtenues précédemment. Il faut donc faire 0 (pour initialiser l’opération d’addition) et ajouter la première valeur de la liste et ainsi de suite. Une fois qu’on a la somme, on divise par 1000 pour avoir des kilomètres
  • La réponse est alors: 79.82147744769853

Pour comprendre la partie 2, on peut réécrire la chose de la façon suivante :

[ $sectionOnTheRoad mapper.hdist MAXLONG MAXLONG 1 ] MAP
'totalDistancePerSection' STORE

0 $totalDistancePerSection <% VALUES 0 GET + %> FOREACH

Non, toujours pas ? Vous me rassurez, j’ai du creuser plus loin aussi.

Commençons par :

$totalDistancePerSection <% VALUES 0 GET %> FOREACH

VALUES (doc) consomme une série temporelle et en sort les valeurs sous la forme d’une liste. Nous avons une liste initiale de 8 séries que nous avons ramené à 8 points. Avec FOREACH (doc), on applique donc la fonction VALUES sur chaque série contenant un seul point. Plutôt que d’avoir en sortie des listes à un seul point, le 0 GET permet d’avoir directement la valeur.

warp10 - values et foreach

Pour faire une addition, en WarpScript, c’est :

1 1 +

ou :

1
1
+

Par celà, j’entends que pour appliquer +, il faut que les deux éléments soient définis dans la pile.

Notre boucle FOREACH emet dans la pile chaque valeur qu’il faut ajouter à la précédente. On peut donc rajouter le + dans la boucle FOREACH :

$totalDistancePerSection <% VALUES 0 GET + %> FOREACH

Mais si je cherche à exécuter cela, cela ne fonctionne pas - cela reviendrait à faire:

valeur1IssueDuForeach +
valeur2IssueDuForeach +
valeur3IssueDuForeach +
valeur4IssueDuForeach +
...

Si on part de la fin, la valeur 4 va pouvoir être additionnée à la valeur 3 car celle-ci existe dans la pile. MAIS la valeur 1 n’est additionnée à rien à ce stade et l’opération est invalide. D’où la nécessité de rajouter le 0 pour pouvoir avoir deux éléments pour notre première addition.

Ce qui nous donne bien :

0 $totalDistancePerSection <% VALUES 0 GET + %> FOREACH

Maintenant que la brume s’est éclaircie et que le 🤯 est passé à 😎 pour cette syntaxe de fin, je vous propose de nous retrouver dans un prochain billet pour la suite de ma solution au concours.

Web, Ops, Data et Time Series - Avril 2021

falco sysdig sécurité dashboard raspberrypi pico hashicorp vault vector containerd git git-filter-repo psp gitlab-ci podman warp10 sqlite terraform timescale velero docker docker-compose grafana loki tempo kubernetes minio influxdata notebook geospatial agpl bme680 co2

Code

Conteneur et orchestration

  • Electro Monkeys - Docker Compose avec Nicolas de Loof : Retour sur la Developper Experience autour de Docker, l’historique et le futur de docker-compose, la création de la spécification Compose, les intégrations AWS/ECS et Azure/ACI, l’intégration Kubernetes, etc.
  • nerdctl: Docker-compatible CLI for contaiNERD : une CLI qui imite la CLI Docker mais en interagissant directement avec containerd. Elle permet aussi de bénéficier de certaines fonctionnalités de containerd qui ne sont pas prévues pour tout de suite dans Docker apparemment.
  • Blog: Kubernetes 1.21: Power to the Community : au programme de cette nouvelle version : Cronjobs GA, Immutable Secrets and ConfigMaps GA, IPv4/IPv6 dual-stack support, Graceful Node Shutdown, PersistentVolume Health Monitor mais aussi PodSecurityPolicy Deprecation et TopologyKeys Deprecation
  • PodSecurityPolicy Deprecation: Past, Present, and Future: article plus détaillé sur la dépréciation des PSP.
  • Podman v3.1.0 Released : ajout de la gestion des secrets, améliorations des commandes kube avec notamment la génération des PersistentVolumeClaim ou encore la gestion des propriétaires des volumes.
  • Velero 1.6.0 : améliorations diverses comme le support des identifiants par buckets (et non globaux uniquement), mise à jour de restic vers 0.12.0, etc.
  • Compose CLI Tech Preview : compose devrait devenir une sous-commande officiel de la CLI Docker ; on pourra alors faire docker compose up -d
  • Docker 20.10.6 : version de maintenance avec le support des puces Apple Silicon M1.
  • Kubernetes : vers 3 releases par an au lieu de 4 : de quoi courrir un peu moins derrière les versions et à relier avec le support de chaque version étendue à 1 an depuis la 1.19.

Data

  • sq: swiss-army knife for data : le jq pour les données relationelles. Du SQL ou des fichiers Excel/CSV/JOSN/XML en entrée et les mêmes formats en sortie (et un peu plus).
  • SQLite is not a toy database : On a souvent une fausse image de sqlite - l’article permet de se mettre à jour…

IaC

IoT

  • Pico 2 Pi Adapter Board : un petit adapteur sympathique pour Raspeberry Pi Pico et vous permettre de brancher facilement vos composants sans soudure et mener ainsi vos expériences.
  • Piper Make : Pour programmer facilement votre Raspberry Pi Pico en MicroPython mais avec une logique de blocs à la Scratch.
  • Utilisation des BME680 et RV3028 avec Raspberry Pi Pico : le composant BME680 permet d’évaluer la qualité de l’air - le projet permet donc de capturer et d’afficher cette information avec un Raspberry Pi. Son successeur, le BME688 dispose d’une pincée d’IA.
  • Projet CO2 et Makers CO2 : pour mieux comprendre les enjeux autour de l’aération des pièces et comment faire vos capteurs.

Observabilité & Monitoring

Réseau

  • The Mystery of AS8003 : Une entité inconnue jusque là mais liée à l’administration américaine a annoncé la gestion d’une très grande plage réseau. Les implications et les motivations sont encore à éclaircir. Le billet émet différents hypothèses. Le thread twitter associé est intéressant aussi.

Sécurité

Time Series

Web, Ops, Data et Time Series - Mars 2021

gke gcp kubernetes nomad hashicorp consul vault timescale warp10 IoT ptsm tsfr spark databricks tempo indluxdata

La prochaine édition de Time Series France aura lieu le mardi 30 Mars à 18h avec la présentation de la base StuteoDB, basée sur Apache Cassandra. Par ici pour les détails et inscriptions.

Container et orchestration

Time Series

Ma comptabilité, une série temporelle comme les autres - partie 4 - dashboards

warp10 timeseries comptabilité discovery dashboard

Suite de notre épopée :

Nous allons voir aujourd’hui comment présenter ces données à l’aide de Discovery, la solution de Dashboard as Code pour Warp 10 fournie par SenX.

Installation de Discovery

Tout est décrit dans le billet Truly Dynamic Dashboards as Code

Dans mon cas, warp 10 est dans une partition dédiée /srv/warp10 - warp 10 est donc installé dans /srv/warp10/warp10. C’est la valeur de $WARP10_HOME.

Pour la configuration du plugin HTTP, j’ai un fichier $WARP10_HOME/etc/conf.d/80-discovery.conf contenant :

# Load the HTTP Plugin
warp10.plugin.http = io.warp10.plugins.http.HTTPWarp10Plugin
# Define the directory where endpoint spec files will reside
http.dir = /srv/warp10/discovery
# Define the host and port the plugin should bind to
http.host = 127.0.0.1
http.port = 8081
# Expose the Directory and Store so FETCH requests can be performed via the plugin
egress.clients.expose = true

Le plugin HTTP sera donc accessible via une url de base en http://127.0.0.1:8081/

J’ai ensuite créé le fichier /srv/warp10/discovery/discovery.mc2/srv/warp10/discovery est la valeur associée à http.dir dans le fichier précédent.

{
  'path' '/discovery/'
  'prefix' true
  'parsePayload' true
  'macro' <%
    'cerenit/dashboards/' @senx/discovery/dispatcher
  %>
}

Ce fichier indique que :

  • Les dashboards seront disponibles à partir de l’url /discovery/<nom_du_dashboard> ou /discovery/<dossier_ou_arborescence>/<nom_du_dashboard>
  • Le dossier où seront les dashboards seront stockés dans $WARP10_HOME/macros/cerenit/dashboards. Il s’agira de fichier WarpScript ou Flows avec l’extension en .mc2.

Avec ces deux fichiers, nous savons maintenant que :

  • nous accèderons aux dashboards via http://127.0.0.1:8081/discovery/<nom_du_dashboard>.
  • les dashboards seront des fichiers stockés dans ``$WARP10_HOME/macros/cerenit/dashboards`
  • donc le dashboard $WARP10_HOME/macros/cerenit/dashboards/mon_dashboard.mc2 sera accessible via http://127.0.0.1:8081/discovery/mon_dashboard.

Création du premier dashboard

Un dashboard se décompose en différentes parties. Celle contenant les données a le mot clé tiles et contient différente tile. Chaque tile affiche un graphique, un zone de texte, un titre ou tout composant warpView. Pour le reste, on s’appuiera sur le template par défaut.

Donc créeons un fichier $WARP10_HOME/macros/cerenit/dashboards/comptabilite/compta1.mc2 contenant :

<%
   {
     'tiles' [
       {
         'type' 'display'
         'w' 4 'h' 1 'x' 3 'y' 0
         'data' 'Compta - Exemple 1'
       }
       {
         'type' 'line'
         'w' 4 'h' 2 'x' 1 'y' 3
         'data' [
           @cerenit/accountancy/revenue
           'revenue' STORE
           $revenue
         ]
       }
       {
         'type' 'line'
         'w' 4 'h' 2 'x' 5 'y' 3
         'data' [
           @cerenit/accountancy/expense
           'expense' STORE
           $expense
         ]
       }
       {
         'type' 'line'
         'w' 4 'h' 2 'x' 3 'y' 5
         'data' [
           $revenue $expense -
         ]
       }
     ]
   }
   @senx/discovery/render
%>

Comme indiqué précédemment, je me focalise sur le contenu de tiles. La grille de présentation des dashboards est fixé à 12 colonnes par défaut.

Ici, je cherche donc à afficher 4 éléments :

  • Le premier élément est un bloc titre, contenant “Compta - Exemple 1”. Il est placé sur la 3ème item de la grille en parant de la gauche (valeur de x), il est en haut (valeur y de 0) et il occupe une largeur de 4 (valeur de w) et une hauteur de 1 (valeur de h),
  • les trois éléments suivants sont des graphiques de tyme “line” avec un positionnement pour deux les deux premiers soient sur une ligne et le dernier en dessous et plutôt centré par rapport aux deux premmiers.
  • le deuxième et troisième éléments font appels à des macros @cerenit/accountancy/xxx. Je pourrais mettre du code Warpscript directement dans le fichier comme dans l’exemple. Toutefois, le code exécuté dans le dashboard est visible dans le navigateur. Dans la mesure où mes requêtes pour récupérer les données demandent de l’authentification avec un passage de token, je déporte ce code dans une macro et je ne fais donc qu’appeler cette macro. Ainsi, le code sera généré coté serveur et seul le résultat sera retourné dans le navigateur.
  • le dernier élement illustre la capacité intéressante et différentiante de Discovery : on ne fait pas que décrire le dashboard avec du code, on peut aussi faire des opérations sur nos données. Ici je calcule dynamiquement le résultat à partir des données de chiffre d’affaires (revenue) et de dépenses (expense). J’aurais pu faire l’appel à une troisième macro comme les deux éléments précédents, mais vu que j’ai cette capacité de réaliser des opérations au sein d’un dashboard, pourquoi me priver ?
  • enfin, on appelle la fonction @senx/discovery/render pour générer le dashboard.

Revenons sur nos macros ; Warp 10 permet d’avoir des macros exécutées coté serveur. Ces macros peuvent être utiles pour créer/partager du code, elles peuvent prendre des paramètres en entrée si besoin et elles sont exécutées coté serveur. Dans notre cas, pour éviter que nos tokens se balladent dans le navigateur comme indiqué précédemment, c’est cette propriété qui va nous intéresser.

La macro @cerenit/accountancy/revenue se trouve donc dans le fichier $WARP10_HOME/macros/cerenit/accountancy/revenue.mc2 et contient :

<%
  {
    'name' 'cerenit/accountancy/revenue'
    'desc' 'Provide revenue'
  } INFO

  // Actual code
  SAVE 'context' STORE

  '<readToken>' 'readToken' STORE
  [ $readToken 'revenue' { 'company' '=cerenit' } NOW [ 2016 12 1 ] TSELEMENTS-> ] FETCH
  0 GET

  $context RESTORE
%>
'macro' STORE
$macro

Je ne vais pas m’étendre sur la rédaction des macros mais succintement :

  • Le permier bloc donne le nom de la macro et sa description
  • La suite indique le code qui va être réalisé - pour ma part, un FETCH sur la classe “revenue” pour la compagny “cerenit” du 01/12/2016 jusqu’à maintenant. Cette liste n’a qu’un élément que je récupère. C’est ce qui me sera retourné.

La macro @cerenit/accountancy/expense est sur le même modèle en remplaçant revenue par expense.

Ces deux macros nous retournent donc chacune une série temporelle sur la période 12/2016 jusqu’à ce jour : une pour le chiffre d’affaires, une pour les dépenses.

Si vous allez sur http://127.0.0.1:8081/discovery/comptabilite/compta1, vous verrez le dashboard suivant :

warp10

Le template par défaut est assez minimaliste et on note la présence d’un logo SenX. Je n’ai rien contre, mais comme c’est la compatabilité de mon entreprise que je présente, autant changer cet aspect des choses.

Ajustements graphiques

Pour continuer progressivement, nous allons :

  • Rajouter un titre et une description à notre dashboard en précisant les propriétés title et description en début de fichier
  • Rajouter un footer en bas de page en précisant la propriété footer
  • Supprimer le logo SenX en précisant la propriété template
  • Ajuster la position des blocs pour qu’ils soient centrés comme le reste des éléments

On met cela dans un nouveau fichier $WARP10_HOME/macros/cerenit/dashboards/comptabilite/compta2.mc2.

<%
   {
     'title' 'Comptabilité CerenIT'
     'description' 'Comptabilité CérénIT depuis 2016'
     'tiles' [
       {
         'type' 'display'
         'w' 4 'h' 1 'x' 4 'y' 0
         'data' 'Compta - Exemple 2'
       }
       {
         'title" "Chiffre d'affaires'
         'type' 'line'
         'w' 4 'h' 2 'x' 2 'y' 3
         'data' [
           @cerenit/accountancy/revenue
           'revenue' STORE
           $revenue
         ]
       }
       {
         'title' 'Dépenses'
         'type' 'line'
         'w' 4 'h' 2 'x' 6 'y' 3
         'data' [
           @cerenit/accountancy/expense
           'expense' STORE
           $expense
         ]
       }
       {
         'title' 'Résultat'
         'type' 'line'
         'w' 4 'h' 2 'x' 4 'y' 5
         'data' [
           $revenue $expense -
         ]
       }
     ]
     'footer' '<p style="text-align: center;">CérénIT &copy; 2021 - Réalisé avec Discovery et Warp 10 de SenX</p>'
     'template'
   <'
<!DOCTYPE html><html><head><title id="pageTitle"></title>
  {{{CSS}}}
  {{{HEAD}}}
</head>
<body>
<div class="heading">
  <div class="header"><h1 id="title" class="discovery-title"></h1><p id="desc" class="discovery-description"></p></div>
</div>
{{{HEADER}}}
{{{GRID}}}
{{{FOOTER}}}
{{{JS}}}
</body></html>
    '>
   }
   @senx/discovery/render
%>

Si les propriétés title, description et footer vont de soi, pour trouver comment supprimer le logo SenX, il m’a fallu lire le contenu de la macro @senx/discovery/html pour mieux comprendre les différents placehoders et leur fonctionnement.

Si vous allez sur http://127.0.0.1:8081/discovery/comptabilite/compta2, vous verrez le dashboard suivant :

warp10

A ce stade, on note que les propriétés title de chaque graphique n’est pas affiché. En dehors de ça, nous retrouvons bien tous nos éléments ajustés.

Néanmoins, cette lecture de @senx/discovery/html permet de voir que l’on a pas mal de points d’entrée pour rajouter des éléments spécifiques. Le tout sera de veiller à ne pas impacter les composants graphiques WarpView dans leur sémantique pour ne pas créer de dysfonctionnement.

Un petit coup de bar(re)

Pour finir ce tutoriel, nous allons :

  • lisser un peu ces lignes d’une part, en remplaçant le type de line à spline pour les trois graphiques déjà réalisés (pour les autres modes de réprésentation, voir les options de chart)
  • rajouter un histogramme avec le cumul annuel de nos données avec un chart de type bar.

On met cela dans un nouveau fichier $WARP10_HOME/macros/cerenit/dashboards/comptabilite/compta3.mc2.

<%
   {
     'title' 'Comptabilité CerenIT'
     'description' 'Comptabilité CérénIT depuis 2016'
     'tiles' [
       {
         'type' 'display'
         'w' 4 'h' 1 'x' 4 'y' 0
         'data' 'Compta - Exemple 3'
       }
       {
         'title' 'Chiffre d\'affaires'
         'type' 'spline'
         'w' 4 'h' 2 'x' 2 'y' 3
         'data' [
           @cerenit/accountancy/revenue
           'revenue' STORE
           $revenue
         ]
       }
       {
         'title' 'Dépenses'
         'type' 'spline'
         'w' 4 'h' 2 'x' 6 'y' 3
         'data' [
           @cerenit/accountancy/expense
           'expense' STORE
           $expense
         ]
       }
       {
         'title' 'Résultat'
         'type' 'spline'
         'w' 4 'h' 2 'x' 4 'y' 5
         'data' [
           $revenue $expense -
         ]
       }
       {
          'title' 'Consolidation annuelle'
          'type' 'bar'
          'w' 4 'h' 2 'x' 4 'y' 7
          'data' [
            [ $revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
            [ $expense bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
            [ @cerenit/accountancy/result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
          ]
          'options' { 'timeMode' 'timestamp' }
       }
     ]
     'footer' '<p style="text-align: center;">CérénIT &copy; 2021 - Réalisé avec Discovery et Warp 10 de SenX</p>'
     'template'
   <'
<!DOCTYPE html><html><head><title id="pageTitle"></title>
  {{{CSS}}}
  {{{HEAD}}}
</head>
<body>
<div class="heading">
  <div class="header"><h1 id="title" class="discovery-title"></h1><p id="desc" class="discovery-description"></p></div>
</div>
{{{HEADER}}}
{{{GRID}}}
{{{FOOTER}}}
{{{JS}}}
</body></html>
    '>
   }
   @senx/discovery/render
%>

Pour ce dernier graphique, il est donc de type bar. Pour le détail des requêtes, je vous renvoie à la partie 2 qui explique cela. Dans notre cas, il faut juste veiller à passer une option supplémentaires pour que timeMode interprête la date issue de la requête comme un timestamp et non comme une date par défaut. D’autres options comme la gestion de la présentation en mode vertical/horizontal ou en mode “stacked” ou pas.

Si vous allez sur http://127.0.0.1:8081/discovery/comptabilite/compta3, vous verrez le dashboard suivant :

warp10

Pour résumer ce billet, nous aovns pu voir que :

  • Warp 10 dispose d’une solution de Dashboard avec Discovery
  • Comme on pouvait le voir dans le studio, à partir du moment où l’on a nos requêtes, il est aisé de les mettre dans des dashboards
  • Les macros permettent de ne pas exposer des tokens dans le navigateur et de faire des calculs cotés serveur
  • Il est possible de personnaliser l’apparence des dashboards - des points d’entrée et des mécanismes de surcharges sont à notre disposition pour en faire ce que l’on souhaite
  • Les composants WarpView sont assez riches et se configurent facilement