Ext Ops Panel
Ouvrir le tableau de bord

Accueil/Docs/Métriques personnalisées

Contrat de métriques personnalisées

Version du contrat 2.1.0 · payload schema_version 2.0

Les métriques personnalisées permettent au service de votre extension de faire remonter les chiffres qui comptent pour elle (conversions, revenus, santé du backend, entonnoirs) dans le même panneau qui suit déjà les installations et l’état dans le Chrome Web Store. Le contrat est piloté par les métadonnées et à double surface : chaque métrique déclare où et comment elle apparaît, à la fois sur le tableau de bord et dans le statut Telegram, et le panneau reste ainsi un moteur de rendu générique, sans code propre à un produit.

↓ Télécharger l’OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml · info.version 2.1.0

1. Fonctionnement (double surface)

Votre service expose un endpoint de métriques. Ext Ops Panel est le client : il interroge cet endpoint (jamais l’inverse) et affiche la réponse. La réponse alimente deux surfaces à la fois :

Le panneau fusionne vos métriques avec les métriques de base natives (installations / désinstallations / réinstallations / retours / Chrome Web Store / santé), qui se trouvent dans l’onglet réservé base et dans la ligne Telegram du même nom.

2. L’endpoint et l’authentification

Le panneau appelle votre endpoint en GET et attend le payload v2 comme réponse 200. Deux modes de récupération partagent la même forme :

L’authentification utilise l’un des modes none, bearer ou api_key_header. Les secrets sont stockés chiffrés au repos sur le backend du panneau et ne sont jamais transmis à l’extension du tableau de bord. Si une récupération échoue, le panneau se dégrade proprement (dernier instantané ou bloc masqué) sans casser les métriques de base.

3. Structure du payload

Le payload de premier niveau contient la mise en page (onglets, sections, lignes Telegram) et le tableau des métriques.

ChampTypeRemarques
schema_versionchaîneObligatoire. Doit valoir "2.0".
generated_atchaîne (date-heure)Moment où le payload a été composé (fraîcheur / détection d’inactivité).
tabsTab[]Onglets du tableau de bord. Voir la section 4.
sectionsSection[]Groupes de métriques dans un onglet (tab, id, title, order).
telegramTelegramMetaModèle header + lines[]. Voir la section 7.
metricsMetric[]Les métriques elles-mêmes. Voir les sections 5 et 6.

4. Onglets et icônes d’onglets

Un onglet est { id, title, order, icon? }. L’onglet réservé base appartient au panneau (icon="layout-dashboard") ; votre service ajoute les autres. Lorsque de nombreux onglets sont ouverts, l’onglet actif affiche l’icône et le titre, les autres se réduisent à l’icône seule (le titre reste dans l’infobulle et l’aria-label).

Deux id d’onglet sont réservés au panneau : base (l’onglet Basic) et monitoring (l’onglet Monitoring intégré que remplissent les rapports de heartbeat, voir le contrat de monitoring). Ne les déclarez pas dans tabs : une déclaration est ignorée et le panneau fixe leur titre, leur icône et leur ordre. Les métriques les visent toujours via dashboard.tab.

La valeur icon est l’une des deux choses suivantes :

Le panneau fournit un catalogue sélectionné de 60 noms Lucide (léger pour le bundle MV3). Le même catalogue sert aux icônes d’onglets et aux icônes d’objectifs (section 5.3) :

5. Métrique : valeur + deux blocs de placement

Une métrique porte une valeur typée et deux blocs indépendants : dashboard (où et comment dans l’extension) et telegram (où et comment dans le message). Chacun peut être omis ou masqué via show:false : une métrique peut ainsi n’exister que sur une seule surface.

ChampRemarques
idIdentifiant de métrique stable, unique dans le payload.
kindType de données (section 5.1). Sélectionne les champs de valeur qui s’appliquent.
labelLibellé lisible canonique.
value et champs propres au typeLes champs du payload dépendent de kind (section 5.1).
severityL’une des valeurs ok, info, warn, crit (associée à une couleur / un emoji).
shareableBooléen facultatif, true par défaut. Avec false, la métrique est réservée au propriétaire : sur un projet partagé en lecture seule, elle est retirée côté serveur et n’atteint jamais une vue observateur / publique (à utiliser pour les métriques financières). Régit l’accès, pas le placement.
dashboard{ show, tab, section, order, name, render, options } (section 5.2).
telegram{ show, line, pos, format, sep, when } (sections 6 et 8).

