Posts by "Sébastien Daviot"

Comment extraire les données de navigation depuis Piano Analytics ?

J’ai régulièrement à analyser des données de navigation à l’aide du module d’exploration de Piano Analytics. Toutefois, il est parfois compliqué d’extraire ces données pour les retravailler dans Excel ou les présenter dans PowerPoint.

Explication sur le contenu de l’extract

Pour faciliter ce processus, j’ai développé un petit code JavaScript permettant d’extraire l’ensemble des données de navigation affichées dans l’explorateur Piano. Ce script génère un fichier CSV contenant, pour chaque page, les informations suivantes :

  • Si la page est sélectionnée dans le chemin de navigation en cours
  • L’étape à laquelle appartient la page
  • La position de la page dans l’étape (triée en fonction du volume)
  • Le niveau 2 associé
  • Le nom de la page
  • Le nombre d’occurrences
  • Le taux de sortie (uniquement disponible dans l’analyse exploratoire « après une page »)

Attention : Si vous effectuez une extraction en mode concentration (« avant une page »), les numéros d’étapes sont inversés (1 pour la dernière étape, 2 pour l’avant-dernière étape, etc.).

Utilisation du code

Le script JavaScript lit le DOM de la page pour récupérer les informations nécessaires et générer un fichier CSV. Pour l’utiliser, vous avez deux options.

  • Ajouter le code en favori dans votre navigateur : Suivez les instructions ici pour installer un bookmarklet JavaScript, puis cliquez dessus lorsque vous êtes dans l’analyse de navigation
javascript:(function(){function extractDataAndExportToCSV(){const url=window.location.href;if(url.includes("https://analytics.piano.io/explorer/#/treeview/")){const stickers=document.querySelectorAll('.ats-treeview-container .treeview-sticker');const data=[];stickers.forEach(sticker=>{const isSelected=sticker.classList.contains('sticker-selected');const classList=Array.from(sticker.classList);const positionClass=classList.find(cls=>cls.startsWith('treeview-sticker-'));const x=parseInt(positionClass.split('-')[2]);const y=parseInt(positionClass.split('-')[3]);const level2=sticker.querySelector('.ats-b-treeview-sticker-subtitle')?.innerText||'';const pageName=sticker.querySelector('.ats-b-treeview-sticker-title')?.innerText||'';const occurrence=(sticker.querySelector('.ats-b-treeview-sticker-value')?.innerText||'').replace(/[\s,]/g,'');let exitRate=null;if(url.includes('/afterapage/')){const progressBarValue=sticker.querySelector('.ats-progress-bar-value')?.innerText||'';if(progressBarValue){exitRate=parseFloat(progressBarValue.replace('%','').replace(',','.'))/100;if(navigator.language==='fr-FR'){exitRate=exitRate.toString().replace('.',',');}}}data.push({Selected:isSelected,step:x+1,'order in step':y+1,'Level 2':level2,'Page Name':pageName,Occurrence:occurrence,'Exit Rate':exitRate});});const csv=convertToCSV(data);downloadCSV(csv);}}function convertToCSV(data){const header=Object.keys(data[0]).join(';');const rows=data.map(row=>Object.values(row).join(';'));return[header,...rows].join('\n');}function downloadCSV(csv){const blob=new Blob([csv],{type:'text/csv;charset=utf-8;'});const link=document.createElement("a");const url=URL.createObjectURL(blob);link.setAttribute("href",url);link.setAttribute("download","export.csv");link.style.visibility='hidden';document.body.appendChild(link);link.click();document.body.removeChild(link);}extractDataAndExportToCSV();})();
  • Exécuter le code suivant directement dans la console de votre navigateur
function extractDataAndExportToCSV() {
    const url = window.location.href;

    if (url.includes("https://analytics.piano.io/explorer/#/treeview/")) {
        const stickers = document.querySelectorAll('.ats-treeview-container .treeview-sticker');
        const data = [];

        stickers.forEach(sticker => {
            const isSelected = sticker.classList.contains('sticker-selected');
            const classList = Array.from(sticker.classList);
            const positionClass = classList.find(cls => cls.startsWith('treeview-sticker-'));
            const x = parseInt(positionClass.split('-')[2]);
            const y = parseInt(positionClass.split('-')[3]);
            const level2 = sticker.querySelector('.ats-b-treeview-sticker-subtitle')?.innerText || '';
            const pageName = sticker.querySelector('.ats-b-treeview-sticker-title')?.innerText || '';
            const occurrence = (sticker.querySelector('.ats-b-treeview-sticker-value')?.innerText || '').replace(/[\s,]/g, '');
            let exitRate = null;
            if (url.includes('/afterapage/')) {
                const progressBarValue = sticker.querySelector('.ats-progress-bar-value')?.innerText || '';
                if (progressBarValue) {
                    exitRate = parseFloat(progressBarValue.replace('%', '').replace(',', '.')) / 100;
                    if (navigator.language === 'fr-FR') {
                        exitRate = exitRate.toString().replace('.', ',');
                    }
                }
            }

            data.push({
                Selected: isSelected,
                step: x + 1,
                'order in step': y + 1,
                'Level 2': level2,
                'Page Name': pageName,
                Occurrence: occurrence,
                'Exit Rate': exitRate
            });
        });

        const csv = convertToCSV(data);
        downloadCSV(csv);
    }
}

function convertToCSV(data) {
    const header = Object.keys(data[0]).join(';');
    const rows = data.map(row => Object.values(row).join(';'));
    return [header, ...rows].join('\n');
}

function downloadCSV(csv) {
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    const link = document.createElement("a");
    const url = URL.createObjectURL(blob);
    link.setAttribute("href", url);
    link.setAttribute("download", "export.csv");
    link.style.visibility = 'hidden';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}
extractDataAndExportToCSV();

Conseils d’exploitation dans Excel

Pour recréer le tunnel sélectionné dans Piano, vous pouvez filtrer les données dans Excel en utilisant la colonne « Selected » = True :

Si vous souhaitez obtenir la distribution des pages pour une étape précise, il suffit de filtrer sur le numéro d’étape :

Note importante : Ce code se base sur la structure actuelle de la page de Piano Analytics et pourrait ne plus fonctionner en cas de modification de celle-ci. Je ferai de mon mieux pour maintenir le script à jour, mais n’hésitez pas à me signaler tout dysfonctionnement.

Comparer simplement vos tunnels de conversion Piano Analytics

Petit rappel sur la fonctionnalité Tunnel

Nous avions vu dans un article précédent comment la nouvelle fonctionnalité « Tunnel » de Piano Analytics permettait de renforcer les analyses de séquences déjà disponibles dans l’interface. En effet, celle-ci propose de visualiser les volumes, les taux de passages et le temps moyen pour des séquences de plusieurs évènements ou groupes d’évènements que vous allez pouvoir définir :

Comment comparer plusieurs tunnels, sur la métrique de son choix ?

Après plusieurs semaines d’utilisations, j’ai rencontré certains use cases que je ne parvenais pas à gérer à 100% juste via ce module. Les 2 principaux étant :

  • de pouvoir visualiser des métriques autres que uniquement le volume de visites/visiteurs/users (taux de passage, temps passé, taux de sortie)
  • de pouvoir comparer rapidement plusieurs tunnels, en faisant varier un élément.

Pour vous illustrer ces points, imaginez le cas suivant : j’ai 10 visuels d’auto-promotions différentes pour une campagne visant à générer des achats et, au-delà de connaître simplement leur taux/volume de conversions, je souhaite aussi pouvoir comparer leur performance à chacune des étapes de mon tunnel de conversion.

Remplacez maintenant « auto-promotions » par n’importe quel levier d’aide à la conversion comportant des variantes pour vous projeter dans cette problématique (landing pages, AB Test, campagnes de personnalisation…).

Il serait théoriquement possible de le faire, en réalisant manuellement les 10 tunnels, mais il faut bien avouer que cela est fastidieux et qu’un travail manuel de retraitement de la donnée sera de toute façon nécessaire. Et même en faisant cela, je n’aurais toujours pas le temps moyen passé entre chacune de mes étapes.

L’objectif de cet article

L’objectif de cet article va donc être de vous montrer comment exploiter l’API proposé par Tunnel pour automatiser cette tâche, et ainsi obtenir ce type d’information en très peu de temps. La démonstration se fera via Python, mais la logique est applicable au autres langages ou à l’outil de votre choix.

Nous obtiendrons donc :

  • Un tableau récapitulatif des métriques associées à chaque variante de notre tunnels,
  • Pour la métrique de votre choix, un histogramme de comparaison pour chaque variante,
  • Pour la métrique de votre choix, une carte de chaleur composée des étapes et des variantes de votre tunnel.

Pour cet exercice, nous prendrons le cas d’un tunnel composé des étapes suivantes :

  • Le visiteur clique sur une autopromotion, pouvant être présente un peu partout sur le site. J’ai 10 autopromotions différentes, que je voudrais faire varier,
  • Puis le visiteur affiche une page produit,
  • Puis le visiteur ajoute un produit au panier,
  • Puis le visiteur affiche le panier,
  • Puis finalement, le visiteur réalise un achat.

Voici le tunnel dans l’interface, où j’ai filtré sur une des dix auto-promotions en première étape :

Ce cas nous servira de base pour le reste de l’article.

Les données obtenues

Maintenant que nous avons notre cas pratique défini et avant de rentrer dans le code, projetons-nous dans le résultat que nous pouvons obtenir.

Tableaux récapitulatifs des campagnes

Le code retourne tout d’abord un tableau récapitulatif des 10 campagnes d’auto-promotions ayant initié la séquence, avec les métriques proposées par l’API :

  • Visites (nombre de visites ayant été jusqu’au bout du tunnel),
  • Exits (nombre de visites abandonnistes),
  • visits_conversion_rate (taux de conversion global du tunnel),
  • visits_exit_rate (taux de sortie global du tunnel),
  • min_timespent (temps le plus court enregistré pour réaliser le tunnel, en secondes),
  • max_timespent (temps le plus long enregistré pour réaliser le tunnel, en secondes),
  • avg_timespent (temps moyen pour réaliser le tunnel, en secondes).

Cela permet d’avoir un premier aperçu des performances de chaque campagne, et de repérer les métriques intéressantes.

Carte de chaleurs des tunnels

On remarque dans mon exemple que les taux de conversion sont disparates, selon la campagne qui a initié le tunnel. Je souhaite donc pouvoir isoler cette métrique, et l’afficher dans un histogramme :

En plus de cela, je souhaite avoir le détail des taux de passage, entre chacune des étapes.

Voici le résultat, mise en forme via une matrice colorimétrique (plus le taux de passage est important, plus la valeur tend vers le rouge) :

N.B : la première étape a 0 pour taux de conversion, puisque celle-ci ne comporte pas d’étape précédente.

Tunnel de conversion détaillé

Si je souhaite finalement zoomer sur un des tunnels, pour mieux appréhender les différences de valeurs, une dernière fonction me permet de l’afficher de manière plus visuelle :

Pour que vous compreniez mieux le rendu, voici un tunnel, mais cette fois-ci avec la métrique « visits » :

Guide d’utilisation du fichier Python

Maintenant que nous avons vu l’intérêt d’un tel retraitement de données, nous allons voir ensemble, comment utiliser le fichier.

Récupération de l’appel API dans Tunnel

Nous allons d’abord devoir générer un appel API « modèle », qui nous permettra ensuite d’automatiser l’ensemble des requêtes.

Pour cela, configurez votre tunnel comme bon vous semble (nombre d’étapes, filtres, segments…). Une contrainte à respecter : l’étape que vous souhaitez faire varier doit comporter un filtre « est égal à {{la valeur de votre choix}} ». La valeur en elle-même n’est pas importante puisque le script Python va la dynamiser. Voici un exemple :

Une fois cela configuré, cliquez sur le bouton de partage :

Puis copiez la partie « Body », contenant tout le paramétrage API de votre tunnel, et mettez cela de côté dans un bloc-notes :

Récupération de la clé API

Pour requêter vos données Piano Analytics depuis l’extérieur de l’interface, vous allez devoir obtenir une clé API. Cette clé est liée à votre compte Piano Analytics, vous n’aurez donc pas la même que votre collègue. Vous aurez également accès au même périmètre que celui disponible dans votre interface (même liste de niveaux 1).

Une fois connecté à votre compte, allez dans votre profil puis dans l’onglet Api Keys.

 Générez une nouvelle clé et conservez bien l’Access key et surtout la secret key, qui ne vous sera plus donnée par la suite.

Configuration de votre script Python

Pour utiliser ce code, il vous faudra donc une instance Python disponible. Si vous n’en avez pas, vous pouvez utiliser Le service de notebook en ligne de Google.

Vous allez devoir paramétrer quelques variables afin de rendre le code fonctionnel, mais rien d’insurmontable :

  • key : votre clé API,
  • etape : numéro de l’étape qui doit être variabilisée (cela n’est pas obligatoirement la première étape),
  • propriete : nom de la propriété où vous avez placé le filtre « est égal » à variabiliser,
  • valeurs : liste des différentes valeurs que prendra le filtre « est égal ». Pas de limite dans le nombre de valeurs, retenez juste que chaque valeur va générer un appel API. Donc plus il y a de valeurs, plus le temps d’exécution du script sera important,
  • NomsEtapes : vous pouvez renommer les différentes étapes de votre tunnel, afin que cela soit plus parlant lors de la lecture des graphiques…Attention, le nombre de valeurs dans la liste doit exactement correspondre au nombre d’étapes de votre tunnel,
  • jsonFromDQ : reprendre exactement le Json que vous avez récupéré de Data Query à l’étape précédente.
