mdeanda / Kibana JSON/Stacktrace Formatter

// ==UserScript==
// @name         Kibana JSON/Stacktrace Formatter
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  try to take over the world!
// @author       Miguel De Anda
// @match        */app/kibana*
// @match        http://localhost:5601/*
// @grant        none
// @license      MIT
// @copyright    2019, mdeanda (https://openuserjs.org/users/mdeanda)
// @updateURL    https://openuserjs.org/meta/mdeanda/Kibana_JSONStacktrace_Formatter.meta.js
// ==/UserScript==

(function() {
    'use strict';

    var jqueryUrl = 'https://code.jquery.com/jquery-2.2.4.min.js';

    var timer = setInterval(function() {
        if (window.jQuery) {
            clearInterval(timer);
            var $ = window.jQuery;

            go($);
        }
    }, 500);


    function go($) {
        var sourceClasses = [".truncate-by-height", ".doc-viewer-value", ".kbnDocViewer__value"];

        var newCss = ".doc-viewer-value { max-height: 30em; overflow: hidden; }";
        var styleTag = document.createElement("style");
        styleTag.textContent = newCss;
        document.body.appendChild(styleTag);

        window.addEventListener('dblclick', function(evt) {
            var target = evt.target;
            var el = $(target);
            for (var i=0; i<sourceClasses.length; i++) {
                var cls = sourceClasses[i];
                if (el.is(cls)) {
                    clicked($, el);
                    break;
                }
                var p = el.parent(cls);
                if (p.length > 0) {
                    clicked($, p);
                    break;
                }
            }

        });
    }
    function clicked($, target) {
        try {
            var parsed = parse(target)
            if (!parsed) {
                parsed = parse($('span', target));
            }
            if (parsed) {
                var stack = buildStackTrace(parsed);
                if (!stack) return;

                var fullHtml = popupHtml(parsed);
                var win = window.open('about:blank', '', 'width=840,height=480');
                var doc = win.document;
                doc.write(fullHtml);
            }
        } catch(e){console.dir(e);}
    }
    function parse(el) {
        var val = el.html();
        try {
            return JSON.parse(val);
        } catch (e) {}
        try {
            return JSON.parse('[' + val + ']');
        } catch(e) {}

        return null;
    }
    function getPackagesForPackage(classname, separator, prefix) {
        var output = [];
        var pkg = [];
        var parts = classname.split('.');
        parts.pop(); //remove classname
        parts.forEach(function(p) {
            pkg.push(p);
            var packageName = prefix + pkg.join(separator);
            output.push(packageName);
        });
        return output;
    }
    function buildStackTrace(jsonArray) {
        //assumes each entry has: class, method, file, line
        var lines = [];
        for (var i=0; i<jsonArray.length; i++) {
            var line = jsonArray[i];
            if (!line['class'] || !line.method || !line.line) {
                return null;
            }

            var packageCss = getPackagesForPackage(line['class'], '_', '_cls_').join(' ');
            var fl = (line.line > 0) ? line.file + ':' + line.line : '';
            var str = '<div class="_cls__base ' + packageCss + '">'
                + '<span class="classname">' + line['class'] + '</span>'
                + '.'
                + '<span class="method">' + line.method + '</span>'
                + '<span class="file">' + '(' + fl + ')' + '</span>'
                + '</div>';
            lines.push(str);
        }
        return lines.join('\n');
    }
    function getPackages(parsed) {
        var map = {};
        parsed.forEach(function(item) {
            var packages = getPackagesForPackage(item['class'], '.', '');
            packages.forEach(function(packageName) {
                if (typeof map[packageName] == 'undefined') map[packageName] = 1;
                else map[packageName] = map[packageName] + 1;
            });
        });
        return map;
    }
    function buildPackageSelector(packages) {
        var output = [];

        output.push('<div>');
        output.push('<select onchange="changed(this.value)" id="select">');
        output.push('<option value="">[ View All ]</option>');
        Object.keys(packages).sort().forEach(function(key) {
            output.push('<option value="_cls_' + key.replace(/\./g, '_') + '">' + key + ' - ' + packages[key] + '</option>');
        })
        output.push('</select>');
        output.push('</div>');

        return output.join('\n');
    }
    function getPopupJs() {
        var o = [];
        o.push('function changed(value) {');
        //o.push('  alert(value);');
        //hide/show mode
        o.push('  if (value == "") value = "_cls__base";');
        o.push('  var items = document.querySelectorAll("._cls__base");');
        o.push('  for (var i=0; i<items.length; i++) {');
        o.push('    items[i].style.display = "none";');
        o.push('  }');
        o.push('  items = document.querySelectorAll("." + value);');
        o.push('  for (var i=0; i<items.length; i++) {');
        o.push('    items[i].style.display = "";');
        o.push('  }');
        o.push('}');
        return o.join('\n');
    }
    function popupHtml(parsed) {
        var output = [];
        output.push('<' + 'style>');
        output.push('.stacktrace { font-family: monospace; font-size: 12px }');
        output.push('.classname { color: #222266; }');
        output.push('.method { color: #226622; }');
        output.push('.file { color: #444444; }');
        output.push('<' + '/style>');
        output.push('<' + 'script>' + getPopupJs() + '</' + 'script>');
        output.push(buildPackageSelector(getPackages(parsed)));
        output.push('<div class="stacktrace">');
        output.push(buildStackTrace(parsed));
        output.push('</div>');

        return output.join('\n');
    }
})();