5.1. Types (type de données + payload)

Les types forment un ensemble ouvert : un type inconnu est ignoré par le moteur de rendu (compatibilité ascendante).

kindChamps du payloadExemple d’utilisation
numbervalue (+ delta, unit facultatifs)nombre d’installations / de retours
deltavalue, delta (affiche 1200 (+72))total (+aujourd’hui)
percentvalue (0 à 100)taux de réussite du moteur
durationseconds ou minutesp50/p95/moy., fraîcheur
bytesbytes ou kbtaille de sortie (~522 Ko)
ratingvalue, count5,0★ (1)
moneyvalue, currency, delta en optionMRR, dépenses
statusstate + severityfeu tricolore de santé
seriespoints:[{date,count}], series:[{name,points}] multiple en option, empilées avec stack: trueinstallations quotidiennes, utilisateurs actifs
intradayseries:[{name,intraday:[{minute,value}]}], step_min en option (15 par défaut), tzaujourd’hui par intervalles de 15 minutes
hour_histogram{ weekdays:[...], start, end, tz }conversions par heure
funnelsteps:[{key,label,value}]de la page tarifs au paiement
breakdownitems:[{label,value,unit?,sub?/money?,country?,parts?}], ranges:{today,week,month,all} en optionabonnements par forfait, par moteur, par surface ; visites par source ou par pays
listitems:[{text,count?,severity?}] au niveau supérieur de la métrique, pas sous valueerreurs récentes
tableitems:[{…}], un objet par ligne ; colonnes depuis dashboard.options.columns, sinon les clés de la première lignefiles d’attente, envois récents
textvalue (chaîne)texte libre

Une métrique table :

{ "id": "queues", "kind": "table", "label": "Mail queues",
  "items": [ { "queue": "outbound", "size": 3, "oldest": "2m" },
             { "queue": "deferred", "size": 0, "oldest": "-" } ],
  "dashboard": { "show": true, "tab": "backend", "section": "engines", "order": 5, "render": "table",
                 "options": { "columns": ["queue", "size", "oldest"] } } }

5.2. Rendu (comment afficher sur le tableau de bord)

dashboard.render choisit la visualisation (correspond généralement à kind, mais peut différer, par ex. afficher un percent comme un badge). C’est aussi un ensemble ouvert.

renderAspectOptions (dashboard.options)
kpiCarte : nom + grande valeur + delta ; avec stack, les parties sur une rangée sous la valeurtone, stack
badgePastille de statut / pictogramme selon la gravité–
chartGraphique linéaire (Mois/Toute la période, moyenne glissante sur 7 jours, survol) ; avec stack: true, colonnes empilées par jour avec une ligne Totalseries, defaultMode, stack
intradayAujourd’hui par intervalles (96 par jour UTC) : barres empilées ou lignes lissées, axe du temps HH:MM, lecture au survol sur toutes les sériesmode (bars | lines, par défaut lines)
hour_histogramBarres horaires, groupées Jours ouvrés / Week-end / Par jour de la semaineseries
funnelÉtapes avec conversion entre elles–
breakdownBarres catégorielles horizontales avec valeur + part du total. Avec ranges, un sélecteur aujourd’hui / 7 jours / 30 jours / depuis le début ; avec map: countries, une carte du monde au-dessus de la liste ; avec stack, des barres découpées en parties activablesitems, defaultRange, map, stack
tableTableau (par exemple les retours)columns
textParagraphe / en ligne–
statusÉtat du feu tricolore selon la gravité–
listListe d’éléments avec nombre / gravité facultatifs–

intraday vs chart. L’axe de chart est quotidien (points[].date = YYYY-MM-DD) et sa vue Mois ne garde que les 30 derniers points : les 96 intervalles d’aujourd’hui n’y tiennent donc pas. intraday est le graphique Aujourd’hui natif du panneau, exposé comme type du contrat : même convention, où minute compte les minutes depuis le début de la journée UTC (0, 15, …, 1425), mais les séries et les données sont déclarées par la source. N’envoyez que les intervalles non vides, le panneau complète le reste.

