9 мин чтенияИнтеграции

Сокращайте ссылки в примечаниях к релизам и отслеживайте каждую загрузку

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

Marius Voß
DevRel · edge infra
Обложка в пиксельном стиле: как сокращать ссылки в примечаниях к релизам - одна стабильная ссылка на последнюю версию перенаправляется с v2.3 на v2.4, а для Slack, X и рассылки используются отдельные ссылки с тегами

Примечания к релизам распространяются дальше почти всего, что пишет инженерная команда. Никто их не измеряет. Вы вставляете ссылку на загрузку в текст релиза, кто-то копирует её в Slack, маркетинг добавляет её в рассылку, сопровождающий публикует её в X. Через полгода половина таких ссылок ведёт на уже несуществующий файл, и ни одна не сообщает вам ничего. Чтобы правильно сокращать ссылки в примечаниях к релизам, нужны три вещи: одна стабильная короткая ссылка на "последнюю" версию, которую вы перенаправляете при каждом релизе, отдельная ссылка с тегами для каждого канала и каждой версии и workflow на событии release: published, который создаёт обе ссылки, чтобы никто не должен был держать это в памяти.

Вот и весь ответ. Дальше я расскажу, как связать всё это, не устроив беспорядок, и что данные о кликах могут и не могут сказать вам после этого.

Я наблюдал множество проектов, которые вручную работали со ссылками в примечаниях к релизам GitHub, и сценарий сбоя всегда один и тот же: кто-то даёт ссылку на файл с версией, ссылку цитируют в теме форума или ответе на Stack Overflow, а следующий релиз незаметно оставляет её без владельца. Если вы уже управляете ссылками как кодом, описанный ниже подход хорошо сочетается с короткими ссылками под управлением Terraform, с той разницей, что ссылки релизов меняются по расписанию, которым вы вручную не управляете.

Почему ссылки в примечаниях к релизам устаревают и теряют атрибуцию

Здесь скрываются две отдельные проблемы. Для них нужны разные решения.

Первая - устаревание ссылок. GitHub предоставляет стабильные URL для страницы релиза (/releases/latest) и для файлов через /releases/latest/download/asset-name, но второй вариант работает только тогда, когда имя файла во всех релизах остаётся неизменным, согласно собственной документации GitHub о ссылках на релизы. Большинство конвейеров сборки добавляют версию в имя файла, поэтому app-2.3.0.dmg превращается в app-2.4.0.dmg, и работавший на прошлой неделе URL загрузки "последней" версии теперь возвращает 404. Ссылки на документацию тоже устаревают, когда сайт документации реорганизуют. Более общие закономерности описаны в нашей стратегии предотвращения устаревания ссылок; примечания к релизам - просто место, где проблема проявляется сильнее всего, потому что ссылки распространяются дальше.

Вторая проблема - атрибуция, и она менее заметна. GitHub REST API действительно сообщает download_count для каждого файла релиза, что больше, чем многие думают. Но он не сообщает, откуда пришла загрузка. Всплеск в 4 000 загрузок на следующий день после релиза мог быть вызван рассылкой, обсуждением на Hacker News или одним корпоративным клиентом, чей CI зацикленно скачивает бинарный файл. Ссылки, вставленные в Slack и личные сообщения, полностью убирают источник перехода - это небольшая версия проблемы атрибуции тёмных социальных каналов.

Стабильная ссылка на последнюю версию, которую вы перенаправляете при каждом релизе

Создайте одну короткую ссылку, например get.example.dev/latest, и относитесь к ней как к указателю. Её используют каждая страница документации, значок в README и скрипт установки. При каждом стабильном релизе вы обновляете её назначение. Слаг не меняется, поэтому ничего из того, где эту ссылку уже процитировали, не ломается.

В Elido такой указатель - обычная ссылка. Создайте её один раз с помощью POST /v1/workspaces/{workspace_id}/links, передав domain_id брендированного домена, slug и destination_url. Сохраните id из ответа 201. Перенаправление - это PATCH /v1/workspaces/{workspace_id}/links/{link_id} с новым destination_url и больше ничем; слаг, теги и история кликов сохраняются.