key = "votre_cle_API"
etape = 1
propriete = 'onsitead_format'
valeurs = ["Campagne 1", "Campagne 2", "Campagne 3", "Campagne 4", "Campagne 5", "Campagne 6", "Campagne 7", "Campagne 8", "Campagne 9", "Campagne 10"]
nomsEtapes = ['clic autopromo','page_produit','ajout_panier','page_panier','achat']
jsonFromDQ = {
  "funnel": {
    "scope": "visit_id",
    "steps": [...

Exécutez maintenant le script principal, qui va de manière schématique boucler sur des appels API générés à partir de votre liste de valeurs, puis consolider le tout dans un grand tableau (dataframe Pandas).

Visualisation des données

Comme nous l’avons vu, plusieurs méthodes et fonctions sont à votre disposition pour récupérer l’information. Parcourons-les ensemble.

Les tableaux récapitulatifs

Pour afficher les tableaux récapitulatif des tunnels, il faut cibler la variable globalData, qui va contenir 2 tableaux.

globalData[1] va contenir les chiffres globaux pour chaque tunnel :

globalData[0] va contenir le tableau « brut » de tous les tunnels (si vous souhaitez retravailler les données d’une autre manière) :

Les graphiques

La fonction displaygraph() va afficher 2 graphiques :

  • Un histogramme de comparaison des tunnels sur la métrique de votre choix (trié par valeur décroissante),
  • Une matrice colorimétrique, qui affiche la métrique pour chaque tunnel et chaque étape de votre tunnel.

Vous pouvez personnaliser le 2ième argument de la fonction avec le nom de votre métrique parmi celles disponibles.

Finalement, la fonction detailFunnel() vous permet d’avoir le détail d’un des tunnels, sur la métrique de votre choix. Il y a ici 2 arguments personnalisables :

  • le 2ième, permettant de sélectionner le tunnel de votre choix,
  • le 3ième, permettant de sélectionner la métrique de votre choix.

Conclusion

En conclusion, Tunnel est un outil très utile pour l’analyse de séquences d’événements, mais il peut être limité en termes de métriques disponibles et de comparaison. Cet article a montré comment exploiter l’API proposée par Tunnel pour automatiser cette tâche et obtenir rapidement des informations telles que des tableaux récapitulatifs des métriques associées à chaque variante de Tunnel, des histogrammes de comparaison pour chaque variante, et des cartes de chaleur. Bien que le code présenté ici utilise Python, la logique peut être appliquée à d’autres langages et outils. Cette démonstration montre donc que Tunnel n’est qu’un point de départ vers d’autres analyses plus poussées.

Code source

Voici le code source permettant de récupérer les données de votre scénario d’analyse et d’afficher les graphiques de votre choix :

import pandas as pd
pd.options.mode.chained_assignment = None 
import requests
import json
import numpy as np
import datetime
import time
import re
import plotly.express as px

######## Partie configuration

key = "123ABC" # Votre clé API
etape = 1 #Le numéro de l'étape qui va être variabilisé
propriete = 'onsitead_format' # Nom de la propriété où vous avez placé le filtre "est égal" à variabiliser
valeurs = ["Campagne 1", "Campagne 2", "Campagne 3"] # liste de valeurs qui seront placées dans les filtres de l'étape
nomsEtapes = ['clic autopromo','page_produit','ajout_panier','page_panier','achat'] #vous pouvez renommer les différentes étapes de votre tunnel
jsonFromDQ = { #reprendre exactement le Json que vous avez récupéré dans Data Query
  "funnel": {
    "scope": "visit_id",
    "steps": []
      }
    }

######## Partie déclaration et excecution des données
def apiCall(apikey, url):
  headers = {
    'x-api-key': apikey
  }
  call = requests.get(url, headers=headers)
  data = json.loads(call.content)
  return data

def jsontoTab(json):
  steps = {k: v for k, v in json['DataFeed']['Rows'][0].items() if k.startswith('m_step')}
  tab = []
  ModelingStep = {}
  for step in steps:
    splitKey = step.split('_')
    ModelingStep = {
        '_'.join(splitKey[2:len(splitKey)]) : steps[step],
        'step': int(splitKey[1].split('step')[1])
    }
    tab.append(ModelingStep)
  finalTab = pd.DataFrame(data=tab).groupby(['step']).sum().reset_index()
  return finalTab

def jsontoTabGlobal(json):
  steps = {k: v for k, v in json['DataFeed']['Rows'][0].items() if k.startswith('m_global')}
  newDict = {}
  for step in steps:
    newDict[step.split('m_global_')[1]] = steps[step]
  final = pd.DataFrame(data=newDict,index=[0])
  return final

def urlBuilder(url,step,prop,filter):
    NewDict = url
    NewDict['funnel']['steps'][step-1]['condition']['filter']['$and'][1][prop]['$eq'] = filter
    toStr = json.dumps(NewDict).replace(" ", "")
    urlFinal = 'https://api.atinternet.io/v3/data/getData?param='+toStr
    return urlFinal

def builder(initialUrl, stepToChange,propToChange,ListValues,stepNames):
  listTable = []
  listTableGlobal = []
  for value in ListValues:
    changedUrl = urlBuilder(initialUrl,stepToChange,propToChange,value)
    jsonRaw = apiCall(key,changedUrl)
    tableGlobal = jsontoTabGlobal(jsonRaw)
    tableGlobal['funnel_version'] = value
    tableGlobal['visits'] = ((tableGlobal['exits']/tableGlobal['visits_exit_rate'])-tableGlobal['exits']).round()
    tableGlobal= tableGlobal[['funnel_version','visits','exits','visits_conversion_rate','visits_exit_rate','min_timespent','max_timespent','avg_timespent']]
    listTableGlobal.append(tableGlobal)

    table = jsontoTab(jsonRaw)
    table['funnel_version'] = value
    table['step_name'] = ''
    for i in range(len(stepNames)):
      table['step_name'][table['step'] == i+1] = stepNames[i]
    table = table[['funnel_version','step','step_name','visits','min_timespent','max_timespent','avg_timespent','visits_exits','visits_conversion_rate','visits_exit_rate']]
    listTable.append(table)
  return [pd.concat(listTable),pd.concat(listTableGlobal)]

def displaygraph(data,metric,values):
    fig1 = px.bar(data[1].sort_values(by=[metric], ascending=False), x='funnel_version', y=metric)
    fig1.show()

    globalvalues = []
    for value in values:
      valueList = list(data[0][data[0]['funnel_version'] == value][metric])
      globalvalues.append(valueList)
    fig2 = px.imshow(globalvalues,
                labels=dict(x="Etapes", y="Variante", color=metric),
                color_continuous_scale='RdBu_r',
                x=nomsEtapes,
                y=values,
               text_auto=True,
               width=1000, 
               height=650)
    fig2.update_xaxes(side="top")
    fig2.show()
  
def detailFunnel(data,funnel,metric):
  fig = px.funnel(data[data['funnel_version'] == funnel], x='step_name', y=metric)
  fig.show()
  
globalData = builder(jsonFromDQ,etape,propriete,valeurs,nomsEtapes)

####################### partie visualisation

globalData[0] #Va contenir le tableau "brut" de tous les tunnels
globalData[1] #Va contenir les chiffres globaux pour chaque tunnel
displaygraph(globalData,'visits_conversion_rate',valeurs) #affiche l'hisgramme récap et la matrice colorimétrique, en fonction de la métrique de votre choix
detailFunnel(globalData[0],'Campagne 8','visits') #Donne le détail d'un des tunnels, en fonction de la métrique de votre choix

Exploiter la nouvelle fonctionnalité « tunnel » de Piano Analytics

Piano analytics a mis en ligne il y a quelques semaines une nouvelle fonctionnalité, appelée sobrement « funnel », qui vient compléter le toolkit des analyses séquentielles déjà disponibles. Celle-ci propose de visualiser les volumes, les taux de passages et le temps moyen pour des séquences de plusieurs évènements que vous allez pouvoir définir.

C’est l’occasion de vous expliquer le fonctionnement de cette nouvelle feature, ses principales différences avec les outils de séquences déjà disponibles (l’analyse « Navigation » et la segmentation séquentielle) et finalement quelques use cases afin que vous puissiez vous projeter dans son utilisation. A noter qu’il s’agit à l’heure actuelle d’un MVP, et que de nouvelles fonctionnalités sont à venir très prochainement (enregistrement des templates, partages, breakdown).

Présentation de l’interface

La fonctionnalité est disponible depuis Data Query, en allant dans « Nouveau » > « Créer un rapport tunnel » :

Dans le menu supérieur, vous retrouvez le même menu que Data Query, avec :

  • La sélection de votre/vos site(s) de niveau 1 (et oui, vous pourrez créer un tunnel multi-sites) (1)
  • La selection de la période d’analyse (2)
  • La barre de segment (et oui, vous pourrez segmenter un tunnel) (3)

La zone de gauche va vous proposer de choisir le premier évènement que vous souhaitez cibler dans votre tunnel (4), ou le segment que vous souhaiteriez éventuellement appliquer (5)

Finalement, la bande « configuration » vous propose de choisir la granularité de votre tunnel (visites, visiteurs, users). (6)

Création de votre premier tunnel

Maintenant que l’interface est présentée, créons ensemble notre premier tunnel ! Voici le cas que nous allons traiter : sur un site e-commerce, nous allons recréer un tunnel complet de notre parcours d’achat, en y incluant à la fois les évènements Sales Insights, mais aussi les évènements de connexion au compte (trackés en pages vues).

La première étape pour vous va être ici de déterminer les évènements représentant chaque étape. Dans mon cas les voici :

  • cart.display (affichage du panier)
  • page.display (page de connexion)
  • cart.delivery (choix mode de livraison)
  • cart.payment (choix mode de paiement)
  • transaction.confirmation (confirmation de l’achat)

Nous allons donc placer ces évènements sur la timeline du tunnel :

La deuxième étape sera de définir si des filtres doivent être placés sur certains évènements. Dans mon cas je vais devoir préciser que l’évènement page.display concernant la page de connexion au compte client. Je vais ainsi cliquer sur l’évènement « page.display » et ajouter le filtre « connexion_tunnel_achat » sur la propriété « page » :

Les autres évènements symbolisent déjà l’avancée dans le tunnel d’achat, je n’ai donc pas besoin d’ajouter de filtres sur d’autres étapes. Je souhaite cependant me concentrer sur les paniers importants, je filtre donc ceux ayant au moins 100€ de marchandise :

Vous pouvez également segmenter votre tunnel, pour par exemple ne visualiser que les parcours effectués sur mobile, ou ceux ayant été exposés à une auto-promotion :

Notre tunnel est prêt, il ne reste plus qu’à lancer le calcul, comme un Template Data Query classique !

Astuce 1 : les étapes avec filtres apparaissent avec un petit entonnoir à gauche du nom de d’évènement.

Astuce 2 : vous n’êtes pas obligé de spécifier un évènement dans une étape, vous pouvez uniquement y appliquer des filtres. Ainsi tout évènement correspondant aux filtres sera inclus dans l’étape. Pour cela il suffit d’éditer l’évènement et de sélectionner « any event » dans le menu déroulant.

Analyse d’un tunnel

Votre tunnel maintenant affiché, vous allez pouvoir analyser sur la partie graphique :

  • Le volume de visites à chaque étape (1)
  • Le volume de déperdition à chaque étape (2)
  • Le taux de passage et le taux de sortie, en survolant une des étapes (3)

Sur la partie KPi’s, vous allez retrouver :

  • Les visites (combien de visites étaient présentes à la première étape de mon tunnel ?) (4)
  • La durée moyenne (combien de temps s’est écoulé entre la première et la dernière étape ?) (5)
  • Le taux de conversion (volume de la dernière étape divisé par le volume de la première) (6)
  • Sorties (nombre de visites ayant quitté le tunnel sur une des étapes) (7)

Libre à vous maintenant de modifier votre tunnel en ajoutant une étape, en modifiant votre segment ou encore en supprimant un filtre présent sur une étape !

Les calculs derrière la fonctionnalité

Pour bien utiliser cette fonctionnalité, il convient de comprendre les calculs qui sont effectués en coulisse.

Les étapes des séquences que vous définissez ne sont pas obligatoirement consécutives. Ainsi, d’autres évènements peuvent se glisser entre 2 étapes, ce qui n’empêchera pas la prise en compte de la visite dans le tunnel. Contre intuitif de prime abord, cela me semble un très bon choix, puisque cela vous permettra, par exemple, d’éviter les évènements parasites se glissant dans votre tunnel (par exemple des évènements de clics entre 2 pages car vous avez marqué votre menu principal avec des évènements click.navigation).

Ainsi, si je définis la séquence « chargement page A » > « lecture video B » > « chargement page C », voici les visites qui seront retenues :

Gardez donc en tête qu’une visite réalisant la séquence complète de votre tunnel pourra potentiellement avoir fait plusieurs aller-retours sur le site avant de convertir.

Cette logique est également valable pour les granularités visiteurs et utilisateurs, où seul l’ordre des évènements compte et non leur enchainement.

Les principales différences avec les autres fonctionnalités de séquençage

Comme indiqué en introduction, d’autres outils permettaient déjà de faire du séquençage :

  • la segmentation séquentielle
  • l’analyse navigation

Voyons rapidement les principales différences entre ces outils et quels sont leurs avantages/inconvénients.

La segmentation séquentielle

La segmentation séquentielle va avoir le même mode de calcul que l’analyse tunnel, à savoir la comptabilisation des visites/visiteurs/users ayant suivi un enchainement d’évènements dans un certain ordre, mais pas obligatoirement consécutifs (celui-ci est disponible dans le module de segmentation avancée, via le bouton « puis » ou « then »). Exemple ici :

Il serait donc en théorie possible de recréer un tunnel, en créant plusieurs métriques calculées basées sur des segments séquentiels, et vous obtiendriez le même résultat, mais cela serait bien sûr chronophage. L’avantage est en revanche que vous pouvez placer un taux de passage dans un dashboard ou un template Data Query, ce qui n’est pas possible avec tunnel. Il permet également d’être utilisé comme segment dans l’ensemble de vos analyses Explorer.

L’analyse navigation

L’analyse navigation va, elle, avoir une autre logique de calcul. Celle-ci dispose uniquement d’une granularité « visites », centrée sur les évènements page.display() et la propriété page_full_name (le nom de votre page, concaténé avec ses chapitres). Exemple ici avec le sunburst :

source : support.piano.io

L’analyse va également être concentrée sur les chargements de pages consécutifs, on peut ainsi parler ici de séquences directes (enchainement des évènements les uns derrières les autres), ce qui fait une grosse différence avec tunnel. Je vous place ici le lien vers la documentation Piano, qui explique les détails du mode de calcul.

Finalement, la navigation va plus avoir un rôle d’analyse exploratoire, puisque que vous vous verrez proposer à chaque étape sélectionnée, les étapes suivantes, OU, les étapes précédentes. Cela n’a donc pas le même objectif qu’un tunnel.

source : support.piano.io

Je vous place ci-dessous un tableau récapitulatif des spécificités et usages de chaque fonctionnalité :

Les nouveaux use cases possibles avec l’analyse tunnel

Maintenant que vous avez acquis toutes les subtilités de cette nouvelle analyse, il est maintenant temps de se projeter dans des exemples d’analyses, que vous n’auriez pas pu faire auparavant.

Créer un tunnel avec plusieurs type d’évènements

C’est le fameux cas que j’ai donné comme exemple en introduction. Comme l’analyse navigation ne traite que les évènements de pages et que le tunnel disponible dans Sales Insights ne gère que ses propres évènements, il n’était auparavant pas possible de créer un tunnel mixant plusieurs typologies d’évènements.

Le cas du tunnel d’achat marqué avec des évènements Sales Insights, mais comprenant une étape de connexion marquée avec un évènement page.display() devient maintenant réalisable.

Rassembler plusieurs évènements en une seule étape

Définir les étapes d’un tunnel ne veut pas nécessairement dire qu’elles ne doivent contenir qu’un seul évènement. Reprenons notre exemple précédent avec le tunnel d’achat. Imaginons que je veuille avoir une étape supplémentaire concernant la connexion, à savoir :

  • ceux qui ont été exposés au formulaire de connexion
  • ceux qui ont été au bout du processus de connexion (connexion au compte ou création de compte)

Problème : la connexion et la création d’un comptes sont 2 évènements page.display() différents, avec leur propre label (« connexion_succes » et « validation_creation_compte »).

Je peux cependant définir dans mon tunnel une étape « validation connexion » qui aura un filtre « page = connexion_succes ou validation_creation_compte« , permettant de prendre en compte les 2 cas de figures.

Créer des entonnoirs de conversion

Avec l’exemple précèdent, nous voyons qu’il est possible, en jouant avec les filtres, de rassembler plusieurs évènements au sein d’une étape. Nous pouvons appliquer ce principe en créant un entonnoir de conversion, qui pourra comprendre des milliers d’évènements pour chacune des étapes. Restons sur notre site e-commerce, où nous voudrions connaitre la déperdition de trafic entre la home page et la commande. Nous aurons donc comme étapes :

  • La home page, qui est en soi une page unique
  • Les listes produits, qui sont des centaines de pages
  • les pages produits, qui sont des milliers de pages
  • le panier et la confirmation d’achat, qui sont des évènements uniques

Faire un suivi des conversions multi-visites

Dès que nous sortons d’une conversion à scope visites, les analyses vont devenir plus complexes. En effet, même si l’interface dispose des outils nécessaires pour suivre un visiteur ou un user, la plupart des analyses et des métriques se rapportent à la visite (sources, temps passé, conversions…). Ce module tunnel va nous permettre de visualiser plus facilement des parcours multi-visites, et surtout d’estimer le temps nécessaire pour réaliser une conversion longue.

Imaginons que vous êtes le web analyste de Voyageurs du Monde, une société vous proposant de réaliser des voyages sur mesure. Très clairement, le temps entre la première prise de contact et la validation d’une proposition de voyage va être de l’ordre de plusieurs jours, à plusieurs semaines.

Notre objectif va donc être de suivre ce processus, de la création du compte jusqu’à l’achat et d’en déterminer la déperdition ainsi que le temps moyen. Voici nos étapes clés :

  • La création d’un nouveau compte
  • L’envoi d’une première liste de souhaits
  • la consultation de la proposition de Voyageur du monde (sachant qu’il peut y en avoir plusieurs, en fonction des retours du client)
  • La validation de la proposition (synonyme de paiement)

Nous allons analyser cela sur une période de 6 mois, afin de laisser le temps aux plus indécis d’éventuellement terminer leur parcours.

Cependant nous allons ici avoir un problème : un utilisateur peut aussi bien créer son compte le premier mois que le dernier mois de l’analyse. Ainsi, celui ayant débuté le parcours ciblé le sixième mois aura eu beaucoup moins de temps pour réaliser l’ensemble, impactant donc négativement le taux de conversion global.

Afin d’annuler ce comportement, nous allons appliquer un segment à portée Utilisateurs qui ciblera ceux ayant créé leur compte lors du premier mois de notre période d’analyse. Ainsi, uniquement ces utilisateurs seront suivis en terme de déperdition dans le reste du parcours.

Créer un tunnel omnicanal

Vous ne l’aviez peut-être pas en tête, mais il est possible d’envoyer des évènements offline vers Piano analytics. Retrait d’un produit en magasin, rendez-vous physique, appel call center… sont des évènements offline, que vous pouvez rattacher au reste du parcours online d’un utilisateur. Exemple ici avec un event click&collect, qui sera rattaché à l’utilisateur ayant fait la commande en ligne, tout en ne générant pas de visite dans vos données.

https://logx.xiti.com/event?s=12345&events=
[{"name":"confirmation_retrait_clickandcollect",
"data":{
"user_id":"12345678",
"transaction_id":"T8787878887"
}}]

Pensez bien à déclarer votre évènement personnalisé comme offsite, au risque de générer des visites parasites dans vos données :

Maintenant que ce concept est acquis, mettons nous dans la peau d’un site de génération de leads. Le process est le suivant :
  • Le visiteur arrive depuis une landing page
  • Il consulte l’offre et remplit un formulaire pour une demande de rappel par un conseiller
  • le conseiller parvient à joindre le visiteur au téléphone et lui fait parvenir un contrat (event offline)
  • Le client consulte son espace en ligne, puis signe le contrat envoyé

Bien entendu, il faudra préalablement mettre en place un système de requêtes server to server, afin que le serveur de la centrale d’appels envoie l’information de l’appel réussi en temps réel au serveur Piano Analytics.

A noter que seule la granularité « User » sera disponible pour ce type de tunnel, puisque les evènements offline ne peuvent être rattachés qu’au user_id.

Conclusion

Le module est amené à évoluer à très court terme, aussi cet article sera mis à jour au fur et à mesure des évolutions de tunnel. Comme vous avez pu le voir à travers ces exemples, il permet de répondre dès maintenant à des problématiques qui n’étaient pas réalisables auparavant. il a donc toute sa place dans vos habitudes d’analyses à partir de maintenant.

Automatiser le cleaning de son data model Piano Analytics

Si votre entreprise utilise Piano analytics depuis un certain temps, un grand nombre de propriétés personnalisées ont certainement été créées dans votre data model. Et si plusieurs équipes de votre organisation travaillent de manière indépendante sur Piano Analytics ou si un certain turn over existe, il se peut que vous ayez perdu le fil de ce qui est réellement présent ET utile dans votre data model.

Vous pourriez alors vous retrouver avec un data model dégradé comportant des propriétés :

  • En doublon (ayant la même fonction, pas exactement le même nom mais remontant les mêmes valeurs)
  • Faux doublons (ayant la même fonction mais pas exactement le même nom et par exactement les mêmes valeurs)
  • Non exploitées (plus aucune données ne remontent depuis un certain temps)
  • Mono valeurs (remonte systématiquement la même valeur)

Vous pourriez peut-être être étonné du terme « data model dégradé » que j’ai utilisé. En quoi avoir des doublons ou des propriétés sans donnée peut impacter l’exploitation quotidienne des données de votre entreprise ? Laissez-moi vous avancer quelques arguments.

La problématique d’avoir un data model non optimal

Le stock de propriétés personnalisées n’est pas infini

Selon votre compte, vous bénéficiez de 500 à 1 000 propriétés disponibles dans votre data model. Ce chiffre peut vous paraitre inatteignable mais vous seriez surpris de voir à quel point cela peut aller vite, surtout quand plusieurs équipes se partagent l’outil. Cette limite atteinte, vous ne pourrez plus créer de nouvelle propriété sans upgdrader votre compte ou le cleaner. Le compteur de vos propriétés personnalisées est disponible dans votre data model. Pour avoir le bon compte, vous devez appliquer les filtres suivants :

  • Entités : propriétés personnalisées
  • Statuts : validé
  • Visibilité : Visible ET masquée

Compréhension du data model plus complexe

Il faut avoir en tête que chaque propriété créée sera ajoutée dans la liste des éléments disponibles dans Data Query, dans les critères de segmentation ou encore dans le « croiser avec » d’Explorer…Compliqué de s’y retrouver si cette liste comporte des doublons ou des valeurs sans aucune donnée.

Possibles erreurs d’analyses

Si 2 propriétés ont des noms proches mais ne remontent pas exactement la même information, un utilisateur peut potentiellement les confondre et donc analyser des données qui ne concernaient pas sa recherche initiale. Pire, il peut aussi mettre en place une nouvelle règle de processing dans le data model, venant modifier les données de la mauvaise propriété et ainsi impacter irrémédiablement une autre équipe.

Non transversalité des données

Une propriété pouvant être transverse à plusieurs sites, il est dommage de ne pas harmoniser la manière de faire remonter une information. Si vous avez 2 propriétés quasi identiques mais dont une uniquement utilisée sur votre site web et l’autre uniquement sur votre application, il sera beaucoup plus difficile d’analyser ces 2 valeurs au global.

Notre objectif sera donc d’identifier ces propriétés problématiques afin de les désactiver du data model et ainsi regagner en clarté.

Extraction et première analyse de son data model

Comme nous l’avons vu, vous pouvez accéder à l’ensemble de vos propriétés personnalisées dans votre Data model en filtrant sur « Entités » > « Propriétés personnalisées ». Cette vue vous donne une première lecture de vos propriétés mais l’extract proposé dans « Action » > « Extraire Data model » comporte d’avantage d’informations et nous permettra surtout d’ajouter nos propres annotations.

Voici la définition des différents champs présents dans cet extract :

  • propertyKey : clé unique de la propriété, que vous retrouvez notamment dans l’API, ou qui est à utiliser dans votre marquage
  • Type : format déclaré de la propriété (string, integer, date…)
  • Status : La propriété est-elle encore actives (validated) ? Si oui, alors celle-ci est encore susceptible de remonter des données. Si la propriété est inactive (disabled) elle ne peut alors plus remonter de données, même si votre tracking génère encore des events avec celle-ci
  • Hidden : la propriété est-elle disponible dans les analyses Piano Analytics ? Si elle est cachée (hidden=true), alors elle n’apparaitra dans aucun menu. Attention cependant, elle reste disponible pour remonter des données et elle est également requétable dans l’API.
  • custom : La propriété est-elle personnalisée (= créée par vous) ?
  • Name : Quel est son nom visible dans l’interface ?
  • Description : Quel est la définition renseignée avec la propriété au moment de sa déclaration ?
  • usedInImport : la propriété est-elle utilisée pour réaliser des imports de données externes ?
  • Tags : quels sont les mot-clés qui ont été associés à la propriété au moment de sa déclaration ?

Cette liste comportant l’ensemble du data model, nous allons devoir filtrer sur quelques éléments pour isoler nos propriétés personnalisées :

  • S’isoler uniquement sur les propriétés personnalisées, avec « custom » = « true »
  • Garder uniquement celles qui sont encore actives, avec « Statut » = « validated »

J’aurais tendance à vous conseiller de garder les propriétés cachées « hidden »= »true », puisque celles-ci remontent encore de la données et seraient donc aussi potentiellement cleanables.

C’est normalement déjà fait via l’extract, mais je vous conseille de trier le tableau sur la colonne PropertyKey, afin de voir apparaitre les doublons plus facilement.

Automatiser la détection des propriétés problématiques avec Python

Ce fichier nous donne une première base d’information, mais il ne peut en l’état pas nous mettre en évidence les propriétés sans données, ou celles avec des valeurs similaires. Il faudrait donc le faire manuellement, ligne par ligne, avec l’aide de l’interface.

Nous allons donc agrémenter cela à l’aide d’un petit code qui va aller chercher la présence de données sur chaque propriété grâce à l’API Piano Analytics. A partir de là, nous allons pouvoir automatiser un grand nombre de vérifications (doublon, faux-doublon, aucune donnée…)

Création d’un appel API générique

La première étape avec être de créer l’appel API qui nous servira de base pour déterminer :

  • Si une propriété récolte de la donnée (=nombre d’events où la propriété est non nulle) ?
  • Si oui, quel est le nombre de valeurs associées et quelles sont les top valeurs ?
  • Si ces valeurs se retrouvent dans une autre propriété (=synonyme d’un potentiel doublon) ?

Commençons pas créer le template nécessaire dans data Query :

  1. Sélectionnez la propriété personnalisée de votre choix (peu importe laquelle) avec la métrique « évènements ».
  2. Sélectionnez l’ensemble de vos sites de niveaux 1 via la case « tous vos sites » dans le menu de sélection (et oui, une propriété peut potentiellement remonter sur un S1 obscure dont vous n’avez pas connaissance).
  3. Vous allez maintenant sélectionner la période sur laquelle vous souhaitez vérifier la présence de données. A vous de déterminer la période suffisante pour décider de supprimer une propriété qui ne remonte plus de donnée. De mon côté je vais partir sur les 6 derniers mois.
  4. Comme nous voulons vérifier la présence de données, assurez-vous bien que l’option « Affichez les lignes N/A » est bien décochée.

Vous devriez vous retrouver avec une liste des valeurs associées à votre propriété, avec un volume d’évènements par valeur et au total :

Vous pouvez maintenant prendre l’appel l’API GET (« Ouvrir dans » > « Copier l’URL API GET ») de votre template et le mettre de côté. Celui-ci sera encodé, aussi je vous conseille de le désencoder afin de le rendre plus manipulable pour la suite.

Création de la clé API

Pour requêter vos données Piano Analytics depuis l’extérieur de l’interface, vous allez devoir obtenir une clé API. Cette clé est liée à votre compte Piano Analytics, vous n’aurez donc pas la même que votre collègue. Vous aurez également accès au même périmètre que celui disponible dans votre interface (même liste de niveaux 1).

Une fois connecté à votre compte, allez dans votre profil puis dans l’onglet Api Keys.

 Générez une nouvelle clé et conservez bien l’Access key et surtout la secret key, qui ne vous sera plus donnée par la suite.

Configuration du script Python

Pour utiliser ce code, il vous faudra donc une instance Python disponible. Si vous n’en avez pas, vous pouvez utiliser Le service de notebook en ligne de Google.

Vous allez devoir paramétrer quelques variables afin de rendre le code fonctionnel, mais rien d’insurmontable.

headers = {'x-api-key': "secretKey_accessKey"}
url = 'URL API du template créé précédemment'
topValues = 10 #Nombre de top values qui seront présentées
nbSameValues = 2 #Nombre de valeurs similaires pour ajouter un flag entre 2 propriétés
cheminFichierDM = '/file/datamodel.csv'  # Chemin d'accès au data model 
cheminresultats = '/file/' #chemin de dépot des résultats
  1. La variable headers va contenir la concaténation de votre clé secrète avec votre clé publique, séparées par un « _ ».
  2. La variable url contiendra l’URL API que nous avons créée tout à l’heure. Gardez bien en tête que la période sur laquelle les propriétés seront vérifiées est celle que vous avez utilisée dans votre Template (Les 6 derniers mois dans mon exemple)
  3. La variable topValues vous permet de déterminer le nombre de valeurs d’exemples que vous souhaitez voir apparaitre pour chaque propriété dans le fichier final. Si, par exemple, vous placez « 10 », vous aurez donc les 10 valeurs ayant reçues le plus de trafic sur la période choisie
  4. La variable nbSameValues vous permet de déterminer le nombre de valeurs similaires entre 2 propriétés pour leur ajouter un flag de similarité. Si par exemple la propriété Alpha a pour top valeurs [‘A’,’B’,’C’,’D’,’E’] et Beta [‘D’,’E’,’F’,’G’,’H’], et que vous avez nbSameValues=2, alors les 2 variables seront flaggées comme similaires (et donc à vérifier). Mais si vous aviez configuré nbSameValues=3, il n’y aurait pas eu de flag car seules 2 valeurs sont communes aux 2 variables.
  5. La variable cheminFichierDM indique le chemin d’accès du fichier csv extrait depuis votre data model. Ne modifiez pas le fichier extrait, sous peine de voir le script ne pas fonctionner
  6. Finalement la variable cheminresultats sera le dossier dans lequel les fichiers de résultats seront déposés.

Vous n’avez plus qu’à faire tourner le script complet ! Chaque propriété va générer 2 appels API, qui vont ensuite être traités. Il se peut donc que l’exécution dure plusieurs minutes.

Analyse des résultats

Le script va générer 2 fichiers CSV, stats.csv et check_data_DM.csv. Parcourons-les rapidement ensemble.

Comprendre la qualité de votre data model

Le fichier stats.csv vous indique les métriques clés pour comprendre les principaux points d’attention à avoir concernant votre data model.

  1. Custom props : Nombre total de propriétés personnalisées testées dans le script
  2. Nombre de propriétés à vérifier : nombre de propriétés avec au moins un discriminant (points 3 à 6)
  3. Props sans donnée : nombre de propriétés sans aucune donnée sur la période (= aucun event où la propriété testée avait une valeur non nulle)
  4. Props avec doublons : nombre de propriétés ayant un nom identique à une autre, synonyme de potentiels doublons
  5. Props avec valeur unique : nombre de propriétés n’ayant qu’une seule valeur associée
  6. Props avec valeurs similaires : nombre de propriétés ayant au moins X valeurs identiques avec une autre propriété (X étant le nombre défini dans la variable nbSameValues).

Analyser dans le détail les propriétés problématiques

Le fichier check_data_DM.csv reprend les colonnes de départ du data model, filtré sur les propriétés personnalisées et validées. Des nouvelles colonnes ont été ajoutées par le script pour vous aider dans vos travaux :

  1. Statut appel : retourne ‘OK’ si l’appel API a bien pu s’effectuer
  2. Nb résultats : Nombre d’évènements où la propriété avait une valeur non nulle sur la période définie. Vous pouvez filtrer sur « 0 » pour isoler les propriétés sans donnée.
  3. sample : Top valeurs pour la propriété analysée, sous la forme d’une liste
  4. Nombre de valeurs différentes : nombre de valeurs différentes pour la propriété analysée. Vous pouvez filtrer sur « 1 » pour isoler les propriétés qui ne remontent qu’une seule valeur
  5. Doublon ? : retourne la liste des propriétés ayant le même nom (colonne « name ») que la propriété analysée. Filtrez sur une des combinaisons pour analyser ces doublons
  6. Valeurs similaires avec une autre propriété ? : retourne la liste des propriétés ayant au moins X valeurs identiques avec la propriété analysée. Filtrez sur une des combinaisons pour analyser ces propriétés aux valeurs similaires
  7. Colonne avec discriminant(s) : indique True si la propriété possède au moins un des discriminants.

Conclusion

Vous avez maintenant toutes les informations pour déterminer les propriétés à conserver de celles qui doivent être désactivées ou fusionnées.

Une propriété sans discriminant ne veut pas dire qu’elle ne mérite pas votre attention. Elle peut par exemple remonter un volume très faible de trafic (quelques centaines d’events) et donc être synonyme d’une remontée partielle des données. Vous pouvez également constater des valeurs obsolètes dans les valeurs d’exemples données dans la colonne « Sample ».

Finalement, ce cleaning sera inlassablement à refaire sans stratégie de gestion de votre data model. Pensez à mettre en place ou gouvernance solide pour la déclaration et le maintien dans le temps des nouvelles propriétés.

Bonne chance 🙂

Le script complet

import pandas as pd
import requests
import json
import numpy as np
import datetime
import time
pd.options.mode.chained_assignment = None

headers = {'x-api-key': "secretKey_accessKey"}
url = 'URL API du template créé précédemment'
topValues = 10 #Nombre de top values qui seront présentées
nbSameValues = 2 #Nombre de valeurs similaires pour ajouter un flag entre 2 propriétés
cheminFichierDM = '/file/datamodel.csv'  # Chemin d'accès au data model 
cheminresultats = '/file/' #chemin de dépot des résultats

props = pd.read_csv(cheminFichierDM, sep=";", decimal=',', encoding="UTF-8")
props = props[(props['custom'] == True)&(props['status'] =="validated")]
props['Statut appel'] = ''
props['nb resultats'] = ''
props['sample'] = ''
props['nombre de valeurs différentes'] = ''
props['doublon ?'] = ''
props['valeur similaires avec une autre propriété ?'] = ''
urlRootRows = 'https://api.atinternet.io/v3/data/getData?param='
urlRootTotal = 'https://api.atinternet.io/v3/data/getTotal?param='
jsonRowsCall = json.loads(url.split('?param=')[1])
ParamsToRemove = ['sort','max-results','page-num']
jsonTotalCall = json.loads(url.split('?param=')[1])
[jsonTotalCall.pop(x) for x in ParamsToRemove]

for index, row in props.iterrows():
  try:
    jsonRowsCall['columns'] = 
,"m_events"] jsonTotalCall['columns'] =
,"m_events"] call = requests.get(urlRootRows+json.dumps(jsonRowsCall), headers=headers) data = json.loads(call.content) values = [] for i in list(data['DataFeed']['Rows'])[0:topValues]: values.append(i
]) props['sample'][index] = values props['nombre de valeurs différentes'][index] = len(list(data['DataFeed']['Rows'])) call = requests.get(urlRootTotal+json.dumps(jsonTotalCall), headers=headers) data = json.loads(call.content) props['nb resultats'][index] = data['DataFeed']['Rows'][0]["m_events"] props['Statut appel'][index] = 'OK' except: props['Statut appel'][index] = 'KO' for index, row in props.iterrows(): testDoublon = props[props['name'] == row['name']] if len(testDoublon) >1: props['doublon ?'][index] = list(testDoublon['propertyKey']) props['valeur similaires avec une autre propriété ?'][index] = [] for indexbis, rowbis in props.iterrows(): if(index != indexbis): checkSimilars = list(set(row['sample']).intersection(rowbis['sample'])) if len(checkSimilars) >= nbSameValues: props['valeur similaires avec une autre propriété ?'][index].append(rowbis['propertyKey']) props['colonne avec discriminant(s)'] = False props['colonne avec discriminant(s)'][(props['nb resultats'] == 0) | (props['doublon ?'] != '') | (props['nombre de valeurs différentes'] == 1) | (props['valeur similaires avec une autre propriété ?'].astype(str) != '[]')] = True props.to_csv(cheminresultats+'check_data_DM.csv', sep=";", decimal=',',encoding='utf-8-sig') #Enregistrement du tableau final stats = { 'Custom props':len(props), 'Nombre de propriétés à vérifier': len(props[(props['nb resultats'] == 0) | (props['doublon ?'] != '') | (props['nombre de valeurs différentes'] == 1) | (props['valeur similaires avec une autre propriété ?'].astype(str) != '[]')]), 'Props sans donnée': len(props[props['nb resultats'] == 0]), 'Props avec doublons' : len(props[props['doublon ?'] != '']), 'Props avec valeur unique':len(props[props['nombre de valeurs différentes'] == 1]), 'Props avec valeurs similaires': len(props[props['valeur similaires avec une autre propriété ?'].astype(str) != '[]']) } stats = pd.DataFrame({'propriétés':list(stats.values())}, index=list(stats.keys())) stats.to_csv(cheminresultats+'stats.csv', sep=";", decimal=',',encoding='utf-8-sig')