Un intervalle vide vaut null, et non 0. Pour une moyenne (par exemple le temps moyen de conversion), un zéro serait un mensonge : la ligne s’interrompt au lieu de tomber à zéro. Un intervalle manquant et value: null sont équivalents : la ligne s’interrompt, aucune barre n’est dessinée et la lecture au survol affiche un tiret.

Empilé : le total et ses parties dans un graphique et une liste. Quand une métrique se compose de parties (par exemple les pages vues de deux sites), envoyez le total et les parties comme UNE seule métrique : le panneau dessine les parties les unes sur les autres et la légende active ou désactive chacune, sans onglets supplémentaires ni filtres déroulants. Les deux extensions sont additives : un moteur de rendu qui ne les connaît pas dessine la même métrique qu’avant.

Graphique : kind: "series" avec dashboard.options.stack: true. Les séries s’empilent dans l’ordre du tableau : la première est la couche du bas. Le panneau dessine une colonne par jour (sur toute la période, par fenêtre de 7 jours) avec un segment par série visible, si bien que le haut d’une colonne est la somme exacte des séries visibles ; un jour où elles valent toutes zéro n’a pas de colonne. Un clic sur une entrée de la légende masque une couche (la dernière visible reste affichée) ; le reste est redessiné depuis zéro et l’axe des valeurs suit le total visible. Au survol d’une colonne, ou en s’y rendant avec les flèches, la lecture affiche chaque couche visible et une ligne Total. Toute la période empile la moyenne glissante sur 7 jours de chaque série, marquée par ~. Toute autre valeur de stack conserve les lignes superposées.

Liste : kind: "breakdown" avec dashboard.options.stack défini comme liste ordonnée de parties [{key, label}], et parts: {"key": number} sur chaque élément, dans items et dans chaque fenêtre de ranges. value doit être égal à la somme de toutes les parties. Le panneau affiche une puce par partie qui l’active ou la désactive pour toute la carte, dans toutes les fenêtres. Une ligne affiche la somme de ses parties visibles, les lignes à zéro sont masquées, la liste est retriée selon cette somme et la barre est découpée en un segment par partie visible. Avec map: "countries", les cercles suivent les sommes visibles. Un élément sans parts compte en entier, quoi qu’on désactive.

{ "id": "site_sources", "kind": "breakdown", "label": "Sources",
  "items": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ],
  "ranges": { "today": [ { "label": "Search engines", "value": 30, "parts": { "makcards": 29, "journalingapp": 1 } } ],
              "month": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ] },
  "dashboard": { "show": true, "tab": "site", "section": "site_sources", "render": "breakdown",
                 "options": { "defaultRange": "month",
                              "stack": [ { "key": "makcards", "label": "makcards.online" },
                                         { "key": "journalingapp", "label": "journalingapp.app" } ] } } }

Tuile : une tuile kpi de type number ou delta accepte la même liste dashboard.options.stack et parts: {"key": number} sur la métrique elle-même. La grande valeur reste value, qui doit être égale à la somme de toutes les parties. En dessous, le panneau affiche les parties sur une rangée dans l'ordre de la pile (sur une tuile étroite, la rangée passe à la ligne), avec la couleur que ce même nom a sur un graphique empilé de l'onglet ; une partie à zéro est atténuée. La tuile n'a pas d'interrupteurs. Les en-têtes de la liste des projets et Telegram continuent de lire value.

{ "id": "site_views_today", "kind": "number", "label": "Page views", "value": 42,
  "parts": { "portal": 30, "cabinet": 4, "backoffice": 8 },
  "dashboard": { "show": true, "tab": "site", "section": "site_today", "render": "kpi",
                 "options": { "stack": [ { "key": "portal", "label": "zakazindia.ru" },
                                         { "key": "cabinet", "label": "Client cabinet" },
                                         { "key": "backoffice", "label": "Backoffice" } ] } } }

5.3. La liste des projets : utilisateurs et objectifs de l’onglet Base

La liste des projets affiche quelques chiffres d’un service web sans l’ouvrir. Le panneau les lit uniquement dans l’onglet base et uniquement quand la métrique a une value numérique. La première correspondance l’emporte.