Оставьте тип 302. Для ссылок Elido по умолчанию используется 302, и менять его здесь не стоит: 301 по умолчанию можно кэшировать согласно RFC 9110, поэтому браузер, увидевший перенаправление в прошлом месяце, может больше никогда не спросить его заново. Указатель, который браузеры запомнили навсегда, больше не является указателем. Подробнее - в материале 301 и 302: перенаправления для коротких ссылок.

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

Заранее решите один вопрос. Должна ли ссылка на последнюю версию вести на файл или на страницу релиза? Я бы направил её на страницу релиза для всего, где есть больше одной сборки под разные платформы, а отдельные ссылки на последнюю версию для каждой платформы (/latest-mac, /latest-linux) оставил бы только тогда, когда документации по установке действительно нужен прямой файл. Чем меньше перемещаемых указателей, тем меньше шансов ошибиться при их перенаправлении.

Теги для ссылок на отдельные релизы GitHub

Ссылка на последнюю версию отвечает на вопрос "ссылка всё ещё работает?". Она не может ответить на вопрос "какой канал сработал?", потому что все кликают по одному и тому же слагу. Поэтому для каждого релиза при публикации создаётся небольшой набор ссылок, по одной на канал.

Вот часть, которую пропускает большинство руководств по UTM. Добавление utm_source=slack к URL на github.com не даёт ничего полезного, потому что вы никогда не увидите аналитику GitHub. UTM оправданы только тогда, когда назначение - сайт, который вы измеряете, например ваша документация или собственная страница загрузки. Если назначение - GitHub, отдельная короткая ссылка для каждого канала и есть атрибуция: клик подсчитывается при перенаправлении, ещё до того, как его увидит GitHub.

КаналСлаг для v2.4.0НазначениеЧто говорят вам клики
Сообщество Slackv2-4-0-slackСтраница релиза GitHubКлики из вашего сообщества
X / Mastodonv2-4-0-socialСтраница релиза GitHubОхват за пределами существующих пользователей
Рассылкаv2-4-0-newsРуководство по обновлению в документации + UTMКлики и поведение на сайте в вашей аналитике
Последняя версия (стабильная)latestТекущий релиз, перенаправляетсяОбщий спрос по всем версиям

Добавляйте к каждой ссылке на отдельный релиз версию и канал (["release", "v2.4.0", "slack"]), потому что именно по тегам вы позже извлечёте этот набор: GET .../links?tags=v2.4.0 перечисляет всё для одного релиза. Делайте значения UTM простыми и одинаковыми в разных релизах. Правила, которые я бы скопировал, есть в руководстве по соглашениям об именовании UTM.

Создание ссылок при событии публикации релиза

В соседнем руководстве описано общее создание ссылок в CI, поэтому здесь я остановлюсь на особенностях релизов. Триггером служит release с типом активности published. Согласно списку событий workflow GitHub, published срабатывает и для стабильных релизов, и для предварительных, включая предварительные релизы, опубликованные из черновика. Именно поэтому следующий шаг перенаправления проверяет флаг prerelease.

name: release-links
on:
  release:
    types: [published]