Trust Commander : comment exploiter l’API pour monitorer votre CMP ?

Optimiser le consentement des visiteurs sur nos environnements digitaux est devenu en quelques années un enjeu stratégique majeur pour les sites web et les applications. Les CMP (Consent Management Plateform) se sont développées et ont largement été adoptées par les entreprises afin de fiabiliser la collecte du consentement, maintenir à jour la bannière, ou encore monitorer la performance de celle-ci (taux d’interaction, taux d’optin/d’optout etc.).

C’est justement sur ce dernier point que je souhaite aujourd’hui m’attarder. En effet les CMP proposent des outils de reporting en sein de leur interface afin d’avoir une première lecture de la performance de votre bannière.

Mais il peut arrivez que vous ayez besoin d’extraire ces données, afin d’aller plus loin dans votre analyse ou alors pour injecter la donnée dans un outil tiers (outil de Bi, Data Lake etc.). C’est dans ce contexte que les API proposées par certaines CMP peuvent devenir fort utiles.

Cependant, comme toute API, un travail de préparation préalable est nécessaire afin de comprendre comment celle-ci fonctionne (token, variables, format de retour etc.) et la manière dont nous ait retournée la donnée (imbrications du Json, systèmes d’id, recalcul de la donnée etc.). Je voudrais donc dans cet article vous partager mon retour d’expérience sur l’utilisation de l’API Trust Commander et vous donner un maximum de tips si vous envisagez vous aussi de l’exploiter.

