vitrinka.docs
QA a testování

Nahlásit problém

Jedno tlačítko ve vaší aplikaci: člověk napíše, co se pokazilo, případně označí kde, a poslední minuty se přiloží samy. Hlášení dorazí do vašeho Inboxu jako úkol, nebo počká na admina, když za nahlašujícího nikdo neručí.

Co nahlašující uvidí

Lidé ve vaší aplikaci uvidí v rohu stránky velké tlačítko Nahlásit problém — nebo vůbec nic, pokud widget otevíráte vlastním ovládacím prvkem, třeba položkou v menu Nápověda. Mluví česky nebo anglicky: jazykem prohlížeče, pokud ho vaše aplikace nenastaví.

  1. Nejdřív popis. Malá karta se zeptá Co se pokazilo?. Jedna věta a Odeslat je celé hlášení. Karta říká, kolik posledních minut se přikládá, a vypíše, co všechno je přiloženo.
  2. Ukažte nám kde, pokud chcete. Ukažte nám kde zmrazí stránku pod průhlednou vrstvou a nahlašující problém označí kruhem, obdélníkem, šipkou, nebo skrytím, které zakryje soukromou část obrazovky. Značky se číslují 1, 2, 3… a každá může mít poznámku. Dokud neexistuje první značka, drží se vedle ukazatele nápověda, která gesto ukazuje: klikněte, podržte a veďte kolem problému. Z klávesnice Enter značku začne, šipky ji posouvají, Shift se šipkami mění její velikost a Enter ji umístí.
  3. Vlastní klávesová zkratka. Výchozí žádná není. V nastavení karty (⚙) si ji nahlašující nahraje tím, že ji stiskne — musí obsahovat Ctrl, Alt nebo ⌘, Esc nahrávání zruší — a od té chvíle otevře kartu kdekoli ve vaší aplikaci. Pamatuje si ji ten prohlížeč.
  4. Po odeslání karta poděkuje a nabídne Zobrazit hlášení, pokud vaše instalace provozuje portál pro nahlašující. Když se odeslání nepovede, žádné slovo se neztratí; Zkusit znovu pokračuje v tomtéž hlášení, nepošle druhé.

Nahlašující, kterého vaše aplikace identifikovala, si může zaškrtnout i Pošlete mi e-mail, až to bude opravené.

Instalace

Komponenta je hlásicí podoba balíčku @vitrinka/web. Každá instalace vitrinky ji servíruje jako jeden soubor, takže stačí script tag:

<script type="module" src="https://vitrinka.example.com/vendor/report-widget-1.mjs"></script>
<vitrinka-report key="vkb_…" locale="cs"></vitrinka-report>

