Zbystřete své smysly technickými doplňky

HTML 5 NotificationsNevím jak vám, ale nám se při vývoji často stává, že vývojáři některé věci přehlíží a to se nám negativně odráží na produktivitě a kvalitě výstupu. Člověk je tvor omylný, ale inteligentní a proto se snaží se vybavit takovými nástroji, které jeho nedokonalosti dokáží vyvážit. Na posledním hackathonu kolega Michal Kolesnáč přišel s nápadem a prototypem rozšíření našeho existujícího doplňku pro Google Chrome, které pomocí HTML 5 notifikací upozorní vývojáře na potenciální problémy na prohlížené web stránce. Minulý týden jsme řešení dotáhli do konce a myslím, že stojí za to, abych se s Vámi o tento nápad podělil.
Na úvod se podívejte, jak nám výsledné řešení pomáhá v praxi:
[youtube=https://www.youtube.com/watch?v=dluseSIN3tU]

Princip fungování

Princip je relativně obecný a je určitě přenositelný i do vašeho vývojového ekosystému. O vývoji rozšíření pro Chrome toho už bylo napsáno i v češtině docela dost a proto zde nepůjdu do úplných podrobností.
Celý princip je zachycen na následujícím sequence diagramu (btw. vytvořený v http://www.ckwnc.com/ ... což je krásná služba pro generování sequence diagramů - jen nedoplňuje popisky k aktorům):
Sekvenční diagram komunikace
Jednoduše řečeno - každý požadavek na webovou aplikaci prochází servletovým filtrem, který při každém požadavku vygeneruje unikátní token a zapíše do hlaviček odpovědi cookie obsahující URL, na kterém bude v budoucnu odpovídat na požadavky pro zobrazení zpráv spojených s tímto requestem. Po ukončení zpracování HTTP požadavku filtr zanalyzuje aktuální stav aplikace a případně zapíše zprávy pro vývojáře ohledně věcí, kterým by měl věnovat pozornost.
Chrome plugin monitoruje změny cookies a pokud narazí na změnu v cookie se sledovaným názvem, vybere z ní URL, vytvoří XmlHttpRequest a AJAXem se dotáže serveru na seznam zpráv k zobrazení. Požadavek zachytí opět náš servletový filtr, podle unikátního tokenu si ze session vytáhne dříve vygenerovaný seznam zpráv. Nakonec vytvoří JSON zprávu s odpovědí, která obsahuje buď prázdné pole, nebo seznam notifikací s dodatečnými informacemi (např. důležitostí sdělení). JSON je pluginem rozparsován a uživateli jsou prezentovány zprávy jako HTML 5 notifikace. Je důležité si uvědomit, že najednou mohou být zobrazeny pouze 3 notifikace (omezení prohlížeče) a proto je potřeba ty zprávy koncipovat spíše jako odkazy někam dál. V našem případě otvírám po kliknutí na notifikaci RamJet Inspektor, kde je k nalezení už konkrétní rozpad problému.
Cílem rozhodně není zahltit vývojáře informacemi - notifikace mají zobrazovat jen informace o důležitých problémech, které vyžadují pozornost a je riziko, že by je vývojář mohl přehlížet. Naopak pokud by je chtěl přehlížet, tak by mu měly notifikace jeho ignoranci alespoň znepříjemnit :).
V našem případě aktuálně monitorujeme tyto problémy:
  • pomalá odezva stránky (více jak 1 vteřina na vrácení kompletního výstupu)
  • pomalé SQL dotazy při zpracování požadavku (více jak 200ms na zpracování SQL příkazu)
  • duplicitní SQL dotazy (špatné použití cachování)
  • chyby při zpracování stránky (jak při akci, tak i v rámci renderingu) - obvykle by měly být vidět samy od sebe, ale někdy se skryjí ve <script> blocích nebo na stránce chybí komponenta pro výpis chybových hlášení
  • chyby při aplikaci změn v konfiguraci (refresh Spring kontextů selhal) - jelikož se jede z poslední známé funkční konfigurace, vývojář často problém s reloadem nepostřehne a marně pátrá proč se aplikace nechová tak, jak by podle poslední konfigurace měla
  • (zvažujeme) použití deprekovaných komponent a funkcí
  • (plánujeme) zobrazení informace o nelokalizovaných textových popiscích na stránce

A také tyto významné informace v životním cyklu aplikace:

  • vypálení události do Spring kontextu (na události navazujeme např. e-mailové notifikace a další observer akce)
  • reload konfigurace (tj. aplikování změn v konfiguraci)

Realizace

Základem každého plugin je soubor manifest.json, kam je nutné doplnit seznam oprávnění, které bude naše rozšíření vyžadovat - v našem případě potřebujeme oprávnění: notifications (11), cookies (9) a také komunikaci s aplikačním serverem na stejné doméně po zabezpečeném i nezabezpečeném protokolu (12 a 13). Také si musíme povolit přístup k seznamu ikon, které budeme chtít v notifikacích zobrazovat (15-19) a připojit i JavaScriptový soubor (6), který bude obsahovat logiku, která nám vše oživí.
<br />
{<br />
    &quot;name&quot;:&quot;Ramjet Inspector&quot;,<br />
    &quot;version&quot;:&quot;1.8&quot;,<br />
    ... další povinné informace ...<br />
    &quot;background&quot;:{<br />
        &quot;scripts&quot;: [&quot;background.js&quot;]<br />
    },<br />
    &quot;permissions&quot;:[<br />
        &quot;cookies&quot;,<br />
        &quot;tabs&quot;,<br />
        &quot;notifications&quot;,<br />
        &quot;http://*/*&quot;,<br />
        &quot;https://*/*&quot;,<br />
    ],<br />
    &quot;web_accessible_resources&quot;:[<br />
        &quot;skin/INFO.png&quot;,<br />
        &quot;skin/WARNING.png&quot;,<br />
        &quot;skin/ERROR.png&quot;<br />
    ]<br />
}

A takhle vypadá obsah JavaScript souboru background.js, který obstarává naslouchání na změny v cookies a následné zobrazování notifikací (soubor jsem zkrátil a upravil do srozumitelnější podoby - originál je podstatně delší):


var openNotifications = 0;
//REGISTRACE LISTENERU NA ZMĚNY V COOKIES
chrome.cookies.onChanged.addListener(function (data) {
    //zajímá nás pouze vytvoření cookie
    if (data.cause == "explicit" && data.removed == false) {
        //s tímto názvem
        if ("RAMJET_COOKIE_NOTIFICATIONS" == data.cookie.name) {
            fetchAndDisplayNotifications(data.cookie);
        }
    }
});
//ZÍSKÁNÍ SEZNAMU NOTIFIKACÍ AJAXEM ZE SERVERU
function fetchAndDisplayNotifications(cookie) {
    var xhr = new XMLHttpRequest();
    var url = getUrlFromCookie(cookie);
    xhr.open("GET", url, true);
    xhr.onload = function () {
        //o výsledek se má zajímat jen když server odpověděl OK
        if (this.status == 200) {
            //v JSONu nám přijde kolekce objektů
            var notifications = JSON.parse(this.responseText);
            for(var i in notifications) {
                showNotification(notifications[i]);
            }
        }
    };
    xhr.send();
}
//ZÍSKÁNÍ URL STRINGU Z COOKIE A ÚPRAVA PROTOKOLU
function getUrlFromCookie(cookie) {
    var url = cookie.value;
    url = url.replace(new RegExp("\"", 'g'), "");
    if("http" != url.substring(0, 4)) {
        var prefix = cookie.secure ? "https://" : "http://";
        url = prefix + cookie.domain + url;
    }
    url = url.replace(new RegExp("\"", 'g'), "");
    return url;
}
//ZOBRAZENÍ NOTIFIKACE
function showNotification(serverNotification) {
    //výchozí callback pouze zruší automatické zmizení notifikace
    var defaultCallback = function () {
        clearTimeout(timeout);
    };
    //notifikaci vytvoříme pouze pokud je místo na obrazovce
    if (openNotifications < 3) {
        var notification = window.webkitNotifications.createNotification(
                'skin/' + serverNotification.severity + '.png',
                'Ramjet inspector',
                serverNotification.text
        );
        notification.show();
        openNotifications++;
        //po 5 vteřinách se notifikace sama zavírá
        var timeout = setTimeout(function () {
            notification.cancel();
            openNotifications--;
        }, 5000);
        //tady řešíme po kliku otevření správného debug okna inspektora
        notification.onclick = defaultCallback
    } else {
        //jinak odložíme zobrazení notifikace o 5 vteřin
        setTimeout(function() { showNotification(notification); }, 5001)
    }
}

A takhle vypadá obsah JSON zprávy, která odchází ze servlet filtru:


[
   {
      "severity": "warning",
      "toolWindow": "debugger",
      "toolWindowTab": "sqlQueries",
      "text": "Při generování stránky se opakují 3 dotazy celkem 5x. Pravděpodobně plýtváš výkonem!"
   },
   {
      "severity": "info",
      "toolWindow": "events",
      "toolWindowTab": null,
      "text": "Při zpracování požadavku došlo k vyvolání události userCreated."
   }
]

A dál?

Především se potřebujeme přesvědčit, že tento způsob informování vývojáře je použitelný - tedy aby ho naopak zbytečně neobtěžoval. I proto jsem vybíral k notifikaci pouze zásadní situace a problémy v souvislosti s vývojem aplikace (i když si nejsem jist třeba u notifikace pomalé odezvy stránky, která nastává často v souvislosti s debugováním). Pokud se ale osvědčí, budeme chtít udělat podobnou funkcionalitu i pro náš Firefox plugin. Firefox od verze 22 totiž již podporuje HTML 5 notifikace. Nicméně vyvíjet pluginy pro Firefox je daleko větší pruda než pro Chrome, takže ten je aktuálně až druhý v pořadí.

Co si o prototypu myslíte? Připadne vám to jako dobrý nápad?