jobs:
  links:
    runs-on: ubuntu-latest
    env:
      API: https://api.elido.app/v1/workspaces/${{ vars.ELIDO_WORKSPACE_ID }}
      DOMAIN_ID: ${{ vars.ELIDO_DOMAIN_ID }}
      TAG: ${{ github.event.release.tag_name }}
      PAGE: ${{ github.event.release.html_url }}
      ELIDO_TOKEN: ${{ secrets.ELIDO_TOKEN }}
    steps:
      - name: Create one link per channel
        run: |
          v=$(echo "$TAG" | tr '.' '-')
          for ch in slack social news; do
            body=$(jq -n --argjson d "$DOMAIN_ID" --arg s "$v-$ch" \
              --arg u "$PAGE" --arg t "$TAG" --arg c "$ch" \
              '{domain_id:$d, slug:$s, destination_url:$u, tags:["release",$t,$c]}')
            code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$API/links" \
              -H "Authorization: Bearer $ELIDO_TOKEN" \
              -H "Content-Type: application/json" -d "$body")
            case "$code" in 201|409) ;; *) echo "create $ch failed: $code"; exit 1;; esac
            echo "- $ch: https://get.example.dev/$v-$ch" >> "$GITHUB_STEP_SUMMARY"
          done

      - name: Repoint the latest link
        if: ${{ !github.event.release.prerelease }}
        run: |
          curl -sf -X PATCH "$API/links/${{ vars.ELIDO_LATEST_LINK_ID }}" \
            -H "Authorization: Bearer $ELIDO_TOKEN" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg u "$PAGE" '{destination_url:$u}')"

Сводка job даёт тому, кто публикует объявление, готовый список ссылок, а код 409 при повторном запуске означает, что слаг уже существует, поэтому повторённый workflow не завершается с ошибкой и не создаёт дубликаты. Ссылка в рассылке должна была бы вести на вашу документацию с UTM в назначении; здесь я оставил её на странице релиза, чтобы пример был короче.

Три особенности, которые стоили мне целого дня

Первая незаметна. Если ваш конвейер релиза публикует релиз с помощью стандартного GITHUB_TOKEN, этот workflow никогда не запускается, потому что события, созданные с помощью GITHUB_TOKEN, не запускают новые workflow. Ни ошибки, ни пропущенной job, вообще ничего. Вместо этого публикуйте релиз с токеном GitHub App.

Вторая - точки. Я преобразую v2.4.0 в v2-4-0 для слага, потому что строки версий с точками в превью чатов похожи на расширения файлов, а некоторые клиенты странно превращают их в ссылки.

Третья: не позволяйте workflow редактировать текст релиза, если это не обязательно. Это работает (gh release edit --notes-file), но перезаписывает текст, который только что одобрил человек, и создаёт события edited, на которые могут реагировать другие автоматизации. Сводка job менее умна, зато гораздо безопаснее. Повторным попыткам посвящён отдельный материал: лимиты API и идемпотентность.

Если вы всё ещё вставляете ссылки на релизы вручную, настройка описанного workflow займёт около двадцати минут. Создайте бесплатный workspace Elido, направьте на него брендированный домен и позвольте следующему тегу создать собственные ссылки.

Как отслеживать клики по примечаниям к релизам по каналам

После двух или трёх релизов данные начинают отвечать на вопросы, на которые счётчик GitHub ответить не может.

Сравнение по каналам - самый простой вариант. Получите ссылки с тегом версии, затем прочитайте сводку кликов каждой ссылки в разрезе link_id. Если v2-4-0-news опережает v2-4-0-social в пять раз на протяжении трёх релизов, вы узнали, где на самом деле находятся ваши пользователи, и это редко совпадает с предположением команды. Объявление с наибольшим числом лайков часто не то, которое отправляет людей к загрузке, поэтому будьте готовы к возражениям, когда впервые покажете цифры, и дождитесь третьего релиза подряд, прежде чем кто-то начнёт менять план запуска на их основе.

У ссылки на последнюю версию есть менее очевидная особенность. Каждый клик сохраняет назначение, к которому ссылка разрешилась в тот момент, поэтому разбивка аналитики по назначению в разрезе ссылки на последнюю версию распределяет её трафик по версиям. После перенаправления вы можете наблюдать, как доля старого назначения уменьшается, и видеть, как долго пользователи продолжают приходить со страниц в кэше и старых закладок. Это настоящая динамика обновления, измеренная в верхней части воронки.

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

Есть два честных ограничения. Клики - не загрузки: кто-то может перейти на страницу релиза и уйти, а download_count GitHub остаётся источником истины для завершённых загрузок. Кроме того, боты кликают по ссылкам на релизы, особенно загрузчики превью ссылок в чат-приложениях, поэтому изучайте тенденции по нескольким релизам, а не доверяйте одному дню. На странице функции аналитики перечислены доступные в каждом плане виды разбивки.

Как сохранить старые ссылки на релизы

Ссылки на отдельные релизы никогда не перемещаются. v2-3-0-slack указывает на тег v2.3.0 в марте и продолжает указывать на него через пять лет, чего и ожидает человек, читающий старую тему форума. Меняется только ссылка на последнюю версию и только для стабильных релизов.

Единственный случай, когда стоит трогать старую ссылку, - отозванный релиз. Если в v2.4.0 обнаружилась ошибка с потерей данных, не удаляйте его ссылки: перенаправьте каждую ссылку v2-4-0-* на v2.4.1 тем же вызовом PATCH и добавьте короткую заметку в текст релиза. Удаление оставляет людей, сохранивших ссылку, в тупике именно в тот момент, когда исправление нужнее всего. Новая версия всегда лучше 404.

Для проектов, которые также хранят старые ссылки на релизы в README, скриптах установки и метаданных менеджера пакетов, руководство по сокращателям URL для разработчиков рассказывает, где ещё полезны короткие ссылки. Полный набор REST-операций находится на странице API и SDK.

Читайте основную статью → Управляйте короткими ссылками как Terraform

Другие материалы в блоге

Частые вопросы

Как дать ссылку на последний релиз GitHub?

GitHub поддерживает /releases/latest для страницы релиза и /releases/latest/download/asset-name для файла, если у файла во всех релизах сохраняется одно и то же имя. Если в именах файлов указана версия, поставьте перед ним короткую ссылку и перенаправляйте её при каждом релизе.

Можно ли отслеживать клики по загрузкам релиза GitHub?

Частично. GitHub REST API сообщает download_count для каждого файла релиза, но не передаёт данные об источнике перехода, стране или канале, поэтому не позволяет понять, пришла ли загрузка из Slack, X или рассылки. Отдельная короткая ссылка для каждого канала перед ссылкой на файл даёт такую разбивку.

Использовать для короткой ссылки на последний релиз перенаправление 301 или 302?

Используйте 302. Браузеры могут кэшировать 301 на неопределённый срок, поэтому читатель, перешедший по ссылке в прошлом месяце, может продолжить попадать на старую версию после перенаправления ссылки. Для ссылок Elido по умолчанию используется 302, поэтому назначение остаётся под вашим контролем при каждом клике.

Почему мой workflow релиза не запускается, когда другой workflow публикует релиз?

События, созданные с помощью GITHUB_TOKEN репозитория, не запускают новые workflow, кроме workflow_dispatch и repository_dispatch. Если конвейер релиза публикует его с помощью GITHUB_TOKEN, триггер release: published никогда не срабатывает. Вместо этого публикуйте релиз с токеном GitHub App или персональным токеном с детализированными разрешениями.

Работают ли параметры UTM в ссылках на github.com?

Они передаются дальше, но для вас бесполезны, поскольку вы не видите аналитику GitHub. UTM дают пользу только тогда, когда назначение - сайт, который вы измеряете, например ваша документация или страница загрузки. Для назначений на github.com атрибуцию даёт отдельная короткая ссылка для каждого канала.

Что происходит со старыми ссылками на релизы после выхода новой версии?

Ничего, если настроить всё описанным образом. Ссылки на отдельные релизы навсегда указывают на собственные теги, а перемещается только одна ссылка на последнюю версию. Если релиз отозван, перенаправьте его ссылки на исправленную версию, а не удаляйте их, чтобы люди, сохранившие старую ссылку, по-прежнему попадали в полезное место.

Попробуйте Elido

Вставьте URL - получите короткую ссылку

Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.

Бесплатно, без регистрации · 2 в день

Попробуйте Elido

URL-сокращатель с хостингом в ЕС: собственные домены, глубокая аналитика, открытый API. Бесплатный тариф - без банковской карты.

Теги
shorten links in release notes
github release notes links
track clicks on release notes
github actions
release automation
link rot

Читать дальше