Trust Commander et son outil  Dashboard 

L’API que nous allons donc voir dans cet article est celle de Trust Commander, la CMP proposée par Commanders Act.

L’outil propose par défaut un espace « dashboard », affichant les statistiques principales de différentes bannières créées sur votre compte :

Même si les statistiques données sont claires, vous noterez la présence d’info bulles donnant une définition très précise de chaque chiffre (ex : »Taux d’interaction : Le nombre de visiteurs qui interagissent avec au moins un élément du 1ier écran »). Il existe également une page dédiée dans la documentation en ligne de Commanders Act.

On peut constater 3 filtres :

  • Privacy (choix de la bannière)
  • Appareil (choix des devices)
  • User location (Union Européenne ou hors UE).

S’ajoute à cela un filtre de dates, avec des périodes prédéfinies (aujourd’hui, la semaine dernière, les 30 derniers jours) ou une période custom à sélectionner.

En complément des fonctionnalités préexistantes, l’API s’est avérée utile dans mon cas car j’avais besoin d’afficher des courbes d’évolutions de certains indicateurs, pouvoir les ventiler par device et par bannière, mais surtout d’injecter les données dans un outil tiers.

Fonctionnement de l’API trust Commander

L’API Trust Commander va nous donc permettre d’extraire la donnée de notre CMP à la granularité et la fréquence de notre choix.

Une page de l’aide en ligne lui est dédiée dans Commanders Act.

voyons maintenant ensemble comment l’exploiter !

Récupération de votre Token

Comme on peut le voir dans la documentation, la première étape pour nous sera de récupérer votre Token d’authentification. Sans cela, vous ne pourrez pas requêter l’API.

Pour l’obtenir il faudra contacter votre chargé de compte ou le support Commanders Act (support@commandersact.com). Si ça n’est pas déjà fait, je vous conseille d’effectuer cette demande dès maintenant afin d’obtenir votre token pour la suite de cet article.

Configuration de votre URL racine

L’API Trust Commander se compose d’une URL racine et de paramètres, faisant office de filtres.

La première étape pour nous va donc être de personnaliser l’URL en y ajoutant l’ID de votre compte :

https://api-internal.commander1.com/v2/siteId/privacy/statistics

Si vous ne la connaissez pas, vous pouvez la retrouver directement dans l’URL lorsque vous êtes dans Trust Commander :

Dans mon cas, l’URL racine devient ainsi :

https://api-internal.commander1.com/v2/1234/privacy/statistics

Configuration des filtres

Les filtres disponibles dans l’API Trust Commander sont exactement les mêmes que ceux présents dans l’interface, puisqu’elle requête elle même cette API.

Nous allons donc retrouver les filtres de dates, de devices, de campagnes et de localisation de l’utilisateur (tableau disponible dans la documentation Trust Commander) :

Ainsi, si je souhaite récupérer mes données pour le mois de juillet 2022, uniquement sur Desktop, pour les européens, mon URL sera alors (sans espace) :

https://api-internal.commander1.com/v2/1234/privacy/statistics

?token=123456789

&filter[begin_date]=2022-07-01

&filter[end_date]=2022-07-31

&filter[sup_filters][location][]=1

&filter[sup_filters][device][]=3

Vous pouvez tester votre API directement depuis votre navigateur, ou bien depuis un outil spécialisé. De mon côté j’utilise l’extension Thunder Client disponible dans Visual Studio Code. Pour que votre appel fonctionne correctement, vous devez obtenir une réponse 200 avec un JSON contenant des données :

Il n’y a pas beaucoup plus à dire sur cette API très simple d’utilisation…à part peut-être le multi-filtres !

En effet si vous voulez filtrer sur plusieurs valeurs d’un même critère, par exemple isoler les mobiles et les tablettes pour les devices, vous devrez ajouter autant de fois le paramètre du filtre. Dans mon cas cela sera :

https://api-internal.commander1.com/v2/1234/privacy/statistics

?token=123456789

&filter[begin_date]=2022-07-01

&filter[end_date]=2022-07-31

&filter[sup_filters][location][]=1

&filter[sup_filters][device][]=1

&filter[sup_filters][device][]=2

Dernière astuce, vous pouvez d’abord créer vos filtres dans le dashboard trust Commander, puis récupérer la configuration de l’appel directement dans le network de votre console, en filtrant sur « https://api-internal.commander1.com/v2 » , puis en allant dans payload et en cliquant finalement sur « View decoded » (illustation ci-dessous) :

Structure du Json Trust Commander

Avant de crier victoire et se lancer dans l’industrialisation du requêtage de l’API, il est important de comprendre la structure du JSON qui nous ait retournée. Pour visualiser cela, je vais utiliser l’outil Json Visio (vous pouvez en faire de même dans vos projets). Voici ce que cela donne :

Nous avons donc un objet « data » contenant toutes les données. Celui-ci est constitué d’un tableau d’objets, contenant à chaque fois un type (ici « privacy/statistic » et un id). Chacun de ces objets représente une bannière Trust Commander, et au sein de cet objet, nous retrouvons un certain nombre d’attributs qui sont les données liées à la bannière.

Retrouver la correspondance entre le nom de la bannière et son ID

Comme vous l’aurez compris, nous n’avons pas directement le nom de la bannière, mais son ID.

Pour retrouver à quoi correspond l’ID de votre bannière, vous pouvez aller dans sa zone d’édition et rechercher dans l’URL la valeur présente après « settings » :

Pour récupérer toutes les correspondances en une seule fois, vous pouvez également exécuter ce petit code dans votre console, lorsque vous êtes dans le dashboard Trust Commander :

let table = document
  .querySelector("#statistics_summary")
  .querySelector("thead")
  .querySelectorAll("th");
let finalTable = [];

table.forEach((banner) => {
  if (banner.className) {
    finalTable.push({
      name: banner.innerHTML,
      id: banner.className.split('-')[1]
    });
  }
});

console.table(finalTable);

Voici ce que vous devriez obtenir :

Maintenant que nous avons pu traduire nos ID de bannières avec leur nom, nous pouvons nous attaquer aux données présentes dans ceux-ci !

Comprendre la signification des attributs

Une fois que nous sommes dans les attributs de la bannière, il n’y a plus de difficulté particulière, techniquement parlant. Il suffit maintenant de comprendre les correspondances entre les libellés et les définitions de chaque donnée dans le Dashboard Trust et le nom des attributs dans l’API.

Pour vous aider, voici une petite compilation des correspondances du dashboard et des attributs API (avec les définitions, si elle sont présentes) : Fichier Correspondance.

Comme vous le verrez, 2 typologies d’attributs seront probablement différentes dans votre cas : Les volumes d’optin sur les catégories (ex : « nb_optin_category_1 ») et les vendors (ex : « nb_optin_vendor_3 »).

En effet les catégories et les vendors sont définis par vous lors de l’élaboration de vos bannières. La liste est donc unique à chaque compte. Pour retrouver facilement les correspondances entre les ID et les noms des catégories/vendors, vous pouvez vous rendre dans les pages « Gestions des catégories et Gestion de partenaires de Trust Commander :

Vous avez maintenant normalement tous les éléments pour comprendre au mieux le contenu de ce JSON ! Il nous reste maintenant à requêter cette API de manière plus industrialisée.

Récupération de la granularité de données souhaitée en Python via l’API

Maintenant que nous avons réalisé notre premier appel et avoir compris le contenu de celui-ci, il nous reste à construire le code/process permettant de répondre à notre problématique.

Ce que nous souhaitons récupérer comme données

La première étape est de bien définir ce que nous souhaitons comme résultat final.

Dans mon cas je souhaitais récupérer pour toutes mes bannières l’ensemble des données disponibles dans un appel :

  • Pour chaque jour depuis le 1ier janvier 2022
  • Pour chaque Device
  • Uniquement pour l’Union Européenne

Et pour réaliser cela, je fais le choix pour cet article d’utiliser le langage Python ! Si vous n’avez pas d’instance Python sur votre ordinateur, vous pouvez utiliser Le service de notebook en ligne de Google.

Réaliser un premier appel API en Python (guide pas à pas)

Avant toute chose, entrainons-nous à réaliser un appel simple. Nous allons utiliser cette API, qui est déjà dans le format dont nous aurons besoin :

  • Sur une journée (le 01/07/2022)
  • Sur un device précis
  • Sur les données EU

https://api-internal.commander1.com/v2/1234/privacy/statistics?token=123456789&filter[begin_date]=2022-07-01&filter[end_date]=2022-07-01&filter[sup_filters][location][]=1&filter[sup_filters][device][]=3

Pour ce faire, nous allons utiliser la librairie requests et nous allons créer 2 paramètres :

  • url, qui contiendra la racine de l’URL API
  • params, qui contiendra la query string

On stock finalement le résultat retourné dans la variable ‘data’ via la propriété .content de requests.

Voici le code (j’ai tout de suite appelé l’ensemble des librairies dont nous aurons besoin dans le projet) :

import pandas as pd
import requests
import json
import numpy as np
import datetime
import time

url = "https://api-internal.commander1.com/v2/1234/privacy/statistics"

params = {'token': '123456789',
            'filter[begin_date]': '2022-07-01', 
            'filter[end_date]': '2022-07-01',
            'filter[sup_filters][device][]':3,
            'filter[sup_filters][location][]':1
            }
data = requests.get(url, params=params).content
data

Nous allons maintenant transformer ce texte en JSON via la librairie…json, rentrer dans l’objet ‘data’ (cf partie précédente) et tenter de pousser le résultat dans un dataframe Pandas.

data = requests.get(url, params=params).content
data = json.loads(data)['data']
pd.DataFrame(data)

On commence à avoir un début de quelque chose mais cela ne va pas être encore exploitable. En effet toutes les données qui nous intéressent sont dans l’objet « Attributes » qui est encore sous format Json.

Nous allons donc plutôt créer un tableau, qui ira récupérer l’objet « Attributes » pour chaque valeur de notre tableau data. Nous poussons finalement le résultat dans un nouveau DataFrame :

data = requests.get(url, params=params).content
data = json.loads(data)['data']
listValues = []
for values in data: 
  listValues.append(values['attributes'])
pd.DataFrame(listValues)

Voilà qui est beaucoup mieux ! Nous avons maintenant chaque colonne correspondant à une métrique et chaque ligne correspondant à une bannière…mais laquelle ? L’information de l’ID de bannière de trouvait dans l’objet supérieur à attributes, nous n’avons donc pas cette information dans le tableau. Ajoutons lors de notre boucle une nouvelle propriété « banner id » qui viendra s’ajouter parmi les autres colonnes :

data = requests.get(url, params=params).content
data = json.loads(data)['data']
listValues = []
for values in data: 
  values['attributes']['banner id'] = values['id'] #ajout de l'id de la bannière dans l'objet 'attributes'
  listValues.append(values['attributes'])

table = pd.DataFrame(listValues) # création de la variable table, qui stockera notre dataFrame
table[['banner id','nb_visitors_exposed_to_privacy','nb_banner_views']]

Notre premier tableau étant complet, voyons comment nous pouvons récupérer le reste des données.

Récupérer les données à la granularité souhaitée

Obtenir des données journalières

Nous l’avons vu, l’API trust Commander ne ventile pas les données à la granularité jour. Pour le réaliser, nous allons donc faire un appel pour chaque jour de la période souhaitée, puis reconstituer un tableau de données.

Nous créons une première variable ‘start’ à partir de la date de notre choix via la librairie Datetime. Nous créons une deuxième variable ‘end’ qui correspondra à la date actuelle moins une journée. Nous créons finalement une liste de dates pour chaque journée présente entre ‘start’ et ‘end’.

Libre à vous de personnaliser les dates selon vos besoins !

