Ext Ops Panel
Открыть панель

Главная/Документация/Кастом-метрики

Контракт кастом-метрик

Версия контракта 2.1.0 · schema_version payload 2.0

Кастом-метрики позволяют сервису вашего расширения слать любые важные для него числа (конверсии, выручку, здоровье бэкенда, воронки) в ту же панель, которая уже отслеживает установки и здоровье Chrome Web Store. Контракт metadata-driven и dual-surface: каждая метрика объявляет, где и как она появляется и на дашборде, и в Telegram-статусе, поэтому панель остаётся generic-рендерером без продуктового кода.

↓ Скачать OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml · info.version 2.1.0

1. Как это работает (dual-surface)

Ваш сервис отдаёт один эндпоинт метрик. Ext Ops Panel – клиент: он пуллит этот эндпоинт (никогда наоборот) и рендерит ответ. Ответ управляет двумя поверхностями сразу:

Панель мержит ваши метрики с базовыми first-party метриками (установки / удаления / возвраты / фидбек / Chrome Web Store / здоровье), которые живут в зарезервированном табе base и строке Telegram.

2. Эндпоинт и авторизация

Панель вызывает ваш эндпоинт методом GET и ждёт payload v2 в ответе 200. Два режима пула используют одинаковую структуру:

Авторизация – одна из none, bearer или api_key_header. Секреты хранятся зашифрованными at rest на бэкенде панели и никогда не передаются дашборд-расширению. Если пул падает, панель деградирует gracefully (last snapshot или скрыто), не ломая базовые метрики.

3. Структура payload

Верхнеуровневый payload несёт раскладку (табы, секции, строки Telegram) и массив метрик.

ПолеТипЗаметки
schema_versionstringОбязательно. Должно быть "2.0".
generated_atstring (date-time)Когда payload собран (свежесть / idle-детект).
tabsTab[]Табы дашборда. См. раздел 4.
sectionsSection[]Группы метрик внутри таба (tab, id, title, order).
telegramTelegramMetaШаблон header + lines[]. См. раздел 7.
metricsMetric[]Сами метрики. См. разделы 5 и 6.

4. Табы и иконки табов

Tab – это { id, title, order, icon? }. Зарезервированный таб base – наш (icon="layout-dashboard"); ваш сервис добавляет остальные. Когда табов много, активный таб показывает иконку + название, а остальные схлопнуты до иконки (название остаётся в tooltip и aria-label).

Два id табов зарезервированы за панелью: base (вкладка Basic) и monitoring (встроенная вкладка Monitoring, которую заполняют отчёты heartbeat, см. контракт мониторинга). Объявлять их в tabs не нужно: объявление игнорируется, заголовок, иконку и порядок задаёт панель. Метрики адресуются в них через dashboard.tab как обычно.

Значение icon – одно из двух:

Панель везёт каталог из 60 имён Lucide (держим MV3-бандл лёгким). Тот же каталог служит иконкам табов и пиктограммам целей (раздел 5.3):

5. Метрика: значение + два блока размещения

Метрика несёт типизированное значение плюс два независимых блока: dashboard (где и как в расширении) и telegram (где и как в сообщении). Любой можно опустить или скрыть через show:false, так что метрика может жить только на одной поверхности.

ПолеЗаметки
idСтабильный id метрики, уникальный в payload.
kindТип данных (раздел 5.1). Определяет, какие поля значения применяются.
labelКаноническое человекочитаемое имя.
value и поля по kindПоля payload зависят от kind (раздел 5.1).
severityОдно из ok, info, warn, crit (маппится в цвет / эмодзи).
shareableНеобяз. boolean, default true. При false метрика owner-only: на расшаренном read-only проекте отсекается на сервере и не доходит до наблюдателя / public (для финансовых метрик). Управляет доступом, не размещением.
dashboard{ show, tab, section, order, name, render, options } (раздел 5.2).
telegram{ show, line, pos, format, sep, when } (разделы 6 и 8).

5.1. Kinds (тип данных + payload)

Kinds – открытое множество: неизвестный kind рендерер игнорирует (forward-compat).

kindПоля payloadПример использования
numbervalue (+ опц. delta, unit)счётчики установок / фидбека
deltavalue, delta (рендер 1200 (+72))total (+today)
percentvalue (0 до 100)engine success rate
durationseconds или minutesp50/p95/avg, свежесть
bytesbytes или kbразмер вывода (~522KB)
ratingvalue, count5.0★ (1)
moneyvalue, currency, опц. deltaMRR, расход
statusstate + severityhealth-светофор
seriespoints:[{date,count}], опц. мульти series:[{name,points}], стеком при stack: truedaily installs, active users
intradayseries:[{name,intraday:[{minute,value}]}], опц. step_min (по умолчанию 15), tzсегодня по 15-минутным бакетам
hour_histogram{ weekdays:[...], start, end, tz }конверсии по часам
funnelsteps:[{key,label,value}]pricing до checkout
breakdownitems:[{label,value,unit?,sub?/money?,country?,parts?}], опционально ranges:{today,week,month,all}подписки по плану, by engine, by surface; визиты по источникам или странам
listitems:[{text,count?,severity?}] на верхнем уровне метрики, не внутри valueпоследние ошибки
tableitems:[{…}], один объект на строку; колонки из dashboard.options.columns, без них ключи первой строкиочереди, последние отправки
textvalue (string)произвольный текст