Soubor servíruje bez cache ta instalace, která hlášení přijímá (na hostované službě https://app.vitrinka.ai/vendor/report-widget-1.mjs), takže se to, co widget zaznamenává, nikdy nerozejde s tím, co server očekává. base má výchozí hodnotu origin, ze kterého skript přišel. Soubor je ES modul: při načtení definuje element, pro stránku, která ho chce ovládat skriptem, exportuje mountReport a identify, a rrweb načítá ze stejné instalace jen tehdy, když projekt stránku nahrává.

V aplikaci s buildem nainstalujte balíček a připojte ho. rrweb, peer závislost, se načte jen tehdy, když projekt stránku nahrává:

bun add @vitrinka/web rrweb
import { mountReport } from "@vitrinka/web/report";

const report = mountReport({ key: "vkb_…", base: "https://vitrinka.example.com", locale: "cs" });
report.open();

V Reactu ho připojte jednou a předávejte mu jazyk aplikace a token nahlašujícího, jak se mění. Přepnutí jazyka kartu překreslí, aniž by se ztratil rozepsaný text nebo značky:

import { useEffect, useRef } from "react";
import { mountReport, type ReportHandle } from "@vitrinka/web/report";

export function ReportAProblem({ locale, token }: { locale: string; token?: string }) {
  const report = useRef<ReportHandle | null>(null);
  useEffect(() => {
    report.current = mountReport({ key: "vkb_…", base: "https://vitrinka.example.com" });
    return () => report.current?.unmount();
  }, []);
  useEffect(() => report.current?.setLocale(locale), [locale]);
  useEffect(() => report.current?.identify(token ? { token } : null), [token]);
  return null;
}

Stránka běží s jedním záznamem. Je-li připojená pilulka rekordéru z @vitrinka/web, záznam patří jí a Nahlásit problém pak posílá hlášení bez něj — na stránku připojte jen jednu podobu, vybranou podle režimu vaší aplikace.

Volby

VolbaAtributCo dělá
keykeyKlíč vkb_ pro hlášení z daného projektu. Povinný.
basebaseOrigin vitrinky, kam hlášení odcházejí; u script tagu je výchozí ten, odkud byl skript servírovaný.
buttonbuttonfalse skryje plovoucí tlačítko, takže kartu otevírá jen váš vlastní ovládací prvek.
openSelectoropen-selectorCSS selektor vašich vlastních prvků, které kartu otevírají.
localelocalecs nebo en. Bez nastavení jazyk prohlížeče, a není-li ani jedním z nich, angličtina. Za běhu ho změníte přes setLocale() nebo atribut.
capture—Zúží profil záznamu projektu — níže.
identity—Kdo hlásí, stejně jako identify() — níže.

Handle i element nabízejí open(), close(), identify() a setLocale(); handle má navíc unmount().

Klíč pro hlášení

Klíč vydáte v Nastavení → Workspace → Tokens jako klíč pro hlášení z jednoho projektu, spolu s originy, ze kterých smí hlášení přicházet: prázdný seznam nepustí žádný prohlížeč, * pustí jakýkoli. Klíč je veřejný. Načte veřejnou konfiguraci projektu a posílá hlášení do toho jednoho projektu; žádné hlášení nikdy nepřečte zpátky. Počet požadavků je omezený pro každý klíč a odesílaná data procházejí maskováním citlivých údajů.

Kdo hlásí

Vaše aplikace widgetu řekne, kdo hlásí, jedním ze tří způsobů — a to rozhodne, jestli se hlášení k týmu dostane hned.

Aplikace předáCo se stane
Token podepsaný vaším serveremNahlašující je ten, koho token uvádí. Hlášení jde rovnou do Inboxu týmu a nahlašující ho sleduje na portálu.
E-mail bez podpisuHlášení se váže k té adrese a čeká na admina. Pro tu adresu není nic čitelné, dokud se její majitel nepřihlásí na portál.
NicAnonymně: hlášení čeká na admina.
report.identify({ token });
report.identify({ email, name });
report.identify(null);

Identita žije jen v paměti, proto ji předávejte při každém načtení stránky. Funkce identify exportovaná z @vitrinka/web/report určí nahlašujícího pro všechny widgety na stránce; vlastní identify() konkrétního widgetu má přednost.

Podpisový klíč hostitele

Admin projektu ho vydá v Nastavení → Projects na záložce projektu Report a problem v části Host signing key. Ukáže se jen jednou, začíná vhk_ a patří mezi serverová tajemství vaší aplikace — nikdy do prohlížeče. Nové vydání ho nahradí a starý klíč okamžitě přestane platit. revoke zastaví všechna přihlášení podepsaná hostitelem; dokud nevydáte nový klíč, hlášení z vaší aplikace čekají na kontrolu. Nahrazení i zrušení klíče zároveň odhlásí z portálu každého nahlašujícího, kterého klíč přihlásil — klíč se mění proto, že mohl uniknout. Nahlašující přihlášení kódem nebo odkazem z e-mailu zůstávají přihlášení. Vydání i zrušení se auditují jako report.host_key i s počtem ukončených relací portálu.

Podpis tokenu nahlašujícího

Token je kompaktní JWS podepsaný HS256 nad těmito údaji:

ÚdajHodnota
subIdentifikátor uživatele ve vaší aplikaci.
emailJeho adresa, jedna prostá schránka.
nameNepovinné: jak ho uvidí tým a portál.
iat, expSekundy od epochy; exp nejvýš 24 hodin po iat. Vydávejte jeden na každou relaci stránky.
aud<workspace>/<project> — workspace a projekt, ke kterým klíč patří; ukáže se při jeho vydání.

Tajemstvím pro HMAC je celý klíč včetně vhk_. Token, který neprojde ověřením, hlášení nikdy nezahodí: hlášení počká na kontrolu a odpověď na první volání widgetu nese identityError, které řekne, co bylo špatně — audience, platnost, podpis jiným klíčem.

V Node:

import { createHmac } from "node:crypto";

const b64 = (v: string | Buffer) => Buffer.from(v).toString("base64url");

export function reporterToken(user: { id: string; email: string; name?: string }): string {
  const now = Math.floor(Date.now() / 1000);
  const head = b64(JSON.stringify({ alg: "HS256", typ: "JWT" }));
  const body = b64(JSON.stringify({ sub: user.id, email: user.email, name: user.name, iat: now, exp: now + 3600, aud: "acme/shop" }));
  const sig = b64(createHmac("sha256", process.env.VITRINKA_REPORT_HOST_KEY!).update(`${head}.${body}`).digest());
  return `${head}.${body}.${sig}`;
}

V Go:

package reporter

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"time"
)