start = datetime.datetime.strptime("2022-08-15", "%Y-%m-%d") #création de la date de début
end = (datetime.datetime.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') # date actuelle moins une journée
date_generated = pd.date_range(start, end) # création d'une liste de dates entre les 2 bornes
date_generated

Il nous reste maintenant à intégrer cette liste dans une série d’appels API via une boucle. Voici le code complet :

import pandas as pd
import requests
import json
import numpy as np
import datetime
import time

url = "https://api-internal.commander1.com/v2/1491/privacy/statistics"
listValues = []

#Définition de la liste de date
start = datetime.datetime.strptime("2022-08-15", "%Y-%m-%d")
end = (datetime.datetime.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
date_generated = pd.date_range(start, end)

#Création d'une boucle à partir de nos dates
for dates in date_generated:
  params = {'token': '123456789',
              'filter[begin_date]': str(dates), #Dynamisation des dates dans l'appel API
              'filter[end_date]': str(dates), #La date de début sera la même que la date de fin
              'filter[sup_filters][device][]':3,
              'filter[sup_filters][location][]':1
              }

  data = requests.get(url, params=params).content
  data = json.loads(data)['data']
  
  for values in data: 
    values['attributes']['banner id'] = values['id']
    values['attributes']['date'] = dates #Ajout de la date parmi les colonnes du tableau final
    listValues.append(values['attributes'])

table = pd.DataFrame(listValues) #compilation de l'ensemble des appels
table[['banner id','date','nb_visitors_exposed_to_privacy','nb_banner_views']]

Obtenir des données pour chaque device

Pour rappel, les devices sont repartis dans 4 id différents (0,1,2,3). Il nous suffit donc de réappliquer la même logique que celle des dates, en incluant une nouvelle série de boucles sur ces ID :

import pandas as pd
import requests
import json
import numpy as np
import datetime
import time

url = "https://api-internal.commander1.com/v2/1491/privacy/statistics"
listValues = []

start = datetime.datetime.strptime("2022-08-15", "%Y-%m-%d")
end = (datetime.datetime.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
date_generated = pd.date_range(start, end)

#Création d'une liste des ID de device
devicesList = [0,1,2,3]

for dates in date_generated:
  for device in devicesList:
    params = {'token': '123456789',
                'filter[begin_date]': str(dates), 
                'filter[end_date]': str(dates), 
                'filter[sup_filters][device][]':device, #Dynamisation du device ID dans l'appel API
                'filter[sup_filters][location][]':1
                }

    data = requests.get(url, params=params).content
    data = json.loads(data)['data']
    
    for values in data: 
      values['attributes']['banner id'] = values['id']
      values['attributes']['date'] = dates 
      values['attributes']['Device'] = device #Ajout du device parmi les colonnes
      listValues.append(values['attributes'])

table = pd.DataFrame(listValues) #compilation de l'ensemble des appels
table[['banner id','date','Device','nb_visitors_exposed_to_privacy','nb_banner_views']]

Renommer les colonnes et enregistrer les données

Comme nous avons déjà récupéré les traductions des colonnes dans un chapitre précédent, nous pouvons les renommer afin de les rendre plus compréhensibles. Je ne vais renommer ici que les colonnes génériques, mais libre à vous de vous occuper également des catégories et des vendors.

Pour cela j’utilise la méthode pd.read_csv() pour récupérer le tableau de correspondance que je vous ai donné tout à l’heure pour renommer mes colonnes via une boucle sur la méthode .rename()

J’enregistre finalement le résultat dans un fichier CSV :

table = pd.DataFrame(listValues) #compilation de l'ensemble des appels

translation = pd.read_csv('/files/trust/translate.csv', sep=";", decimal=',', encoding="latin1") #import de du tableau de correspondance des colonnes
for index, row in translation.iterrows():
      table = table.rename(columns={row['Attribut Json']:row['NOM'].strip()}) #Renommage des colonnes

table.to_csv('/files/trust/trust_practice.csv', sep=";", decimal=',') #Enregistrement du tableau final

Nous avons maintenant notre fichier CSV exploitable dans un outil de dashboarding :

Gestion des mises à jour des données

Nous pourrions très bien ré-exécuter le script sur l’entièreté de la période chaque jour afin de récupérer les données de la veille. Cependant cette méthode n’est pas très optimisée puisque vous allez chaque jour exécuter un très grand nombre de requêtes API pour récupérer uniquement une nouvelle journée de données.

Nous allons donc rédiger une variante de notre premier script, qui lancera uniquement les appels API depuis la dernière mise à jour du fichier puis l’écraser.

Nous allons appliquer la procédure suivante :

  • Import du fichier d’historique
  • Extraction de la date la plus récente présente dans la colonne ‘date’, qui deviendra la date de début des appels
  • Exécution des requêtes API
  • Fusion du tableau des nouvelles données avec le fichier historique

Voici le code adapté :

import pandas as pd
import requests
import json
import numpy as np
import datetime
import time

#Import de l'historique
history = pd.read_csv('/files/trust/trust_practice.csv', sep=";", decimal=',', index_col=[0])

#Extraction de la dernière date connue dans le fichier, qui sera la nouvelle date de début
start = datetime.datetime.strptime(history['date'].max(), "%Y-%m-%d") + datetime.timedelta(days=1)
end = (datetime.datetime.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
date_generated = pd.date_range(start, end)
devicesList = [0,1,2,3]
url = "https://api-internal.commander1.com/v2/1234/privacy/statistics"
listValues = []

#Condition permettant de vérifier si au moins une journée s'est passée depuis la dernière mise à jour
if len(date_generated) > 0:
  for dates in date_generated:
    for device in devicesList:
      params = {'token': '123456789',
                  'filter[begin_date]': str(dates)[0:10], 
                  'filter[end_date]': str(dates)[0:10], 
                  'filter[sup_filters][device][]':device,
                  'filter[sup_filters][location][]':1
                  }

      data = requests.get(url, params=params).content
      data = json.loads(data)['data']
      
      for values in data: 
        values['attributes']['banner id'] = values['id']
        values['attributes']['date'] = dates 
        values['attributes']['Device'] = device 
        listValues.append(values['attributes'])

  table = pd.DataFrame(listValues) 


  translation = pd.read_csv('/files/trust/Correspondance_db_api.csv', sep=";", decimal=',', encoding="latin1") #import de du tableau de correspondance des colonnes
  for index, row in translation.iterrows():
        table = table.rename(columns={row['Attribut Json']:row['NOM'].strip()})

  table = pd.concat([history,table], ignore_index=True) #Fusion de la nouvelle table et de l'historique
  table.to_csv('/files/trust/trust_practice.csv', sep=";", decimal=',') #Enregistrement du tableau final

Afin de ne pas avoir à lancer la mise à jour manuellement, vous pouvez utiliser un scheduler qui le lancera pour vous automatiquement sur un serveur.

Conclusion

Nous avons découvert à travers cet article comment comprendre et exploiter l’API de Trust Commander et obtenir le maximum de granularité dans nos données CMP. J’ai utilisé de mon côté Python mais vous pouvez tout à fait utiliser un autre système (autre langage, solution cloud, outil de BI).

Piano Analytics et Power Bi/Excel : niveau avancé

Nous avions vus dans un article précédent comment le module Power Query, commun aux logiciels Excel et Power Bi, permettait de réaliser des appels API de la solution Piano Analytics et ainsi récupérer ses données analytics dans ses rapports/dashboards. 

Je vous conseille de faire un tour sur cet article si vous n’êtes pas à l’aise avec Power Query  ou l’API Piano avant de continuer cette lecture.

La limite des 10 000 lignes

Nous savons comment récupérer les données via l’API, mais nous pouvons être confrontés à un problème lorsque nos appels sont détaillés : atteindre la limite des 10 000 lignes récupérables dans un appel.

En effet l’API Piano analytics fonctionne avec un système de pagination, où vous définissez le nombre de résultats souhaités dans l’appel (10 000 maximum), et quelle page de résultats vous souhaitez récupérer.

Par exemple, si j’indique un nombre de résultats à 50 et la page de résultat à 2 (« max-results »:50, »page-num »:2), j’obtiendrais les lignes de résultats 51 à 100.

Comme la limite est de 10 000 lignes, si une requête me retourne 30 000 résultats, je devrais alors réaliser 3 appels API sur les pages 1,2 et 3 pour récupérer l’ensemble de mes données. Or, dans une source de données Power Query classique, je ne peux effectuer qu’un seul appel à la fois. 

Nous pourrions alors tout à fait imaginer créer 3 sources de données et simplement faire varier le numéro de page pour avoir l’ensemble des données : 

  • « max-results »:10000, »page-num »:1
  • « max-results »:10000, »page-num »:2
  • « max-results »:10000, »page-num »:3

Il suffirait ensuite d’utiliser la fonction « ajouter des requêtes » de Power Query pour ne créer qu’un seul tableau regroupant ces 3 requêtes.

Cette possibilité n’est cependant pas optimale car elle va générer quelques désagréments : 

  • Cela n’est pas extensible à l’infinie, si vous devez par exemple récupérer 200 000 lignes (maximum autorisé) vous devrez faire 20 appels différents…
  • Cela va « polluer » votre vue dans power Query, puisque vous aurez potentiellement des dizaines d’appels
  • Cela n’est pas dynamique. Si sur avril la requête vous retournait 30 000 résultats, vous n’êtes pas à l’abris qu’elle passe à 50 000 sur le mois de mai

Pour ces raisons, nous allons essayer de développer une technique permettant de requêter dynamiquement autant de pages de résultats nécessaires en fonction du volume de données à récupérer sur la période. Et quand je parle de « technique » je parle en réalité d’une fonction personnalisée Power Query que nous allons écrire ensemble.

Pour ceux souhaitant simplement récupérer le code de la fonction mais sans en comprendre le fonctionnement, vous pouvez vous rendre directement à la fin de l’article.

Objectifs de la fonction personnalisée

Avant de nous lancer dans l’écriture de la fonction, nous devons définir ce qu’elle doit faire. Voici une liste des étapes à réaliser : 

  • La fonction va devoir en premier lieu déterminer le nombre de lignes de résultats présentes dans notre requête API (l’API Piano Analytics dispose de fonctionnalités pour récupérer cette information, nous verrons cela par la suite)
  • A partir de ce nombre de lignes, en déduire ne nombre d’appels qu’il faudra réaliser, sachant que le nombre maximum de lignes récupérables est de 10 000
  • Générer dynamiquement autant d’appels API qu’il y a de pages de résultats et en récupérer les données
  • Fusionner les résultats de l’ensemble des requêtes dans un tableau

Ecriture de la fonction

Comprendre le fonctionnement de l’API « getRowCount »

Quand on se trouve dans Data Query, l’interface nous indique bien le nombre de résultats d’une requête (dans mon cas 62 452) : 

Une image contenant texte

Description générée automatiquement

Vous ne trouverez en revanche pas cette information lors d’un appel API classique (Bouton « partage » > «API GET ») :

https://api.atinternet.io/v3/data/getData?param={"columns":["date","src","page","m_visits"],"sort":["-m_visits"],"space":{"s":[592201]},"period":{"p1":[{"type":"D","start":"2022-01-01","end":"2022-03-31"}]},"max-results":50,"page-num":1,"options":{}}

Une image contenant texte

Description générée automatiquement

Fort heureusement, la documentation API nous propose une méthode pour obtenir cette information : le getRowCount

Il suffit alors de modifier le terme getData par getRowCount dans l’url de l’API : 

https://api.atinternet.io/v3/data/getRowCount?param={"columns":["date","src","page","m_visits"],"sort":["-m_visits"],"space":{"s":[592201]},"period":{"p1":[{"type":"D","start":"2022-01-01","end":"2022-03-31"}]},"max-results":50,"page-num":1,"options":{}}

On va cependant obtenir ce message d’erreur si nous n’effectuons que cette modification :

Une image contenant texte

Description générée automatiquement

En effet le getRowcount va refuser une partie des informations contenues dans notre appel de base, car ne faisant pas sens dans ce contexte : 

  • Sort
  • Page-num
  • Max-results

Nous le supprimons donc de notre appel pour ne conserver que les informations liées aux données (columns, space, période, segment, filter et options) :

https://api.atinternet.io/v3/data/getRowCount?param={"columns":["date","src","page","m_visits"],"space":{"s":[592201]},"period":{"p1":[{"type":"D","start":"2022-01-01","end":"2022-03-31"}]} ,"options":{}}

Une image contenant texte

Description générée automatiquement

Modification d’un appel classique en appel rowcount dans Power Query

Nous savons maintenant comment récupérer notre rowCount ! Notons nous maintenant ce que Power Query devra faire à partir de l’url API donnée par Data Query : 

  • Changer le terme getData par getRowCount dans l’url de l’API 
  • Ne conserver que les propriétés columns, space, période, segment, filter et options 
  • Lancer l’appel Api et récupérer la valeur présente dans le json.

Isoler la partie paramètres d’un appel

Avant de créer directement une fonction personnalisée, nous allons nous entrainer en ne traitant qu’une seule requête fixe. Je vous invite donc à générer également de votre côté un appel API retournant plus de 10 000 lignes pour suivre les instructions. 

Dans Power Query, créez une nouvelle source vide (accueil > nouvelles source > requête vide). Collez maintenant votre URL API dans le champ d’édition de l’étape par défaut « source » :

Une image contenant texte

Description générée automatiquement

Nous allons maintenant essentiellement travailler dans l’éditeur avancé afin de construire les retraitements souhaités. On va créer 2 variables via la méthode « split() » : une contenant la racine de l’url API, l’autre contenant le json des paramètres de requête, cela nous permettra de retravailler les informations séparément. On va retourner dans une liste pour vérifier le résultat :

Une image contenant texte

Description générée automatiquement

Récupérer les paramètres nécessaires au getRowCount

Notre objectif va être maintenant de récupérer les propriétés dont nous avons besoin pour l’appel rowCount :

  • columns
  • space
  • period
  • filter
  • options

Comme cette partie de l’url est du JSON, nous allons pouvoir utiliser la méthode Json.document pour mieux manipuler et isoler les informations : 

Une image contenant texte

Description générée automatiquement

Une image contenant table

Description générée automatiquement

Il est maintenant très facile de récupérer une propriété de cet objet, en ajoutant son nom à la fin de l’expression (ici « columns » ):

Power Query a par contre transformer notre json en un format non exploitable (value), nous allons donc devoir reconvertir la valeur en JSON (via json.fromValue), puis le JSON en string (text.FromBinary) :

Nous avons presque nous ce qu’il nous faut ! il suffit maintenant d’ajouter le nom initiale de la propriété afin que le format soit prêt à être réinjecter dans un appels API :

Nous savons maintenant comment récupérer la propriété de notre choix depuis un appel API ! Nous allons donc appliquer cette logique sur l’ensemble des paramètres.

Nous allons donc créer des variables contenant chacune des propriétés dont nous avons besoin, afin de les reconcaténer ensuite dans notre appel rowcount :

Une image contenant texte

Description générée automatiquement

Gérer les paramètres optionnels

Dans notre appel d’exemple, nous n’avions pas de filtre et de segment car ce sont des propriétés optionnelles. Il faut cependant quand même gérer le cas ou elles seraient présentes dans un de nos appels. Si on tente de les récupérer avec la même méthode alors qu’elles ne sont pas présentes, la variable tombe en erreur :

Une image contenant texte

Description générée automatiquement

Nous allons donc devoir vérifier leur présence en amont. Pour cela nous allons transformer à la volée notre liste de paramètres en tableau, puis d’utiliser la méthode List.Contains sur la valeur recherchée, qui nous retournera un booléen.

Exemple avec la propriété filter :

Une image contenant texte

Description générée automatiquement

A partir de là, il suffit décrire une condition demandant à retourner une valeur null lorsque notre test retourne FALSE ou de nous retourner le filtre s’il est présent (true). On applique cette logique à nos paramètres optionnels. Au moment de créer la liste des propriétés dans la variable results, on applique finalement la méthode RemoveNulls, qui va supprimer les éléments vides.

Une image contenant table

Description générée automatiquement

Grace à la fonction text.combine, nous allons pouvoir concaténer toutes nos propriétés dans une string, en ajoutant « , » comme caractère séparateur. Nous n’avons plus qu’à réutiliser cette méthode pour construire notre appel rowCount :

Une image contenant texte

Description générée automatiquement

Requêter l’API getRowcount et déterminer le nombre d’appels à réaliser

La prochaine étape va maintenant être de requêter cette API (web.Contents) et de parcourir le JSON (json.document) pour récupérer la valeur qui nous intéresse.

L’API nous retourne un objet « Rowcounts », contenant une liste à 1 valeur, contenant elle-même une propriété RowCount. Nous allons donc sélectionner cette propriété et récupérer la première valeur associée :

Une image contenant texte

Description générée automatiquement

Dans mon cas, j’ai donc plus de 60 000 résultats. Si je veux déterminer automatiquement le nombre de pages de 10 000 de résultats que je devrais récupérer, il me suffit de diviser cette valeur par 10 00 et d’arrondir à l’entier supérieur :

 Voilà pour cette partie, qui a finalement été assez complexe ! 

La prochaine étape visera à lancer ces 7 appels API, pour chacune des pages de données à récupérer ! 

Mais avant cela, nous avons besoin de gérer le cas ou la requête ne retourne aucun résultat. En effet la suite de la procédure va partir du principe qu’il y a au moins une page de résultats à récupérer, il faut donc s’assurer que la page 1 soit à minima requêtée. Nous allons donc construire une condition très simple pour cela :

Récupération des pages de résultats

Construction des appels API à réaliser

La première étape va être de créer une liste de 1 à 7 (dans mon cas), pour reconstruire un appel api complet pour chaque page de résultats. Pour cela nous allons pouvoir utiliser la méthode list.Generate, qui va prendre 3 paramètres : 

  • La valeur de départ de la liste (ici 1)
  • La valeur maximale (notre rowCountAdjusted)
  • L’incrément qu’on souhaite faire à chaque nouvelle valeur de la liste (+1)

Une image contenant texte

Description générée automatiquement

Nous allons ensuite placer notre numéro dans l’expression complète de l’objet page-num de l’API. La méthode list.transform permet d’appliquer des retraitements sur chaque valeur de la liste et la méthode text.combine permet de faire des concaténations :

Une image contenant texte

Description générée automatiquement

Une image contenant texte

Description générée automatiquement

Toujours à l’aide de list.transform, on remplace le page-num de l’appel d’origine (stocké dans source) par notre liste.

Une image contenant texte

Description générée automatiquement

Une image contenant texte

Description générée automatiquement

Nos URLs sont finalement prêtes à être appelées ! 

Déterminer les colonnes qui seront présentes dans le tableau final

Mais (et oui, encore un mais), Pour préparer au mieux la future création du tableau de données, nous allons avoir besoin de savoir dès maintenant les colonnes qui y sont présentes. Fort heureusement, l’API retourne un objet « columns » nous donnant une multitude d’informations, dont 2 qui nous intéressent  : 

  • Name, qui indique la property key de la colonne dans l’appel API
  • Label, qui indique le nom de la colonne (celui présenté dans l’interface)

Une image contenant texte

Description générée automatiquement

On requête donc l’API d’origine et on isole l’objet columns :

Une image contenant texte

Description générée automatiquement

Chaque colonne est représentée par un objet « Record » qui contient les informations souhaitées :

Une image contenant texte

Description générée automatiquement

Une image contenant table

Description générée automatiquement

On réutilise la méthode list.transform pour extraire Name et Label :

Une image contenant texte

Description générée automatiquement

Réaliser l’appel de données

On peut enfin appeler nos données, en repartant de notre liste d’URL API et en y appliquant la méthode web.contents. l’objet de l’API contenant les lignes de données est l’objet « Rows » :

Une image contenant texte

Description générée automatiquement

Le format « list » n’est plus adaptée à partir de cette étape, nous allons donc la convertir en tableau de données grâce à la méthode table.FromList :

Une image contenant texte

Description générée automatiquement

Une image contenant table

Description générée automatiquement

Power Query a automatiquement créé un tableau à une colonne « column1 ». Si vous rentrez dans une des listes, vous constaterez qu’elle contient une multitude d’objets « Record », symbolisant chaque ligne de notre futur tableau. Nous allons donc simplifier tout cela en supprimant un niveau d’imbrication. La méthode exandListColumn va permettre de faire remonter les objets « Record » directement dans la colonne « Column1 » :

Une image contenant texte

Description générée automatiquement

Une image contenant table

Description générée automatiquement

Il ne nous reste plus qu’à déployer nos objets records en tant que colonnes, via la méthode expandRecordColumn. Cette méthode prend 4 arguments : 

  • La table à modifier (expandTable)
  • La colonne contenant les records (column1)
  • Les noms des colonnes à extraire (nous les avons déjà récupérés tout à l’heure dans la variable columnsName
  • Le nom qui sera donné à nos colonnes (nous allons reprendre ceux affichés dans l’interface, que nous avons dans la variable columnsLabel)

Une image contenant texte

Description générée automatiquement

Nous venons donc récupérer plus de 60 000 lignes de données en une seule requête Power Query ! 

Construction de la fonction finale

La dernière étape consiste à transformer l’ensemble de notre côté en une fonction, que nous pourrons réutiliser pour autant d’appels que nous souhaitons. Plusieurs choses à faire pour cela : 

  • Mettre au format « fonction fléchée » notre expression
  • Ajouter un paramètre « requestedAPI » qui symbolisera l’API indiqué par l’utilisateur

Une image contenant texte

Description générée automatiquement

Vous devirez obtenir quelques choses de ce type :

Une image contenant texte

Description générée automatiquement

Placez maintenant l’appel API de votre choix dans le champ « requestedAPI » et cliquez sur appeler. Votre tableau de données va se créer automatiquement dans une nouvelle requête :

Une image contenant table

Description générée automatiquement

Conclusion

Une fois la fonction créée, elle peut être utilisée à l’infini et partagée à d’autres utilisateurs sans avoir besoin de connaissance technique particulière. Si vous avez suivi l’ensemble de cet démonstration vous avez maintenant une très bonne connaissance de l’API Piano Analytics ainsi qu’une bonne compréhension des possibilités offertes par le code « m » de power Query

Le fichier d’exemple contant la fonction finalisée :

Récupérer vos données Piano Analytics dans Power Bi et Excel : le guide complet

L’API Piano Analytics permet très facilement d’extraire une importante quantité d’informations stockées dans la solution.

On peut vouloir importer les données Piano Analytics dans Excel pour réaliser une analyse ponctuelle, réaliser des tableaux de bord ou encore nourrir un reporting Power Point.

Dans un autre contexte, on peut également utiliser Power Bi pour créer des rapports décisionnels avec ses données analytics et toute autre source de données.

Nous allons voir à travers cet article que la solution est la même dans les 2 cas : Power Query.

En effet cet outil est disponible aussi bien dans Excel que dans Power Bi, et permet (entre autres) le requêtage de toute API externe pour importer les données et les remanipuler dans exploitation via un langage de programmation (le mashup).

source : Microsoft

Lancer Power Query dans Power BI ou dans Excel

Les présentations étant maintenant faites, rentrons dans le vif du sujet ! Pour suivre ce guide, vous pouvez utiliser de manière indifférente Power Bi ou Excel

Dans power Bi, Power Query est disponible dans l’onglet Accueil > Transformer les données :

Dans Excel, l’outil est disponible dans l’onglet Données > Obtenir des données > Lancer l’éditeur Power Query :

Création de la clé API

Pour requêter vos données Piano Analytics depuis l’extérieur de l’interface, vous allez devoir obtenir une clé API. Cette clé est liée à votre compte Piano Analytics, vous n’aurez donc pas la même que votre collègue. Vous aurez également accès au même périmètre que celui disponible dans votre interface (même liste de niveaux 1).

Une fois connecté à votre compte, allez dans votre profil puis dans l’onglet Api Keys.

 Générez une nouvelle clé et conservez bien l’Access key et surtout la secret key, qui ne vous sera plus donnée par la suite.

Récupération de l’appel API

A partir de la documentation API, il est tout à fait possible de créer votre appel de zéro. Il est cependant plus confortable de partir de l’outil Data Query, qui sera capable de vous restituer un appel API clé en main.

Rendez-vous donc dans l’interface Data Query, où je vous invite à créer l’appel de votre choix, peu importe la période (nous verrons cette question plus tard). Dans mon cas je vais simplement prendre le nombre de pages vues et de visites pour mes pages avec aux moins 10 pages vues :

Pour récupérer l’appel API correspondant, ouvrez l’onglet de partage et sélectionnez « Copier l’URL API (GET) » (nous reviendrons sur la notion d’API Post dans un autre article).

L’url est maintenant présente dans votre presse papier. Si vous la coller dans un bloc-notes, vous verrez que celle-ci est encodée. Pour y voir plus clair, je vous conseille de la désencoder via l’outil de votre choix. De mon côté je vais utiliser l’outil du site Meyerweb. On obtient donc ce résultat :

https://api.atinternet.io/v3/data/getData?param={"columns":["page","m_visits","m_page_loads"],"sort":["-m_visits"],"space":{"s":[592201]},"period":{"p1":[{"type":"D","start":"2022-04-12","end":"2022-04-18"}]},"max-results":50,"page-num":1,"options":{}}

Cet article n’est pas là pour apprendre à décortiquer la structure de cet appel (pour rappel, la documentation est ici), retenez simplement que le paramètre « param » de l’url est au format Json et est composé de plusieurs propriétés (columns, sort, period, max-results…).

Test de l’API Piano Analytics

On peut maintenant tester si notre clé API fonctionne ! Pour cela collez l’url dans votre navigateur. Un popup va s’afficher et vous demander un nom d’utilisateur et un mot de passe. Mettez votre Access key en tant que Nom d’utilisateur et la secret Key en mot de passe :

Vous obtiendrez normalement un json, que vous pourrez rendre plus lisible avec une petite extension chrome du type Json Viewer. On retrouve bien dans la propriété « rows » nos pages avec leurs données :

Créer son appel Power Query

Ouvrez Power Query et créez une nouvelle source de données avec le type « web », qui correspond au requêtage des API et des pages web :

Placez votre requête API dans le champ et sélectionnez le mode avancé :

Nous allons maintenant gérer l’authentification de l’API, qui se fait via le header de la requête.

On ajoute pour cela un paramètre « x-api-key », avec pour valeur la concaténation de votre access key et de votre secret key séparées d’un « _ » : accessKey_secretKey

Power Query vous demandera alors le type de connexion que vous souhaitez mettre en place. Comme le système d’authentification est déjà présent dans l’appel en lui-même via x-api-key, on peut rester sur le mode anonyme :

Power Query vous affiche maintenant les données qu’il a récupérées, et nous allons tâcher de les déployer afin de pouvoir les utiliser. Procédez comme ceci :

  • Cliquez sur la valeur « Record » à gauche de « DataFeed » (Non nécéssaire pour Power Bi)
  • Cliquez sur la valeur « List » associée à « Rows »
  • Dans l’onglet « Transformer », sélectionnez « convertir vers la table » puis « OK » dans la popin « vers la table » qui s’affiche
  • A droite de l’entête de colonne « Column1 » cliquez sur le petit icône avec les 2 flèches qui s’opposent et sélectionnez les colonnes que vous souhaitez extraire.

Vous avez maintenant vos données qui s’affichent dans Power Query. Vous pouvez renommer les colonnes qui ont toutes le préfixe « columns1 », pour plus de clarté. Vous pouvez maintenant cliquer sur « Fermer et charger » (« fermer et appliquer » pour Power Bi) pour que les données soient chargées dans le document :

Libre à vous de créer maintenant autant d’appels que vous le souhaitez en suivant cette méthode !

Dynamiser les périodes d’appel de vos API

Vous l’aurez peut-être remarqué mais la période donnée par Data Query dans l’API est toujours une période dite « fixe », c’est-à-dire avec une date de début et une date de fin, et cela même si vous sélectionnez une période relative (hier, la semaine dernière, le mois en cours) dans l’interface :

period »:{« p1 »:[{« type »:"D","start":"2022-04-12","end":"2022-04-18"}]}

Pour changer la période d’appel, il faudrait donc théoriquement éditer notre requête dans Power Query et changer manuellement la date, ce qui n’est pas très pratique (notamment si vous avez des dizaines d’appels) :

Fort heureusement la documentation nous donne les outils pour récréer une période relative dans l’appel API.

Si on reprend notre json de base gérant la date « period »:{« p1 »:[{« type »: »D », »start »: »2022-04-12″, »end »: »2022-04-18″}]} » voici ce que cela deviendrait pour des périodes relatives :

Période relativeJson correspondant
Aujourd’hui« period »:{« p1 »:[{« type »: « R », »granularity »: « D », »startOffset »: 0, »endOffset »: 0}]}
hier« period »:{« p1 »:[{« type »: « R », »granularity »: « D », »startOffset »: -1, »endOffset »: -1}]}
La semaine en cours« period »:{« p1 »:[{« type »: « R », »granularity »: « W », »startOffset »: 0, »endOffset »: 0}]}
Le mois dernier« period »:{« p1 »:[{« type »: « R », »granularity »: « M », »startOffset »: -1, »endOffset »: -1}]}
L’année en cours« period »:{« p1 »:[{« type »: « R », »granularity »: « Y », »startOffset »: 0, »endOffset »: 0}]}

Comme vous le voyez, le patern se répète et il n’y a en réalité que 3 éléments à changer :

  • Granularity, qui indique la granularité de la période (Days, Week, Month, Quarter, Year)
  • StartOffset, qui indique la période à laquelle il faut faire remonter l’appel. 0 correspond à la période en cours, -1 la période précédente, -2 la période encore précédente…
  • EndOffset, qui indique la période à laquelle il faut arrêter l’appel. Le format est le même que pour StartOffset

Ainsi, « granularity »: « W », »startOffset »: -2, »endOffset »: -1 » veut dire « sélectionne moi les 2 dernières semaines par rapport à la date actuelle ». Ou encore « granularity »: « D », »startOffset »: -366, »endOffset »: -1 » veut dire « sélectionne moi les 365 derniers jours par rapport à la date actuelle ».

Vous pouvez ainsi imaginer une multitude de combinaisons pour vos appels en mixant ces 3 paramètres.

Si on souhaite maintenant modifier la période de départ de notre appel pour la rendre dynamique par rapport à la date actuelle, par exemple en sélectionnant la semaine précédente :

  • Ouvrez Power Query et sélectionnez la requête à modifier
  • Dans « étapes appliquées », double cliquez sur « Source » pour afficher à nouveau le champ contenant l’appel API
  • Remplacez la propriété « period » avec la période relative.

Validez vos changements en cliquant sur « OK » :

Votre appel s’adaptera donc bien maintenant à la date à laquelle vous actualisez votre document !

Bonus 1 : modifiez la période de tous vos appels API en une seule fois

Comme vous l’aurez compris, pour modifier la période de vos appels API, il faudra alors le faire manuellement (pour passer de la période « semaine -1 » à « semaine -2 » par exemple). Si vous devez régulièrement changer les périodes d’appels, le travail peut vite devenir fastidieux.

Une solution pour contourner ce problème est de créer une valeur unique, qui sera automatiquement prise en compte comme période dans tous les appels.

Nous allons utiliser pour cela les paramètres power query :

  • Allez dans accueil > Gérer les paramètres > Nouveau Paramètre
  • Nommez-la avec le nom de votre choix (« periode_analyse » dans mon cas) 
  • placez le json de la période souhaitée dans le champ « valeur actuelle ». dans mon cas je souhaite le mois précédent, donc {« p1 »:[{« type »: « R », »granularity »: « M », »startOffset »: –1, »endOffset »: -1}]}

Maintenant que notre paramètre existe, nous allons remplacer la période présente dans les appels par notre paramètre. Pour cela :

  • Sélectionnez la requête à modifier et placez-vous sur l’étape « Source »
  • Dépliez via la flèche le code m correspondant à cette étape et repérez la zone où est écrite la période
  • Supprimez la période présente et concaténez le début de l’appel API avec notre paramètre période_analyse, puis concaténez à nouveau avec la fin de l’API via le format début_requete’&période_analyse&’fin_requete (cf. capture)

Effectuez cette opération sur l’ensemble des appels à dynamiser. Et voilà ! Vous pouvez maintenant changer à loisir votre période d’analyse, l’ensemble des appels seront mis à jour. A noter que vous pouvez effectuer exactement la même démarche pour une éventuelle période de comparaison.

Bonus 2 : Dynamiser la période à partir de dates fixes

Le format relatif des dates est très pratique, mais très spécifique à Piano Analytics. Si d’autres sources que vos données analytics sont appelées, vous aurez potentiellement besoin d’utiliser le même format de date partout afin d’avoir la même période dans toutes vos tables.

L’idéal serait donc de pouvoir repasser dans un format de dates plus conventionnelles (AAAA-MM-JJ par exemple) mais tout en gardant le système de dynamisation.

C’est ce que nous allons faire ici ensemble, via l’écriture d’un peu de mashup ! Pour rappel, Power Query n’est « qu’un » générateur de code en langage m (ou aussi appelé Mashup), dont vous pouvez voir l’appercu dans Power Query > Accueil > Editeur avancé. Si on en comprend la nomenclature il est possible d’écrire son propre code sans passer par l’interface.

Notre objectif va être simple : Recréer dynamiquement au format AAAA-MM-JJ les dates de début et de fin du mois précédent afin de pouvoir les placer dans mes appels (à noter que la même logique pourra être appliquée pour les autres types de périodes). Si je suis par exemple le 3 mai 2022, le code devra alors me donner 2022-04-01 et 2022-04-30.

Construction de la requête

La première étape va être pour nous de créer une requête vite. Pour cela allez dans Accueil > Nouvelle Source > Requête vide.

Allez maintenant dans Accueil > Editeur avancé pour afficher le code. Vous devriez avoir quelque chose comme ceci :

La première étape pour nous va être de récupérer la date du jour, afin d’en déduire le mois précédent. Ainsi, à chaque mise à jour du fichier, la date du jour sera automatiquement updatée. Nous allons pouvoir utiliser la fonction DateTime.LocalNow() pour cela. On va également renommer l’étape en « today » afin que cela soit plus explicite.

Vous devriez avoir un code comme ceci, et obtenir la date du jour dans l’éditeur :

On ajoute maintenant une étape, permettant d’enlever 1 mois à la date du jour via la méthode Date.AddMonths et en y plaçant la valeur -1. Nous sommes maintenant dans le bon mois :

Notre objectif va maintenant être de construire les dates de début et de fin, à partir de la variable lastMonth en extrayant l’année et le mois puis en créant le jour. Commençons par la date de début.

Pour extraire l’année d’une date, on va pouvoir utiliser la méthode Date.year. On va également convertir la valeur au format string via la méthode Number.toText car nous en aurons besoin plus tard :

On va appliquer la même logique pour récupérer le mois, avec la méthode date.Month :

On va ici avoir une difficulté qui n’était pas présente sur l’année : on ne souhaite pas récupérer « 3 » comme valeur mais « 03 », sinon la date ne sera pas au bon format (AAAA-MM-JJ). Pour corriger cela il suffirait de concaténer la valeur récupérée avec « 0 » afin d’obtenir le bon format :

startDateMonth = « 0 »& Number.ToText(Date.Month(LastMonth))

Cependant cette méthode ne fonctionnera pas pour les mois contenant 2 chiffres (octobre, novembre, décembre) puisque cela va nous générer des valeurs du type « 010 ».

Pour prendre en compte des mois particuliers, on va demander à tronquer la valeur pour ne conserver que les 2 derniers caractères, via la méthode text.end :

startDateMonth = Text.End(« 0″& Number.ToText(Date.Month(LastMonth)),2)

Finalement la valeur du jour sera toujours la même : « 01 » pas besoin de code spécifique pour cela !

Il ne reste plus qu’à concaténer l’ensemble dans une variable :

startDate = startDateYear& »-« &startDateMonth& »-01″

Construire la date de fin

Récupérer l’année et le mois de la date de fin ne comporte aucune nouveauté, vous pourrez donc récupérer les valeurs de la même manière que la date de début.

Le jour va en revanche nous poser un peu plus de problème. En effet cette valeur doit représenter le dernier jour du mois, il peut donc être égal à 28,29,30 ou 31.

Dans un premier temps nous appliquons la méthode Date.EndOfMonth pour obtenir la date du dernier jour du mois :

    endDateMonth = Date.EndOfMonth(LastMonth)

On extrait maintenant le jour de cette date via la méthode date.Day et on transforme la valeur en texte :

 endDateMonth = Number.ToText(Date.Day(Date.EndOfMonth(LastMonth)))

Il n’y a plus qu’à concaténer l’ensemble des valeurs pour créer la date de fin (je vais le faire ici en un seul bloc :

endDate = Number.ToText(Date.Year(LastMonth))& »-« &Text.End(« 0″ & Number.ToText(Date.Month(LastMonth)),2)& »-« &Number.ToText(Date.Day(Date.EndOfMonth(LastMonth)))

Retourner les 2 valeurs sous la forme d’une liste et les injecter dans nos appels

Il ne reste plus qu’à retourner les 2 dates sous la forme d’une liste pour en finir avec notre code :

Nous allons maintenant pourvoir intjecter les 2 dates crées dans toutes les sources qui nous intéressent et pas uniquement l’API Piano Analytics.

Pour cela, on édite l’appel en question et on va placer chaque item de notre liste au bon emplacement via la sélection {0} et {1} :

Conclusion

Nous avons vu à travers cet article comment effectuer nos appels API Piano Analytics et comment dynamiser les dates d’appels. Ayez bien en tête que n’importe quelle période est dynamisable et si le cas dont vous aviez besoin n’a pas été vu dans cet article, je vous renvoie vers l’ensemble des méthodes M autour des dates.

Piano Analytics : comment migrer son tracking Smarttag vers Piano Analytics ?

Piano a mis à jour en avril 2022 sa toute nouvelle librairie de marquage, Piano Analytics SDK, fixant pour un moment les nouveaux fondamentaux d’une implémentation de tracking avec cet outil. Même si l’ancienne librairie (smarttag.js) reste disponible et viable pour vos implémentations actuelles durant encore quelques temps, il faudra dans un avenir relativement proche mettre à jour votre tracking pour profiter des futures nouveautés proposées par la solution Analytics.

Pas de panique cependant, si vous disposez déjà d’une intégration fonctionnelle de Piano Analytics dans votre TMS, il suffira bien souvent de remplacer les tags directement dans celui-ci, plutôt que d’avoir à modifier de fond en comble le Data Layer ou les déclencheurs en place sur votre site.

Je vous propose donc dans cet article d’essayer de comprendre les principales nouveautés apportées par Piano Analytics SDK et d’acquérir les grands concepts d’un projet de mise à jour de votre tracking.

Les nouveautés de Piano Analytics SDK

Le tracking Piano est event based, c’est à dire qu’il va attendre de vous que vous le déclenchiez sur des actions précises (chargement, clic, impression…) avec à chaque fois 2 types d’informations :

  • Le nom de l’event symbolisant l’action réalisée (‘page.display’ pour un évènement de page vue par exemple)
  • Les informations associées à cet event, appelées ‘propriétés’ (‘page_chapter1’ pour l’arborescence de premier niveau associé à la page chargée par exemple)

Cette approche était déjà présente dans les dernières versions du smartTag, mais beaucoup moins dans la version historique.

L’autre nouveauté importante est la librairie universelle (et donc non personnalisable). En effet Piano Analytics SDK n’est pas proposé dans le module « tag Composer » de la solution, permettant de définir la configuration de son tag (ID de site de niveau 1, domaine de dépôt des cookies, plugins à embarquer). Il suffira alors d’appeler la librairie commune et de configurer des préférences directement dans le marquage.

La notion de « plugins » (clicks, Publicités on-site, MV Testing) disparait au passage, au profit d’un nommage précis attendu dans le nom de l’event et des propriétés.

On se retrouve donc finalement avec une librairie plus simple d’utilisation et donc plus légère qui sera la même pour tous vos sites.

Les présentations étant maintenant faites, rentrons maintenant « dans le dur » pour comprendre ce que vous devrez faire pour migrer vers Piano Analytics SDK.

Comprendre dans quel type d’implémentation vous êtes

Comme l’explique la documentation technique, selon le moment où l’implémentation a été réalisée, vous pouvez actuellement utiliser 2 tags différents :

  • Le smartTag AS2 (le plus ancien), se basant sur des setters (« tag.page.set »)
  • le smartTag PA, se basant déjà sur la nomenclature event based (« tag.event.send »)

Au délà de cette différence setters / event, un gros point de différenciation est que le smattag PA n’utilise plus de système de déclaration Clé/Valeur.

Par exemple, pour utiliser un indicateur personnalisé « date publication » (poussé dans le tracking au format timestamp), dans l’Analytics Suite 2 vous deviez préalablement déclarer cet indicateur dans l’interface, qui vous retournait un ID (‘x2‘ par exemple). C’est cet id qui devait être placé dans le marquage pour faire remonter la donnée. Dans le même esprit, il existait des indicateurs pouvant eux même contenir des systèmes Clés/Valeurs. L’exemple le plus commun sont les niveaux 2, où chaque grande rubrique d’un site était associée à un ID pré-déclaré dans l’interface. l’ID « 2 » de Niveau 2 correspondant par exemple à « Espace abonnés« .

Le smarttag PA utilise lui directement les valeurs finales dans le tracking (celles déclarées dans l’interface). On se retrouve donc dans le tag avec un niveau 2 égal à « espace abonnés » et notre indicateur personnalisé directement nommé par son nom final « date_publication« .

Vous l’aurez compris, le travail de migration à réaliser ne sera alors pas toujours le même selon la version du tag que vous utilisez. En effet le smarttag PA est déjà quasiment prêt à l’emploi pour migrer vers Piano Analytics SDK, tandis qu’un tracking avec le smarttag AS2 devra remplacer toute cette logique de setters mais surtout remplacer tous les ID déclarés des indicateurs personnalisés et les valeurs déclaratives par de de vraies valeurs textuelles.

Déterminez bien dans quelle situation vous êtes pour comprendre quelles sections de l’article vous concerne. J’indiquerai alors « spécifique AS2 » ou « spécifique PA » dans le titre.

Migration de votre tag de page principal

C’est clairement la partie qui va demander le plus de travail puisque de cet endroit se décide toute la configuration de votre tracking :

  • La configuration technique (quel Niveau 1, quel domaine de dépôt de cookie, etc.),
  • La gestion de la privacy (mesure classique, mesure hybride),
  • La gestion du contexte de la page (ce qui sera repris par les autres events présents sur celle-ci).

S’ajoute à cela le déclenchement du tag de page en lui-même, qui peut également avoir son lot de spécificités. Nous allons donc prendre ces éléments 1 par 1 et construire notre nouveau tag de page Piano Analytics SDK

Appel de la librairie et définition de la configuration technique

Comme nous l’avons indiqué plus haut, le smartTag contenait auparavant toute la configuration de notre tag qui avait été définie dans tag Composer. Il était cependant également possible de surcharger cette configuration à la volée au moment de l’initialisation de votre tag.

Vous pouvez donc vous retrouver dans le cas où il n’y a pas de surcharge de la configuration :

<script src="//tag.aticdn.net/123456789/smarttag.js"></script>
<script>
var tag = new ATInternet.Tracker.Tag();
//...suite du code
<script>

Et vous pouvez également être dans le cas où une partie de la configuration est surchargée :

<script src="//tag.aticdn.net/123456789/smarttag.js"></script>
<script>
var tag = new ATInternet.Tracker.Tag({secure: true, site:12345, logSSL:'https://logsx.xiti.com'});
//...suite du code
<script>

Initialement surchargée ou non, nous allons devoir réécrire toute notre configuration au sein de notre tag Piano Analytics SDK. La documentation nous liste toutes les informations configurables via la méthode pa.setConfigurations. Comme vous le voyez la liste est relativement longue et vous êtes libre de configurer l’ensemble ou une partie seulement de ces paramètres. Dans cet article nous allons assurer le minimum pour que les events puissent continuer à être envoyés sur le bon S1 et qu’il n’y ait pas de rupture dans le suivi des visiteurs (pérennité du cookie de suivi). Pour cela il nous faudra connaitre :

  • Le domaine de collecte (aussi appelé log de collecte),
  • L’id de site de niveau 1,
  • le domaine de dépôt des cookies *.

* si cette valeur est actuellement vide dans votre configuration, alors c’est le domaine complet de la page en cours qui sera utilisée…pas idéal pour le suivi inter sous-domaines de vos visiteurs.

Si vous ne connaissez pas ces informations, plusieurs solutions sont possible pour les obtenir :

  • Vous rendre dans le module Tag Composer et consulter la configuration réalisée sur votre site,
  • Observer un hit envoyé depuis votre site dans Tag Inspector,
  • Ouvrir directement le fichier smarttag.js de votre site web et chercher la variable « dfltGlobalCfg » (présente en tout début de code).

Une fois les informations obtenues, il ne reste plus qu’à mettre notre tag Piano Analytics en place :

<script>
window._pac = window._pac || {};
_pac.cookieDomain = '.monsite.com'; // certains paramètres sont à définir avant l'appel à la librairie
</script>

<script src="https://tag.aticdn.net/piano-analytics.js"></script>

<script>
pa.setConfigurations({
    site: 12345, 
    collectDomain: "https://logsx.xiti.com"
  });
<script>

Gestion du consentement

Je vous conseille de gérer la privacy le plus tôt possible dans votre tag, afin d’éviter tout déclenchement de hit sans y avoir défini le consentement de l’utilisateur.

Actuellement plusieurs cas de figures peuvent être présents pour votre site (selon si votre contrat dispose de l’exemption CNIL ou non) mais rien d’insurmontable ici cependant.

Vous n’avez pas l’exemption CNIL. Vous aviez ici le choix de ne pas déclencher le tag AS2 lorsque l’utilisateur se place en Optout, ou au contraire d’en déclencher un mais avec un visitor mode en optout. Cela servira alors simplement à la comptabilisation de hits en optout dans l’interface. Voici le code correspondant :

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag */

var tag = new ATInternet.Tracker.Tag();
if("condition vérifiant si l'utilisateur est optin"){
    tag.privacy.setVisitorOptin();
}
else{ // si l'utilisateur est optout
    tag.privacy.setVisitorOptout();
}
//suite de mon code

Vous allez retrouver des méthodes tout à fait équivalentes avec Piano Analytics :

//Fait : appel de la librairie, définition de la configuration

if("condition vérifiant si l'utilisateur est optin"){
    pa.privacy.setMode('optin');
}
else{ // si l'utilisateur est optout
    pa.privacy.setMode('optout');
}
//suite de mon code

Vous avez l’exemption CNIL et vous utilisez la mesure hybride. Dans ce cas le code va avoir la même base, avec simplement une condition supplémentaire pour gérer le mode exempt. Voici un exemple de code AS2 :

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag */

if("condition vérifiant si l'utilisateur est optin"){
    tag.privacy.setVisitorOptin();
}
else if("condition vérifiant si l'utilisateur est exempt"){
    tag.privacy.setVisitorMode('cnil', 'exempt');
}
else{ // si l'utilisateur est en optout de 3ième niveau
    tag.privacy.setVisitorOptout();
}
//suite de mon code

Voici l’équivalant Piano Analytics :

//Fait : appel de la librairie, définition de la configuration

if("condition vérifiant si l'utilisateur est optin"){
    pa.privacy.setMode('optin');
}
else if("condition vérifiant si l'utilisateur est exempt"){
    pa.privacy.setMode('exempt');
}
else{ // si l'utilisateur est optout
    pa.privacy.setMode('optout');
}
//suite de mon code

Gestion du strictement nécessaire (spécifique à l’exemption CNIL)

Une des conditions pour pouvoir tracer un utilisateur avant son consentement et après son refus de premier et deuxième niveau, est de faire remonter un minimum d’informations lors de sa navigation. Piano a donc défini avec la CNIL une liste d’informations que vous pouvez remonter par défaut (nom de la page, referer, timestamp..). Il est cependant possible de modifier cette liste pour ajouter des informations supplémentaires (sous le contrôle de votre service juridique) et c’est ce qu’on appelle le Buffer. Le buffer était auparavant configurable de 2 manières :

  • Dans Tag Composer, dans la zone « paramètres vie privée »,
  • Directement dans le tag AS2, via la méthode « extendIncludeBuffer ».

Pour illustrer notre cas, nous allons utiliser la méthode extendIncludeBuffer sur 2 paramètres :

  • le paramètre « ac », correspondant à la catégorie de visiteur identifié et ayant pour property key « user_category » dans Piano Analytics.
  • le paramètre « x2 », correspondant à un indicateur de site « date publication » déclaré dans l’AS2 et ayant pour pour property key « date_publication » dans Piano Analytics.

Selon l’ancienneté de votre marquage, vous utilisiez dans le buffer le paramètre technique de l’information (ac, x2) pour un marquage AS2, ou bien directement les property keys du data model Piano analytics. Pour mieux comprendre la correspondance entre les paramètres AS2 et le data model Piano Anaytics, je vous invite à patienter jusqu’à la section « Traduction du tag de page AS2 », où je rentre plus dans le détail.

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag */

if("condition vérifiant si l'utilisateur est optin"){
    tag.privacy.setVisitorOptin();
}
else if("condition vérifiant si l'utilisateur est exempt"){
    tag.privacy.setVisitorMode('cnil', 'exempt');
}
else{ // si l'utilisateur est en optout de 3ième niveau
    tag.privacy.setVisitorOptout();
}
tag.privacy.extendIncludeBuffer(['ac','x2']); // si vous êtes en marquage AS2
tag.privacy.extendIncludeBuffer(['user_category','date_publication']); // si vous êtes en marquage PA
//suite de mon code

Avec Piano Analytics SDK, le buffer doit être directement configuré dans le tag et uniquement avec le nom des property keys présentes dans le data model. Voici la traduction de notre buffer avec le SDK PA, via la méthode « include.properties » :

//Fait : appel de la librairie, définition de la configuration
if("condition vérifiant si l'utilisateur est optin"){
    pa.privacy.setMode('optin');
}
else if("condition vérifiant si l'utilisateur est exempt"){
    pa.privacy.setMode('exempt');
}
else{ // si l'utilisateur est optout
    pa.privacy.setMode('optout');
}
pa.privacy.include.properties(
    ['user_category', 'langue_article'], // la liste des propriétés à passer dans le buffer
    ['exempt']); // dans quel mode du visiteur cette liste doit être appliquée
//suite de mon code

Comme vous le voyez, la méthode est dans la même philosophie qu’auparavant, à la différence près qu’il est également demandé le mode visiteur (optin, exempt, optout…) associé à votre buffer. A noter que les possibilités de configuration du buffer ont largement été agrémentées sur Piano Analytics SDK (notamment via la possibilité d’exclure plutôt que d’inclure des propriétés ou des events), c’est donc peut-être l’occasion pour vous de retravailler votre code.

Gestion des propriétés persistantes (setprops)

La méthode setprop(s) permettait de « mettre en sauvegarde » des informations, souvent liées au contexte de la page, afin quelles soient automatiquement reprises sur les autres évènements pouvant se déclencher sur la page (clics, impressions, events customs). Cela est fort utile pour limiter le code des events et cela permet surtout de rendre cette information croisable avec n’importe quel évènement dans l’interface. Nous pouvons par exemple pousser notre propriété personnalisée « date_publication » dans le setprops afin qu’elle soit automatiquement récupérée par les évènements de partages des articles. Voici donc notre code smarttag avec des setprops sur les propriété « user_category » et « date_publication »

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag, définition de la privacy et du buffer */
tag.setProps({
    'user_category': 'premium',
    'date_publication': 1660225582
}, true);
//suite de mon code

Pour rappel, le dernier paramètre en booléen permet d’indiquer si on souhaite que le setProps soit appliqué à tous les évènements de la page (true) ou uniquement le prochain à se déclencher (false).

Voici ci-dessous l’équivalent avec Piano Analytics SDK :

/*Fait : appel de la librairie, définition de la configuration, 
définition de la privacy et du buffer */
pa.setProperties({
        'user_category': 'premium',
        'date_publication': 1660225582 
      }, {
        'persistent': true
});
//suite de mon code

A noter que la méthode pa.setProperties dispose d’un troisième paramètre ‘events’ permettant d’indiquer sur quels évènements les informations devront être reprises (ils sont tous pris par défaut).

Gestion du contenu et du déclenchement du tag

Après toute cette préparation, il est maintenant temps d’envoyer nos premiers events avec Piano Analytics SDK. Pour cela, il nous reste à remapper correctement les informations qui sont actuellement poussées dans votre tag de page, et on va ici avoir une très nette différence entre un marquage AS2 et un marquage PA déjà orienté events. Cette section va donc être découpée en 2 parties.

Traduction du tag de page spécifique AS2

Le marquage AS2 résidait dans l’utilisation de setters spécialisés dans un certains types d’informations (« page » pour le nom de la page et son chapitrage, customVars pour tous les indicateurs personnalisés, identifiedVisitor pour les informations de visiteurs identifiés…).

Etant donné que la méthode setprops est arrivée il y a relativement peu de temps, il est peu probable que vous l’utilisiez dans votre tracking AS2. C’est pour cette raison qu’on retrouve dans la suite de cet article la catégorie de visiteur identifié et notre indicateur de site personnalisé « date publication » directement dans le tag de page et non dans un setprops, contrairement au tag PA. Voici le code :

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag, définition de la privacy et du buffer */

tag.page.set({
    name:'mon_actualite',
    chapter1:'actualites',
    level2:'2'
});
tag.identifiedVisitor.set({
    id: 12345, // ID unique de mon visiteur identifié
    category:'21'
});
tag.customVars.set({
    site: {
        2: 1660225582 //indicateur personnalisé remontant la date de publication d'un article
    }
});
tag.dispatch();

Comme démontré dans l’introduction, nous allons ici devoir faire disparaître ces setters ainsi que toutes les informations liées à des ID (indicateurs de site, de page, niveaux 2)

Première étape : choisir le bon event Piano Analytics

Dans le marquage AS2, le nom de l’évènement n’était pas toujours très explicite. Ici c’est le tag.page.set associé au tag.dispatch() qui va générer le hit de page. Dans Piano Anaytics SDK, on attend un nommage plus explicite de l’event, comme ceci :

pa.sendEvent('#nom_de_l_event#', {
//#données associés à l'event#
});

La première étape va donc être de trouver l’event PA SDK équivalent au tag que nous sommes en train de migrer (ici notre tag de page).

Il n’existe pas de mapping exhaustif entre les setters/events AS2 et les events Piano Analytics, il va donc falloir chercher soi-même l’information. Pas de panique cependant, vous avez pour cela plusieurs sources d’informations :

  • Le data model, qui vous liste l’ensemble des events standards avec à chaque fois une définition explicite :
  • Le centre support, qui peut toujours vous aiguiller en cas de doute :).

Dans notre cas, le bon event est donc « page.display », que nous pouvons placer dans notre code

/*Fait : appel de la librairie, définition de la configuration, 
définition de la privacy et du buffer, définition du setprops */

pa.sendEvent('page.display', {
//#données associés à l'event
});
Choisir les bonnes propriétés du data model

Comme pour les events, il faut comprendre ici le processus qui est actuellement en place pour alimenter l’interface Piano Analytics, malgré le fait que vous utilisiez l’ancienne nomenclature de marquage. Pour cela, prenons un exemple simple : un tag de page avec un indicateur standard (le nom de la page) et un indicateur personnalisé (la date de publication d’un article). Si on met de côté toute la gestion de la privacy, on obtient un code comme ceci :

var tag = new ATInternet.Tracker.Tag();
tag.page.set({
    name:'mon article'
});
tag.customVars.set({
    site: {
        2: 1660225582
    }
});
tag.dispatch();

Ce code va nous générer le hit suivant (les fioritures ont été retirées) :

https://logsx.xiti.com/hit.xiti?s=12345&idclient=457cebat-2470-4c7c-8942-52d176b64f58&
p=mon article&x2=20220615

Ce hit est bien valide pour le processing de l’Analytics suite 2, mais pas pour le processing Piano Analytics car il n’est pas au format « event’. Il existe donc dans le processing une étape qui vise à traduire un hit AS2 en event Piano Analytics. Un des retraitement réalisé est la création d’une correspondance entre les paramètres de ce hit (p,x2) avec des propriétés du Data Model Piano Analytics. Ainsi, ‘p’ devient ‘page’, ‘x2’ devient ‘date_publication’ et vous retrouvez in fine votre chargement de page dans Piano Analytics.

Maintenant si vous poussez directement un event au format Piano Analytics via le nouveau SDK, le processing ne le considèrera plus comme un évènement à « traduire » et c’est maintenant à vous de faire la correspondance en amont dans votre tag.

Comme pour les events, vous allez donc devoir trouver les correspondances paramètres AS2 / propriétés Piano Analytics pour mettre à jour votre tag.

Pour les propriétés standards (celles proposées par défaut dans la solution), les sources d’informations sont les mêmes que pour les évènements :

Pour les propriétés personnalisées (vos ex indicateurs de site, indicateurs de page…), la correspondance est plus explicite puisque vous pouvez la télécharger depuis le data model.

Pour cela chargez le data model, puis allez dans « Actions » et finalement « Extraire mes var. personnalisées ». Le fichier va vous donner pour chaque déclaration d’indicateur personnalisé le paramètre de hit ainsi que la property key correspondante. A noter que cette correspondance a été réalisée automatiquement par Piano lors du développement de la nouvelle solution Piano Analytics, il ne s’agit pas d’une action manuelle. Je vous conseille de filtrer sur l’ID de site sur lequel vous êtes en train de travailler (via la colonne id_site ou site), puis de vous concentrer sur les colonnes tag_parameter et property_key. Dans mon exemple je vais donc bien retrouver mon x2 avec sa property key « date_publication » :

Vous avez donc maintenant tous les outils pour trouver les propriétés correspondantes à votre marquage AS2 ! Vous pouvez également profiter de ce travail de migration pour faire le tri dans vos paramètres. Si un indicateur personnalisé ne remonte plus ou très peu de données depuis 6 mois, il n’est peut-être pas nécessaire de le reprendre dans le nouveau marquage (vous pouvez même masquer ou désactiver la propriété du data model si elle n’est pas utilisée sur un autre site).

Dans notre exemple, la traduction sera la suivante :

  pa.sendEvent('page.display', {
    'page' : 'mon article',
    'date_publication':1660225582
  });

Maintenant que nous avons la logique, mettons de côté ce petit exemple et reprenons notre travail de traduction pour compléter notre event de page Piano Analytics complet :

/* Fait : appel de la librairie, définition de la configuration, 
définition de la privacy et du buffer */
  
pa.sendEvent('page.display', {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':2,
    'user_id':12345,
    'date_publication':1660225582
  });

Traduction du tag de page PA

Pour ceux utilisant déjà le format event de l’ancien tag, la traduction va être très très rapide puisque la nomenclature entre les 2 versions est quasiment identique. Il n’est ici pas nécessaire de citer à nouveau « user_category » et « date_publication » dans l’event de page, puisqu’ils sont déjà présents dans le setprops. :

/*Fait : appel de la librairie, éventuelle définition d'une surcharge de configuration, 
initialisation du tag, définition de la privacy et du buffer */

tag.setProps({
    'user_category': 'premium',
    'date_publication': 1660225582
}, true);

tag.events.send('page.display',
  {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':2,
    'user_id':12345
  }
);

La traduction est identique à celle avec le tracking AS2 :

/* Fait : appel de la librairie, définition de la configuration, 
définition de la privacy et du buffer, définition du setprops */

pa.setProperties({
        'user_category': 'premium',
        'date_publication': 1660225582 
      }, {
        'persistent': true
});

pa.sendEvent('page.display', {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':2,
    'user_id':12345,
  });

traduction des valeurs avec déclarations préalables (spécifique AS2 tagging)

Vous voyez enfin vos premiers events partir, vous pourriez penser que le travail de traduction est terminé…et bien pas tout à fait. Vous remarquerez que vos données ne remontent pas correctement sur les propriétés « site_level2 » et « user_category ». Cela est dû au fait que ce sont des informations déclaratives, avec une correspondance ID/valeur qui a été configurée dans l’AS2. Par exemple l’ID « 2 » pour la variable « Niveau 2 » de l’AS2 correspond à « zone abonnés » et l’id « 21 » de la variable catégorie de visiteur identifié correspond à « premium ».

Pour les mêmes raisons que les paramètres des hits AS2 ne sont pas automatiquement traduits en propriétés Piano Analytics, les tables de correspondance ne sont plus appliquées sur les indicateurs avec déclaration et les données ne sont donc pas traduites. Vous allez donc devoir ici aussi faire ce travail en amont, en trouvant les tables de correspondance d’origine et en les traduisant.

Pour trouver les tables de correspondance existantes, il faudra alors retourner dans l’Analytics suite 2 (Switcher > Retour à l’Analytics suite 2) puis aller dans « Configuration » > « Configuration ».

Les paramètres à vérifier sont les suivants :

  • Niveaux 2
  • Indicateurs de site (s’ils sont de type « ID »)
  • Indicateur de page (s’ils sont de type « ID »)
  • Indicateurs personnalisés de commandes
  • Auto-promotion
  • Campagnes marketing

Exemple avec les niveaux 2 :

Maintenant que nous avons les correspondances, Vous avez 3 moyens pour les appliquer (par ordre de préférence) :

  • Demander une mise à jour de la valeur dans le data layer du site en fournissant à l’équipe de développement les correspondances,
  • Appliquer les tables de correspondance en JS ou via le TMS (GTM et Tag Commander disposent de fonctionnalités de déclaration de tableaux),
  • Déclarer une table de correspondance sur la propriété, dans le Data Model Piano Analytics.

Dans notre cas, nous avons demandé une mise à jour du data layer afin d’avoir les données au bon format dans le TMS. On part donc de ce tag AS2 :

/* Fait : appel de la librairie, définition de la configuration, initialisation du tag, 
définition de la privacy et du buffer, définition du setprops */

tag.page.set({
    name:'mon_actualite',
    chapter1:'actualites',
    level2:'2'
});
tag.identifiedVisitor.set({
    id: 12345, // ID unique de mon visiteur identifié
    category:'21'
});
tag.customVars.set({
    site: {
        2: 1660225582, //indicateur personnalisé remontant la date de publication d'un article
    }
});
tag.dispatch();
// suite du code 

Pour arriver avec ce tag Piano Analytics SDK :

/* Fait : appel de la librairie, définition de la configuration, 
définition de la privacy et du buffer, définition du setprops */

pa.setProperties({
        'user_category': 'premium',
        'date_publication': 1660225582 
      }, {
        'persistent': true
});

pa.sendEvent('page.display',
  {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':'zone abonnes',
    'user_id':12345
  }
);

Résultat final

Nous avons finalement traduit l’ensemble de la configuration et de notre tag de page ! Je vous replace ci-dessous les codes en entier :

  1. AS2 tagging
<script src="//tag.aticdn.net/123456789/smarttag.js"></script>
<script>
//initialisation du tracker
var tag = new ATInternet.Tracker.Tag();

//définition du consentement de l'utilisateur
if("condition vérifiant si l'utilisateur est optin"){
    tag.privacy.setVisitorOptin();
}
else if("condition vérifiant si l'utilisateur est exempt"){
    tag.privacy.setVisitorMode('cnil', 'exempt');
}
else{
    tag.privacy.setVisitorOptout();
}

//définition du buffer privacy 
tag.privacy.extendIncludeBuffer(['ac','x2']);

//définition de notre event de page
tag.page.set({
    name:'mon_actualite',
    chapter1:'actualites',
    level2:'2'
});
tag.identifiedVisitor.set({
    id: 12345, 
    category:'21'
});
tag.customVars.set({
    site: {
        2: 1660225582 
    }
});
tag.dispatch();
</script>

2. PA tagging

<script src="//tag.aticdn.net/123456789/smarttag.js"></script>
<script>

//initialisation du tracker
var tag = new ATInternet.Tracker.Tag();

//définition du consentement de l'utilisateur
if("condition vérifiant si l'utilisateur est optin"){
    tag.privacy.setVisitorOptin();
}
else if("condition vérifiant si l'utilisateur est exempt"){
    tag.privacy.setVisitorMode('cnil', 'exempt');
}
else{ // si l'utilisateur est en optout de 3ième niveau
    tag.privacy.setVisitorOptout();
}

//définition du buffer privacy 
tag.privacy.extendIncludeBuffer(['user_category','langue_article']);

//définition de nos propriétés persistantes
tag.setProps({
    'user_category': 'premium',
    'date_publication': 1660225582
}, true);

//définition de notre event de page
tag.events.send('page.display',
  {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':'zone abonnes',
    'user_id':12345
  }
);
</script>

3. la version traduite avec Piano Analytics SDK

<script>
window._pac = window._pac || {};
_pac.cookieDomain = '.monsite.com'; // certains paramètres sont à définir avant l'appel à la librairie
</script>

<script src="https://tag.aticdn.net/piano-analytics.js"></script>
<script>

//Déclaration de la configuration
pa.setConfigurations({
    site: 12345, 
    collectDomain: "https://logsx.xiti.com"
  });

//définition du consentement de l'utilisateur
if("condition vérifiant si l'utilisateur est optin"){
    pa.privacy.setMode('optin');
}
else if("condition vérifiant si l'utilisateur est exempt"){
    pa.privacy.setMode('exempt');
}
else{ // si l'utilisateur est optout
    pa.privacy.setMode('optout');
}

//définition du buffer privacy 
pa.privacy.include.properties(
    ['user_category', 'langue_article'], ['exempt']);

//définition de nos propriétés persistantes
pa.setProperties({
        'user_category': 'premium',
        'date_publication': 1660225582 
      }, {
        'persistent': true
});

//définition de notre event de page
pa.sendEvent('page.display',
  {
    'page' : 'mon_actualite',
    'page_chapter1':'actualites',
    'site_level2':'zone abonnes',
    'user_id':12345
  }
);
</script>

Migration de vos autres events

Je vous rassure tout de suite, le plus gros du travail a été réalisé. En effet les autres events se déclenchant sur la page héritent directement de la configuration, des choix privacy ou encore des setprops.

Nous n’avons donc en réalité qu’à traduire l’event en lui-même et, pour cela, vous pouvez vous baser sur le mode opératoire présenté dans le chapitre « Gestion du contenu et du déclenchement du tag » qui vous explique comment retrouver l’event et les propriétés équivalentes entre l’AS2 et Piano Analytics.

Pour vous aider tout de même, voici quelques exemple d’events communs migrés :

Event de click

// AS2 Tagging 
tag.click.send({
    elem: this,
    name: 'clickName',
    chapter1:'chapter 1',
    level2: '2',
    type: 'navigation'
});

// PA Tagging
tag.events.send('click.navigation',
  {
    'click': 'clickName',
    'click_chapter1': 'chapter 1',
    'site_level2':'espace abonnes'
  }
);

// Piano Analytics SDK
pa.sendEvent('click.navigation',
  {
    'click': 'clickName',
    'click_chapter1': 'chapter 1',
    'site_level2':'espace abonnes'
  }
);

Impression d’une bannière avec le tag Publisher

// AS2 Tagging
tag.publisher.set({
    impression: {
       campaignId: '[Acquisition]',
       creation: '[Banner_main]',
       variant: '[Blue]',
       format: '[400x300]',
       generalPlacement: '[Sidebar]',
       detailedPlacement: '[Sidebar_bottom]',
       advertiserId: '[My Site]',
       url: '[www.mysite.com]'
    }
 });
 tag.dispatch(); 

// PA Tagging
tag.events.send('publisher.display',
  {
    'onsitead_type': 'Publisher',
    'onsitead_campaign': 'Acquisition',
    'onsitead_category': 'Gold',
    'onsitead_creation': 'Banner_main',
    'onsitead_variant': 'Blue',
    'onsitead_format': '400x300',
    'onsitead_general_placement': 'Sidebar',
    'onsitead_detailed_placement': 'Sidebar_bottom',
    'onsitead_advertiser': 'My Site',
    'onsitead_url': 'www.mysite.com'
  }
);

// Piano Analytics SDK
tag.events.send('publisher.display',
  {
    'onsitead_type': 'Publisher',
    'onsitead_campaign': 'Acquisition',
    'onsitead_category': 'Gold',
    'onsitead_creation': 'Banner_main',
    'onsitead_variant': 'Blue',
    'onsitead_format': '400x300',
    'onsitead_general_placement': 'Sidebar',
    'onsitead_detailed_placement': 'Sidebar_bottom',
    'onsitead_advertiser': 'My Site',
    'onsitead_url': 'www.mysite.com'
  }
);

Chargement d’une page de résultats (moteur interne)

// AS2 Tagging 
tag.internalSearch.set({
    keyword:'Mobile Phone',
    resultPageNumber:'2'
});
tag.dispatch();

// PA Tagging
tag.events.send('internal_search_result.display',
  {
    'ise_keyword': 'Mobile Phone',
    'ise_page': 2
  }
);

// Piano Analytics SDK
pa.sendEvent('internal_search_result.display', {
    'ise_keyword' : 'Mobile Phone',
    'ise_page' : 2
  });

Migration de vos campagnes marketing

Parlons ici d’un délicat : la nomenclature de marquage de vos campagnes marketing.

Si vous utilisez la nomenclature « at_ » sortie il y a quelques années, alors pas de problème, le tag Piano Analytics SDK est capable de les reprendre automatiquement. Exemple d’une campagne avec la nomenclature « at » :

https://www.monsite.com/landing-page.html?at_medium=email&at_campaign=offre-2022

Si vous êtes dans ce cas, alors vous pouvez arrêter la lecture de ce chapitre ici.

En revanche, si vous utilisez l’ancienne nomenclature de marquage avec le paramètre « xtor », les campagnes ne seront plus restituées dans les données lors du passage sur le nouveau SDK car il n’a pas été développé pour l’interpréter. Exemple de la même campagne mais avec la nomenclature « xtor » :

https://www.monsite.com/landing-page.html?xtor= ES-12

Ici il n’y a pas de solution miracle, il vous faudra réaliser ce travail de traduction des campagnes. Voici quelques conseils pour réaliser cette opération :

  • Parmi toutes les campagnes déclarées, isolez celles remontant encore réellement des données. Vous pouvez par exemple choisir de ne traduire que les campagnes ayant généré au moins 100 visites sur les 3 derniers mois. Exemple d’analyse à réaliser dans Data Query :
  • Une fois les campagnes à migrer isolées, répertoriez où celles-ci sont configurées dans votre écosystème digital (configuration Google Ads, routeur Emailing, ad server),
  • Appuyez vous ensuite sur la documentation pour comprendre où replacer chaque information dans les paramètres AT (documentation xtor et documentation at_). Vous pouvez également vous aider de l’outil campaign Creator pour comprendre au mieux l’exercice de traduction,
  • Remplacez les anciennes URL trackés par celles créées via ce processus.

Ce travail peut être fastidieux selon la quantité de campagnes à migrer, mais il est indispensable pour réaliser votre transition vers Piano Analytics SDK.

Gestion de vos objectifs/Conversions (spécifique AS2 tagging)

Dans l’analytics Suite 2 il était nécessaire de déclarer un label de page dans une interface spécifique pour que celle-ci comptabilise une conversion lorsqu’un hit de page était reçu avec ce libellé.

Cette déclaration préalable ne sera plus prise en compte lors de votre bascule vers Piano Analytics et vos conversions ne remonterons plus. La solution est d’indiquer, via une propriété spécifique du tag, que nous nous trouvons sur une conversion via l’event de votre choix (event de page, ou pas).

Voici par exemple un tag de page AS2 avec une déclaration préalable de son label en tant qu’objectif :

var tag = new ATInternet.Tracker.Tag(); 
tag.page.set({
    name:'confirmation_abonnement'
});
tag.dispatch();

Voici comment gérer cela en ‘tag first’ avec piano Analytics :

  pa.sendEvent('page.display', {
    'page' : 'confirmation_abonnement',
    'goal_type': 'souscription'
  });

Comme vous le voyez, la propriété goal_type permet de comptabiliser automatiquement une conversion dans les données Piano Analytics et la valeur ‘souscription’ permet de type cette conversion. Vous pourrez ainsi ventiler vos conversions par types dans vos analyses.

On peut cependant noter que ce format n’est pas très pratique, puisqu’il oblige a faire un tag de page spécifique pour chaque conversion.

Pour rendre cela plus flexible, 2 options s’offrent à vous (en tout cas 2 que j’envisage dans mes projets).

1. Génération d’un event spécifique

Une solution peut être de créer un event spécifique qui sera déclenché lors de la conversion. Vous pouvez même utiliser le nom de la page comme contrainte pour ne déclencher l’event que lorsque l’utilisateur est sur la bonne page :

pa.sendEvent('validation_souscription', {
  'goal_type': 'souscription'
});

2. ajout de goal_type via un setprops conditionné

Si vous souhaitez continuer de gérer vos conversions via votre event de page, vous pouvez pour cela ajouter un setprop spécfique. Ce setprop sera conditionné par exemple par la présence du nom de la page concernée dans un objet contenant lui-même les valeurs de goal type.

// la variable 'pageName' contient le nom de la page

// Déclaration de nos pages d'objectifs (les clés de l'objet) et de nos goal_type (les valeurs de notre objet)
let conversions = {
  'confirmation_abonnement':'souscription',
  'page_merci_commande':'e-commerce',
  'abonnement_newsletter_confirmation':'newsletter'
}

if(conversions[pageName] !== undefined){ // si ma page existe bien dans l'objet 'conversions'
  pa.setProperties({ // Alors j'ajoute une nouvelle propriété
    'goal_type': conversions[pageName] // récupération dynamique du goal_type
  }, {
    'persistent': false // repris uniquement sur l'event à venir (page.display)
});
};
  pa.sendEvent('page.display', { // reprise du goal_type dans l'event page.display
    'page' : 'confirmation_abonnement'
  });

Conclusion

Comme vous le voyez, la migration de votre tracking vers Piano Analytics SDK est un projet à part entière, même si vous serez largement autonome dans votre travail si vous disposez d’un TMS.

A vous d’avancer étape par étape dans le portage de votre marquage existant, de faire des tests réguliers et de profiter de cette migration pour réaliser du nettoyage.

Je n’ai évidemment pas pu parler de tous les cas de figures, aussi la référence reste encore et toujours la documentation officielle et le centre support en cas de besoin.