Метрика 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. Render (как показать на дашборде)

dashboard.render выбирает визуал (обычно совпадает с kind, но может отличаться, напр. показать percent как badge). Тоже открытое множество.

renderВидОпции (dashboard.options)
kpiКарточка: имя + крупное значение + дельта; со stack – части одним рядом под значениемtone, stack
badgeСтатус-пилюля / пиктограмма по severity–
chartЛинейный график (Month/Lifetime, rolling-7 avg, hover); при stack: true – стековые столбцы по дням со строкой Totalseries, defaultMode, stack
intradayСегодня по бакетам (96 за UTC-сутки): стек-бары или сглаженные линии, ось времени HH:MM, hover-ридаут по всем сериямmode (bars | lines, по умолчанию lines)
hour_histogramПочасовые бары, группировка Weekdays / Weekend / By day of weekseries
funnelСтупени с конверсией между ними–
breakdownГоризонтальные категорийные бары со значением + долей от суммы. С ranges – переключатель сегодня / 7 дней / 30 дней / всё время; с map: countries – карта мира над списком; со stack – бары из переключаемых частейitems, defaultRange, map, stack
tableТаблица (напр. фидбек)columns
textАбзац / inline–
statusСостояние-светофор по severity–
listСписок элементов с опц. count / severity–

intraday vs chart. Ось chart – дневная (points[].date = YYYY-MM-DD), а режим Month режет её до последних 30 точек: 96 внутридневных бакетов туда не помещаются. intraday – это наш первопартийный Today-график, вынесенный в контракт: та же договорённость, где minute – минуты от начала UTC-суток (0, 15, …, 1425), но серии и данные объявляет источник. Присылайте только непустые бакеты – остальное уплотняет панель.

Пустой бакет – это null, а не 0. Для среднего (например, среднее время конвертации) ноль был бы неправдой, поэтому линия рвётся, а не падает в пол. Пропущенный бакет и value: null эквивалентны: линия разрывается, бара нет, в ридауте «–».

Стек: итог и части на одном графике и в одном списке. Если метрика складывается из частей (например, просмотры двух сайтов), присылайте итог и части ОДНОЙ метрикой: панель рисует части друг над другом, а легенда включает и выключает каждую, без отдельных вкладок и выпадающих фильтров. Оба расширения аддитивны: рендерер, который их не знает, рисует ту же метрику как раньше.

График: kind: "series" и dashboard.options.stack: true. Серии складываются в порядке массива, первая – нижний слой. Панель рисует столбец на каждый день (в lifetime – на каждое 7-дневное окно) с сегментом на каждую видимую серию, поэтому верх столбца – точная сумма видимых серий; день, где все они нулевые, остаётся без столбца. Клик по легенде скрывает слой (последний видимый остаётся); остальные перерисовываются от нуля, ось значений следует за видимой суммой. При наведении на столбец или переходе к нему стрелками ридаут показывает каждый видимый слой и строку Total. Lifetime складывает rolling-7 среднее каждой серии, с маркером ~. Любое другое значение stack оставляет наложенные линии.

Список: kind: "breakdown", в dashboard.options.stack – упорядоченный список частей [{key, label}], у каждого элемента в items и в каждом окне ranges – parts: {"key": число}. value обязан быть равен сумме всех частей. Панель показывает по чипу на часть, чип переключает её для всей карточки во всех окнах. Строка показывает сумму видимых частей, строки с нулём скрываются, список пересортировывается по этой сумме, а бар делится на сегменты по видимым частям. С map: "countries" круги на карте следуют видимым суммам. Элемент без parts считается целиком при любых тогглах.

{ "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" } ] } } }

Плитка: kpi-плитка вида number или delta принимает тот же список dashboard.options.stack и parts: {"key": число} у самой метрики. Крупное значение остаётся value, оно обязано быть равно сумме всех частей. Под ним панель печатает части одним рядом в порядке стека (на узкой плитке ряд переносится), цветом, который то же имя получает на стековом графике вкладки; часть с нулём приглушена. Переключателей у плитки нет. Заголовки в списке проектов и Telegram по-прежнему читают 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. Список проектов: пользователи и цели с базовой вкладки

Список проектов показывает несколько чисел веб-сервиса, не открывая его. Панель берёт их только с вкладки base и только у метрики с числовым value. Выигрывает первое совпадение.

МетрикаКак панель её находитГде показывается
DAU, WAU, MAUid, label или имя на дашборде ровно dau, wau или mau (регистр не важен).WAU и MAU в колонке «Пользователи», DAU в «Сегодня».
Цели 1–3id ровно goal_1, goal_2 или goal_3.«Сегодня», в одной строке с DAU: пиктограмма и число.