// Token signs a reporter's identity with the project's host signing key.
func Token(key, sub, email, name string) (string, error) {
	now := time.Now().Unix()
	claims, err := json.Marshal(map[string]any{
		"sub": sub, "email": email, "name": name,
		"iat": now, "exp": now + 3600, "aud": "acme/shop",
	})
	if err != nil {
		return "", err
	}
	enc := base64.RawURLEncoding
	body := enc.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + "." + enc.EncodeToString(claims)
	mac := hmac.New(sha256.New, []byte(key))
	mac.Write([]byte(body))
	return body + "." + enc.EncodeToString(mac.Sum(nil)), nil
}

Stejný token nahlašujícího přihlásí i na portál — přihlášení.

Profil záznamu

Co widget uchovává před nahlášením, určuje profil záznamu projektu. Admin projektu ho nastaví na téže záložce Report a problem a widget si ho při načtení přečte se zbytkem konfigurace z GET /api/v1/report-config.

NastaveníVýchozíCo určuje
Úložištěúložiště prohlížečebrowser zapisuje maskovaný záznam i do IndexedDB prohlížeče, takže minuty před nahlášením přežijí načítání stránek mezitím; memory ho drží jen v otevřené kartě.
Okno300 sekundJak daleko do minulosti záznam sahá: 10 sekund až 30 minut.
Velikost12 MiBStrop záznamu, 1 až 64 MiB. Za ním odpadá nejstarší část a záznam se označí jako zkrácený.
Třídyinterakce, konzole, síť, chybyinteractions — maskovaný záznam stránky; errors — nezachycené chyby, console.error a selhané požadavky; console — všechny zprávy konzole; network — všechny požadavky; full — všechny čtyři.

Vaše aplikace může profil jen zúžit — paměť místo úložiště prohlížeče, kratší okno, nižší strop, méně tříd — nikdy ho nerozšíří:

mountReport({ key: "vkb_…", capture: { storage: "memory", windowSec: 120 } });
  • Maskuje se před uložením. Prohlížeč drží přesně to, co by se odeslalo, zamaskované podle profilu maskování projektu, který se nastavuje v hlavičce jeho panelu Reports: strict (výchozí) maskuje všechen text i každou hodnotu vstupu, balanced hodnoty vstupů, full nic. Značka skrytí zakryje svou oblast v každém snímku dřív, než cokoli opustí prohlížeč.
  • Uložený záznam vyprší. Uložená část z prohlížeče zmizí, když vypadne z okna, když se odešle hlášení z dané karty, a při nejbližším načtení stránky po uplynutí své doby. Přepnutí projektu na paměť ji smaže.
  • Co nahlašující píše, zůstává v paměti — popis ani poznámky se nikdy neukládají.
  • Z požadavků jen metadata — metoda, cesta, stav a doba trvání, nikdy hlavička ani tělo.

Co se stane potom

  • Založeno hned. Hlášení, za které vaše aplikace ručí, nebo hlášení napsané na portálu, se okamžitě stane úkolem: bugem, který čeká v projektu, nahoře v Inboxu týmu. Na pozadí se z jeho záznamu stane session s přehráním, na nástěnku chyb projektu přibude karta a každá značka kromě skrytí se stane anotací na obrazovce, kterou nahlašující označil, propojenou s úkolem. Se zapnutou Eve dorazí už roztříděné — třídění Eve.
  • Drženo ke kontrole. Anonymní hlášení, nebo hlášení s e-mailem, za který nikdo neručí, jde do karantény. Jeho záznam je odložený a dostanou se k němu jen admini workspace, takže nic z něj se nedostane na nástěnku, do vyhledávání ani k AI dřív, než se na něj podívá člověk.

Karanténa a kontrola

Admini workspace kontrolují zadržená hlášení v modulu QA pod Bug reports.

AkceKdoCo se stane
Prohlédnoutadmin workspaceCo bylo napsáno, značky, konzole, požadavky a přehrání.
Upravit citlivý obsahadmin workspaceOdstranit vybrané záznamy nebo přehrání před uvolněním.
Schválitadmin workspaceZe záznamu vznikne session, hlášení přistane na nástěnce chyb projektu a do Inboxu dorazí jako úkol — stejnou cestou jako hlášení, za které aplikace ručí.
Zamítnoutadmin workspaceHlášení se uzavře a nic se neuvolní.

Se zapnutým přepínačem auto-ticket projektu otevře schválení i tiket přes konektor projektu.

Na této stránce