W części 1 pokazałem trik: przekierowanie 303 podaje Ci media UUID, którego Jira potrzebuje, żeby wyrenderować obrazek inline. Ręczne przekierowanie, przeczytaj nagłówek Location, wyciągnij UUID regexem. Deterministycznie. Bez modelu.

To jeden krok. Prawdziwy raport to więcej niż jeden krok. Oto cały flow i repo, które go odpala.

Create, upload, resolve, embed

Wrzucenie raportu z obrazkami do Jiry to sekwencja czterech etapów i kolejność ma znaczenie:

  1. Create - twórz issue albo komentarz z placeholderowym ADF.
  2. Upload - wgraj każdy obrazek jako załącznik. Multipart, z nagłówkiem X-Atlassian-Token: no-check. Pominiesz ten nagłówek i Jira odrzuca Cię komunikatem o cross-site, który nie mówi Ci nic użytecznego.
  3. Resolve - zamień każde attachment id na media UUID trikiem z 303 z części 1.
  4. Embed - przebuduj ADF z prawdziwymi węzłami media wskazującymi na rozwiązane UUID.

Każdy etap jest deterministyczny. Nie ma w tym pipelinie momentu, w którym model językowy o czymkolwiek decyduje. Model, jeśli w ogóle go używasz, napisał słowa raportu wcześniej. Wszystko od “zamień to na Jira ADF” w dół to robota na bajtach.

Raw, Enriched, Builders

Kod dzieli się na trzy warstwy, a trzymanie ich osobno to cały powód, dla którego to zostaje utrzymywalne:

  • Raw - transport. Wywołania HTTP do Jiry. Upload, resolve przez 303, finalny update. Ta warstwa nie wie nic o kształcie Twojego raportu.
  • Enriched - obsługa obrazków. Wyciągnij szerokość i wysokość prosto z bajtów PNG albo JPEG, bo węzły media w ADF chcą prawdziwych wymiarów, a zgadywanie daje Ci rozciągnięte screenshoty.
  • Builders - konstrukcja ADF. mediaSingle opakowujący węzeł media, tabele, statusowe lozenges, struktura raportu. Czyste funkcje. Input wchodzi, ADF wychodzi, za każdym razem tak samo.

Buildery niosą jeden guard warty wspomnienia: ADF ma limit rozmiaru, a raport z wieloma obrazkami inline plus tabelami potrafi go przekroczyć. Builder tnie na 60KB i degraduje się łagodnie, zamiast pozwolić Jirze odrzucić cały komentarz. Załącznik, który nie jest obrazkiem, wpada w fallback na węzeł external zamiast wywalić run.

Repo

Publiczne, AGPL-3.0:

github.com/darco81/context-first-qa-patterns

Sklonuj, odpal npm run example, a wykona cały flow na wewnątrzprocesowej mockowej Jirze, która emituje prawdziwe 303 - więc widzisz resolve UUID na żywo, nie dotykając realnej instancji. 25 testów, zielone. TypeScript, zero zależności od jakiejkolwiek platformy orkiestracji. Cztery workflowy n8n, które to zainspirowały, są dołączone jako referencja, ale rdzeń jest standalone - przeniesiesz go do zwykłego skryptu.

Dostajesz warstwę transportu. Deterministyczną podłogę. Tę część, która zamienia “mam raport i kilka screenshotów” w “renderuje się inline w Jirze, poprawnie, za każdym razem”.

Gdzie to przestaje być snippetem

Repo to mechanizm. Nie maszyna.

W produkcji odpalam to jako jeden etap wewnątrz większego deterministycznego pipeline’u QA - orkiestracja przez wiele tasków, audit trail, cykl statusów, konfiguracja multi-projekt, screenshoty przechwytywane i diffowane, zanim w ogóle dotrą do tej warstwy. Ta orkiestracja to miejsce, gdzie jest prawdziwa dźwignia, i jej nie ma w repo. Repo daje Ci najtrudniej wywalczony trik, żebyś przestał tracić na niego popołudnia.

Jeśli odpalasz raportowanie QA z dużą liczbą obrazków na skalę, a cztery etapy to ułamek tego, czego potrzebujesz - orkiestracja multi-task, weryfikacja deterministyczna, pełny pipeline - to jest rozmowa konsultingowa. Zobacz sdet.it/uslugi.

Ale trik z 303? Ten jest Twój. Sklonuj i idź wyrenderować trochę obrazków.

Kod i mock w pierwszym komentarzu.