MétriqueComment le panneau la trouveOù elle est affichée
DAU, WAU, MAUL’id, le label ou le nom dans le dashboard vaut exactement dau, wau ou mau (casse indifférente).WAU et MAU dans la colonne Utilisateurs, DAU dans Aujourd’hui.
Objectifs 1 à 3L’id vaut exactement goal_1, goal_2 ou goal_3.Aujourd’hui, sur la même ligne que DAU : une icône et un nombre.

Un objectif est une action cible de vos utilisateurs : inscriptions, achats, formulaires envoyés. Vous choisissez la fenêtre du nombre et le panneau l’affiche tel quel. Nous recommandons de compter la journée en cours et de nommer la fenêtre dans le label (par exemple, Inscriptions aujourd’hui). Le label sert d’infobulle, jusqu’à 80 caractères. Le champ icon prend un nom du catalogue de la section 4 ; les emoji ne sont pas acceptés pour les objectifs. Sans icône, ou avec un nom inconnu, le panneau dessine target. Un zéro s’affiche comme un tiret atténué.

{ "id": "goal_1", "kind": "number", "label": "Sign-ups today", "value": 12, "icon": "user-plus",
  "dashboard": { "show": true, "tab": "base", "section": "goals", "render": "kpi" } }

Le panneau conserve uniquement les dernières valeurs, comme pour DAU. Chaque collecte réussie les remplace, et un objectif absent de la réponse disparaît de la liste.

Plusieurs surfaces

Une source de métriques personnalisées appartient à une surface de votre service, et le panneau conserve les chiffres par surface. La liste des projets affiche leur somme ; la page du projet montre chaque surface séparément.

Extensions Chrome

Une extension suit la même convention. Lorsque son flux envoie dau et des objectifs sur l’onglet base, la liste des projets les affiche sur une ligne supplémentaire sous les compteurs d’installations, de désinstallations, de retours et de réinstallations, au même format que la ligne Aujourd’hui d’un service web.

5.4. Seuils (warn et crit)

Une métrique numérique peut porter thresholds. Le panneau calcule alors lui-même la sévérité de la métrique, à chaque interrogation et à chaque réception de heartbeat, et affiche les limites à côté de la valeur : 41 (threshold 20/100), en jaune entre warn et crit, en rouge au-delà de crit. La sévérité que votre service envoie pour une telle métrique est remplacée par la valeur calculée.

ChampSignification
directionabove : pire quand c'est plus haut (file d'attente, CPU). below : pire quand c'est plus bas (disque libre, jours restants).
warn, critDes nombres. Avec above, une valeur égale ou supérieure à warn est jaune et égale ou supérieure à crit est rouge ; below fait l'inverse. Avec above, warn ne doit pas dépasser crit, avec below il ne doit pas lui être inférieur.
unitLibellé facultatif de 16 octets au plus pour la mention : threshold 80/95%.
recoverHystérésis facultative : une métrique qui était warn ou crit ne revient à ok qu'une fois la valeur au-delà de recover ; entre les deux, elle reste warn.
{
  "id": "queue",
  "kind": "number",
  "label": "Mail queue",
  "value": 41,
  "thresholds": { "direction": "above", "warn": 20, "crit": 100, "recover": 15 },
  "dashboard": { "show": true, "tab": "backend", "section": "mail", "order": 1, "render": "kpi" },
  "telegram": { "show": true, "line": "mail", "pos": 10, "format": "queue {value}", "when": "severity>=warn" }
}

Interrogation planifiée. Pour un service web, vous pouvez activer l'interrogation planifiée d'une source dans ses paramètres. Le panneau interroge alors le point de terminaison selon un intervalle allant du minimum de votre forfait (5 minutes sur Trial et Indie, 1 minute sur Studio et Fleet) jusqu'à 24 heures, avec la même requête que toute interrogation : un User-Agent honnête et au plus une requête par minute. Elle utilise une unité de surveillance et n'est donc pas disponible sur Free. Un seuil dépassé ouvre un incident pour cette métrique et vous prévient ; un nouveau dépassement ne vous prévient pas une seconde fois. L'incident se ferme après une interrogation sans dépassement si l'intervalle est d'une heure ou plus, et après deux interrogations de suite sans dépassement s'il est plus court. Une interrogation échouée ne change rien et affiche sa raison comme dernière erreur. Jusqu'à 20 métriques avec seuils par source donnent des incidents.