Цель это целевое действие ваших пользователей: регистрации, покупки, отправленные формы. Окно числа выбираете вы, панель показывает его как есть. Рекомендуем считать за сегодня и называть окно в подписи (например, «Регистрации сегодня»). Подпись идёт в тултип, до 80 символов. Поле icon принимает имя из каталога раздела 4; эмодзи у целей не принимаются. Без пиктограммы или с неизвестным именем панель рисует target. Ноль показывается приглушённым прочерком.

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

Панель хранит только последние значения, как у DAU. Каждый успешный опрос их перезаписывает, а цель, пропавшая из ответа, исчезает из списка.

Несколько поверхностей

Источник кастомных метрик принадлежит одной поверхности вашего сервиса, и панель хранит числа по поверхностям. Список проектов показывает их сумму, страница проекта показывает каждую поверхность отдельно.

Расширения Chrome

Для расширения действует то же соглашение. Если его фид шлёт dau и цели на вкладке base, список проектов показывает их отдельной строкой под счётчиками установок, удалений, фидбека и возвратов, в том же формате, что строка «Сегодня» веб-сервиса.

5.4. Пороги (warn и crit)

Числовая метрика может нести thresholds. Тогда severity метрики считает сама панель, при каждом опросе и на каждом приёме отчёта heartbeat, и рисует порог рядом со значением: 41 (порог 20/100), жёлтым между warn и crit, красным за crit. Severity, которую сервис прислал для такой метрики, заменяется посчитанной.

ПолеСмысл
directionabove: хуже, когда выше (очередь, CPU). below: хуже, когда ниже (свободный диск, дни до конца).
warn, critЧисла. Для above значение от warn жёлтое, от crit красное; below зеркально. Для above warn не больше crit, для below не меньше.
unitНеобязательная подпись до 16 байт: порог 80/95%.
recoverНеобязательный гистерезис: метрика, которая была warn или crit, возвращается в ok, только когда значение ушло за recover; между ними она остаётся 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" }
}

Плановый опрос. У веб-сервиса в настройках источника можно включить плановый опрос. Тогда панель опрашивает эндпоинт с интервалом от пола вашего тарифа (5 минут на триале и Indie, 1 минута на Studio и Fleet) до 24 часов тем же запросом, что и любой опрос: честный User-Agent и не больше одного запроса в минуту. Опрос занимает один unit мониторинга, поэтому на Free его нет. Пробитый порог открывает инцидент по этой метрике и присылает уведомление; повторный пробой уведомления не повторяет. Инцидент закрывается после одного опроса без пробоя, если интервал не меньше часа, и после двух опросов подряд без пробоя, если интервал короче. Неудачный опрос ничего не меняет и показывает причину в последней ошибке. Инциденты дают до 20 метрик с порогами на источник.

6. Палитрой владеет панель

Цвета графиков и серий назначает панель из бренд-токенов, а не источник. options.series[] несёт только идентичность ({key,label,order}); поле color в payload – advisory и игнорируется. Семантические ключи маппятся на фикс-токены (paid в accent, free в приглушённый нейтрал, failed/error в danger, ready/success в success); прочие ключи берут категориальную бренд-палитру позиционно. Заливки и линии серий держат контраст не ниже 3:1 в обеих темах (WCAG 1.4.11), текст и подписи не ниже 4.5:1. Это защищает бренд от произвольного внешнего hex.

7. Шаблоны Telegram

telegram.header – заголовок сообщения; telegram.lines[] – логические строки ({ id, order, prefix, when }). Композер собирает все метрики с данной telegram.line, фильтрует по when, сортирует по pos, рендерит каждую по шаблону format и склеивает через sep метрики. Сначала печатается prefix строки. Всё – plain text.

ПлейсхолдерВыход
{value}По kind (number 1,200; percent 99%)
{value:k}1.2k / 3.0M (компактно)
{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}Эмодзи по severity: 🟢 ok, 🟡 warn, 🔴 crit, ⚪ idle
{threshold}Порог метрики: threshold 20/100 (строка Telegram по-английски). Без него в format у метрики с порогами в конце дописывается (threshold 20/100).
{trend}↗ / ↘ (±5pp к 7d)

8. Условия показа (when)

when управляет тем, показывается ли поле метрики (или целая строка Telegram). Значения:

whenПоказывает поле
alwaysВсегда (по умолчанию).
nonzeroТолько когда значение не 0 / не пусто.
severity>=warnТолько когда severity warn или crit.
changedТолько когда изменилось с прошлого снапшота.

Неизвестные значения when ведут себя как always (forward-compat).

9. Пример 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"
      }
    }
  ]
}
Поддерживается только нативный v2. Ваш сервис возвращает метрики плюс готовую раскладку по этому контракту; коннекторов легаси-форматов нет. Kinds и типы рендера – из объявленных множеств выше; неизвестные игнорируются, так что новый источник никогда не ломает старую сборку панели.

10. Машиночитаемый контракт

Формальное, импортируемое описание этого эндпоинта – файл OpenAPI 3.1. Он несёт ту же версию контракта (2.1.0) и является единственным каноническим машинным артефактом, общим для всех языков.

↓ Скачать OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml