LW IT Solutions
« Blog Overview /Digital Analytics/Tutorials / Tutorial: Znalezienie wersji kontenera, w której zmienił...
This post in other languages:

Tutorial: Znalezienie wersji kontenera, w której zmienił się tag

Tutorial: Znalezienie wersji kontenera, w której zmienił się tag
Spis treści
  1. Co pokazuje lista wersji, a co ukrywa
  2. Pobranie wersji przez API
  3. Suma kontrolna wskazująca zmianę
  4. Znaleźć wersję, potem porównać dwie
  5. Przydział zapytań i kopia lokalna
  6. Pytanie, na które API nie odpowiada
  7. Pytania i odpowiedzi
  8. Źródła

Liczba w raporcie zmienia się we wtorek. Kontener opublikowano w tym miesiącu cztery razy, a lista wersji mówi, kto to zrobił i kiedy – i zupełnie nic o tym, co poruszyło się w środku.

Otworzyć i przeczytać cztery wersje da się. Czterdziestu już nie, a czterdzieści to liczba zwyczajowa w chwili, gdy ktoś to zauważy. Poniżej droga, która najpierw znajduje wersję, a dopiero potem porównuje dwie.

Osiem wersji kontenera z datą, osobą publikującą i sumą kontrolną jednego tagu, z wyróżnionymi dwoma wierszami, w których ta suma się zmieniła
Suma kontrolna jednego elementu w ośmiu wersjach. Dwa wiersze różnią się od poprzedzającego i tylko te dwa warto otworzyć.

Co pokazuje lista wersji, a co ukrywa

Każda publikacja tworzy wersję, a ta wersja jest pełną, niezmienną kopią kontenera z tamtej chwili. Lista tych wersji pokazuje cztery rzeczy: numer, nazwę, kto opublikował i kiedy.

Nie pokazuje zawartości. Dwie wersje mogą różnić się jednym znakiem w jednej zmiennej, a lista i tak wygląda tak samo. Liczba zmian obok wersji pomaga trochę, a myli wiele, bo liczy zmiany w obszarze roboczym, a nie różnicę wobec wersji poprzedniej – obszar roboczy, w którym coś zmieniono i cofnięto, podnosi tę liczbę, niczego nie zmieniając.

Jedynym polem, które odpowiedziałoby na pytanie wprost, są notatki wersji. To dowolny tekst, pisany przy publikacji, i w niemal każdym istniejącym kontenerze pusty. Wypełnianie go jest najtańszą możliwą poprawą całego tego problemu – i pomaga dopiero od teraz.

Pobranie wersji przez API

API Tag Managera zwraca te same wersje, które pokazuje interfejs, oraz pełną zawartość każdej z nich. Wystarczy dostęp do odczytu.

# Zakres: https://www.googleapis.com/auth/tagmanager.readonly

BASIS="https://tagmanager.googleapis.com/tagmanager/v2"
PFAD="accounts/6000000001/containers/7000000002"

# 1 lista wersji - krotka, bez zawartosci
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASIS/$PFAD/version_headers" | jq -r \
  '.containerVersionHeader[] | [.containerVersionId, .name] | @tsv'

# 2 jedna pelna wersja
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASIS/$PFAD/versions/41" > version-41.json

Dwie rzeczy o pierwszym wywołaniu warto wiedzieć. Zwraca nagłówki zamiast zawartości, co czyni je tanim i jest właściwą drogą do wyliczenia. I jest stronicowane: kontener z wieloma wersjami zwraca nextPageToken, a skrypt go pomijający po cichu pracuje wyłącznie z najnowszą stroną.

Drugie wywołanie jest tym drogim, bo zwraca cały kontener. To powód istnienia następnego rozdziału: pobranie czterdziestu pełnych wersji, by porównać jeden tag, to dużo danych na jedno pytanie.

Suma kontrolna wskazująca zmianę

Każdy element kontenera niesie pole fingerprint, a zmienia się ono, gdy tylko element zostanie zmodyfikowany. Porównanie sumy kontrolnej jednego tagu w kolejnych wersjach odpowiada więc na pytanie bez porównywania czegokolwiek innego.

import json, glob

GESUCHT = "25"   # tagId: GA4 - Event - purchase

vorher = None
for datei in sorted(glob.glob("version-*.json"),
                    key=lambda d: int(d.split("-")[1].split(".")[0])):
    daten = json.load(open(datei))
    tags  = {t["tagId"]: t for t in daten.get("tag", [])}
    tag   = tags.get(GESUCHT)

    if tag is None:
        print(f"{datei:16s} nie istnieje")
    else:
        marke = tag["fingerprint"]
        hinweis = "ZMIENIONY" if vorher and marke != vorher else ""
        print(f"{datei:16s} {marke}  {hinweis}")
        vorher = marke

Wynikiem jest jedna linia na wersję, a te ciekawe zgłaszają się same. Tag nietknięty przez trzydzieści wersji, a potem raz zmieniony, daje dokładnie jeden oznaczony wiersz – i ten wiersz jest odpowiedzią.

Dwie właściwości sumy kontrolnej zasługują na uwagę. Jest nieprzejrzysta – mówi, że coś się zmieniło, a nie co – i do tego kroku to wystarcza. I zmienia się przy każdej edycji, łącznie ze zmianą nazwy: przemianowany tag pojawia się jako zmieniony, choć zachowuje się tak samo. Zestawianie po tagId zamiast po nazwie tego nie zmienia, ale przeżywa tę zmianę nazwy, która w ogóle utrudniła odnalezienie elementu.

Znaleźć wersję, potem porównać dwie

Gdy wersja jest znana, porównanie odbywa się między dwoma plikami, a nie czterdziestoma – i opłaca się na kopii ujednoliconej, a nie na surowym eksporcie.

# tylko szukany tag z obu wersji, posortowany
for v in 43 44; do
  jq -S --arg n "GA4 - Event - purchase" \
    '.tag[] | select(.name == $n)' version-$v.json > tag-$v.json
done

diff -u tag-43.json tag-44.json

To -S czyni wynik czytelnym. Bez niego klucze JSON wracają w kolejności wytworzonej przez API, a porównanie dwóch obiektów o tym samym znaczeniu potrafi mieć kilkadziesiąt linii. Posortowanie kluczy usuwa to całkowicie, a zostaje właściwa zmiana.

Trzy pola warto w porównaniu pominąć, bo zmieniają się same z siebie: fingerprint, służący tutaj do znalezienia wersji, a nie do jej opisu, oraz każde pole znacznika czasu albo ścieżki niosące numer wersji. Wszystko inne, co się różni, jest różnicą prawdziwą.

Przydział zapytań i kopia lokalna

API Tag Managera ma ograniczenia na minutę i na dobę, a pętla po czterdziestu pełnych wersjach szybko na nie wpada. Niepowodzeniem jest odpowiedź 429, a skrypt bez przerwy zamienia jedno badanie w API zablokowane do końca godziny.

for v in $(cat versionen.txt); do
  [ -f "version-$v.json" ] && continue          # juz jest
  curl -s -H "Authorization: Bearer $TOKEN" \
    "$BASIS/$PFAD/versions/$v" > "version-$v.json"
  sleep 2
done

Pominięcie istniejącego pliku to ważna linia. Wersje są niezmienne, więc raz pobranej wersji nigdy nie trzeba pobierać ponownie – a katalog z nimi staje się archiwum odpowiadającym na następne pytanie bez dotykania API w ogóle.

To archiwum warto zakładać celowo, a nie ubocznie. Cotygodniowe zadanie pobierające najnowsze wersje nic nie kosztuje – a dzięki temu historia jest dostępna także dla kontenera, do którego ktoś później traci dostęp.

Pytanie, na które API nie odpowiada

Trzy rzeczy pozostają poza zasięgiem, a wiedza o tym, które to, oszczędza długiego szukania czegoś, czego nie ma.

Pierwszą jest to, kto zmienił określony element. Wersja zapisuje, kto ją opublikował, a publikacja może zawierać pracę kilku osób z kilku tygodni. Autorstwa na poziomie elementu API nie ma, a interfejs również nie.

Drugą jest wszystko, co nigdy nie stało się wersją. Zmiana wprowadzona w obszarze roboczym i cofnięta przed publikacją nie zostawia śladu, podobnie jak skasowany obszar roboczy. Historia jest historią publikacji, a nie edycji.

Trzecią jest skutek. Dwie wersje mogą różnić się warunkiem wyzwalania spełniającym się o dziesięć procent częściej, a nic w kontenerze tego nie mówi – różnicą jest jedna linia konfiguracji, a jej następstwo stoi w danych. Dlatego cała ta procedura jest pierwszą połową badania: daje chwilę i zmianę, a drugą połową jest sprawdzenie, czy liczby również poruszyły się w tej chwili.

Pytania i odpowiedzi

Czy porównanie sumy kontrolnej wykrywa także zmianę triggera albo zmiennej, z których korzysta tag?

Nie, i to jest najważniejsza luka tej procedury. Pole fingerprint tagu zmienia się tylko wtedy, gdy edytowany jest sam tag. Tag odwołuje się do swoich triggerów przez ich identyfikatory, a do zmiennych przez ich nazwy w podwójnych nawiasach klamrowych. Jeśli ktoś zmieni warunek wyzwalania triggera albo wartość zmiennej, tag pozostaje identyczny znak po znaku, a wraz z nim jego suma kontrolna, choć zachowuje się już inaczej.

Pętlę z artykułu da się więc rozszerzyć na zależności: z tagu odczytuje się identyfikatory w firingTriggerId i blockingTriggerId oraz zbiera nazwy wszystkich odwołań {{…}}, a następnie porównuje sumy kontrolne tych triggerów w daten["trigger"] i tych zmiennych w daten["variable"] w kolejnych wersjach tak samo jak sumę tagu.

Nawet to nie wystarcza w pełni, bo zmienne mogą odwoływać się do innych zmiennych. Dokładną drogą jest śledzenie odwołań, dopóki nie przestaną pojawiać się nowe; prostszą jest porównanie od razu sum kontrolnych wszystkich elementów kontenera przy szukaniu wersji.

Co się dzieje, gdy pobranie w pętli kończy się odpowiedzią 429?

Plik i tak powstaje. Bez dodatkowych opcji curl -s zapisuje do wyjścia także odpowiedź z błędem, więc version-44.json zawiera wtedy komunikat błędu API w formacie JSON zamiast kontenera. Przy następnym przebiegu plik uchodzi za istniejący i zostaje pominięty, a więc właśnie linia, która czyni archiwum wartościowym, utrwala błąd.

Skrypt analizujący tego nie zgłasza, lecz wprowadza w błąd: daten.get("tag", []) zwraca dla komunikatu błędu pustą listę, a wersja pojawia się jako „nie istnieje”, jakby tag został w niej usunięty.

Samo dodanie -f nie wystarczy, bo przekierowanie > tworzy plik, zanim curl odpowie; zostałby wtedy pusty plik, pominięty tak samo. Pewną drogą jest plik tymczasowy, któremu nadaje się docelową nazwę dopiero wtedy, gdy curl zakończył się powodzeniem, a jq -e .containerVersionId znajduje wartość.

Czy wersja, w której zmienił się tag, jest też tą, która działała w danym dniu?

Niekoniecznie. Każda publikacja tworzy wersję, ale nie każda wersja zostaje opublikowana: wersję da się utworzyć bez uruchamiania jej na stronie, a starszą wersję da się później opublikować ponownie, na przykład żeby cofnąć zmianę. Numer wersji oddaje więc kolejność powstania, a nie kolejność publikacji.

Przy zestawieniu z raportem liczy się to, kiedy która wersja zaczęła działać. Jeśli porównanie znajduje zmianę w wersji 44, trzeba ustalić, czy i kiedy wersja 44 została opublikowana i czy potem nie obowiązywała znów starsza wersja.

Jakich uprawnień potrzebuje cotygodniowe zadanie archiwizujące?

Wystarczy dostęp do odczytu, jak podaje artykuł, z zakresem tagmanager.readonly. Jeśli zadanie działa na koncie usługi, jego adres e-mail musi zostać dodany w Tag Managerze jako użytkownik z uprawnieniem do odczytu konta albo kontenera; sam zakres nie daje dostępu. Z samym prawem odczytu zadanie nie może niczego opublikować, a utracony klucz ujawnia wtedy zawartość kontenera, ale nie pozwala jej zmienić.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Napisanie komentarza

Odmienne wyniki z innych kont i pytania o konfigurację są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.

ALL ARTICLES & CATEGORIES

CCTV

Śledź tę kategorię przez RSS

Cloud & AI

Śledź tę kategorię przez RSS

Data Privacy

Wszystkie artykuły w tej kategorii (14) Śledź tę kategorię przez RSS

Digital Analytics

Wszystkie artykuły w tej kategorii (52) Śledź tę kategorię przez RSS

Digital Marketing

Wszystkie artykuły w tej kategorii (35) Śledź tę kategorię przez RSS

IT & Networks

Wszystkie artykuły w tej kategorii (17) Śledź tę kategorię przez RSS

Music Production

Wszystkie artykuły w tej kategorii (11) Śledź tę kategorię przez RSS

Raspberry Pi

Śledź tę kategorię przez RSS

Smart Home

Wszystkie artykuły w tej kategorii (18) Śledź tę kategorię przez RSS

Tworzenie stron internetowych

Śledź tę kategorię przez RSS

Wtyczki i triki WordPress

Śledź tę kategorię przez RSS