6. La palette appartient au panneau

Les couleurs des graphiques et des séries sont attribuées par le panneau à partir des jetons de la marque, et non par la source. options.series[] ne porte que l’identité ({key,label,order}) ; un champ color dans le payload est indicatif et ignoré. Les clés sémantiques correspondent à des jetons fixes (paid vers l’accent, free vers le neutre atténué, failed/error vers danger, ready/success vers succès) ; les autres clés reçoivent la palette catégorielle de la marque selon leur position. Les aplats et les lignes des séries gardent un contraste d’au moins 3:1 dans les deux thèmes (WCAG 1.4.11), le texte et les libellés d’au moins 4,5:1. Cela protège la marque de couleurs hexadécimales externes arbitraires.

7. Modèles Telegram

telegram.header est l’en-tête du message ; telegram.lines[] sont les lignes logiques ({ id, order, prefix, when }). Le compositeur rassemble toutes les métriques ayant un telegram.line donné, les filtre selon when, les trie selon pos, affiche chacune via son modèle format et les joint avec le sep de chaque métrique. Le prefix de la ligne est affiché en premier. Tout est en texte brut.

VariableRésultat
{value}Selon le type (nombre 1,200 ; pourcentage 99%)
{value:k}1.2k / 3.0M (compact)
{delta:+}+72 / -3
{value:pct}99%
{seconds:dur}28s / 5m 47s
{minutes:age}45m / 2h 30m / never
{bytes:size}~522KB / 1.4MB
{value}★ ({count})5.0★ (1)
{sev}Emoji de gravité : 🟢 ok, 🟡 warn, 🔴 crit, ⚪ idle
{threshold}Le seuil de la métrique : threshold 20/100. S'il n'est pas dans le format, (threshold 20/100) est ajouté à la fin d'une métrique avec seuils.
{trend}↗ / ↘ (±5 pts vs 7 j)

8. Conditions de visibilité (quand)

when détermine si un champ de métrique (ou une ligne Telegram entière) s’affiche. Valeurs :

whenAffiche le champ
alwaysToujours (par défaut).
nonzeroUniquement si la valeur n’est pas 0 / vide.
severity>=warnUniquement si la gravité est warn ou crit.
changedUniquement si elle a changé depuis l’instantané précédent.

Les valeurs when inconnues se comportent comme always (compatibilité ascendante).

9. Exemple de payload

{
  "schema_version": "2.0",
  "generated_at": "2026-07-02T23:17:30Z",
  "tabs": [
    { "id": "conv", "title": "Conversions", "order": 1, "icon": "trending-up" }
  ],
  "sections": [
    { "tab": "conv", "id": "by_hour", "title": "By hour", "order": 0 }
  ],
  "telegram": {
    "header": "{name} • {cws.users} • {cws.rating}★ ({cws.rating_count})",
    "lines": [
      { "id": "base", "order": 0 },
      { "id": "conv", "order": 1, "prefix": "{sev} Conv " }
    ]
  },
  "metrics": [
    {
      "id": "conv_today",
      "kind": "number",
      "label": "Conversions today",
      "value": 254,
      "severity": "ok",
      "shareable": true,
      "dashboard": {
        "show": true,
        "tab": "conv", "section": "by_hour", "order": 10,
        "name": "Today", "render": "kpi", "options": {}
      },
      "telegram": {
        "show": true,
        "line": "conv", "pos": 20,
        "format": "today {value}", "sep": " • ", "when": "always"
      }
    }
  ]
}
Seule la v2 native est prise en charge. Votre service renvoie des métriques et une mise en page prête, conformément à ce contrat ; il n’existe pas de connecteurs pour d’anciens formats. Les types et les types de rendu proviennent des ensembles déclarés ci-dessus ; les valeurs inconnues sont ignorées, afin qu’une source plus récente ne casse jamais une version plus ancienne du panneau.

10. Le contrat lisible par machine

La description formelle et importable de cet endpoint se trouve dans le fichier OpenAPI 3.1. Il porte la même version de contrat (2.1.0) et constitue l’unique artefact machine canonique, commun à toutes les langues.

↓ Télécharger l’OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml