/* Comparison of two finished runs: renders the comparison embedded as JSON in #analytics-data (the groups of addresses, the dynamics of the seven indicators, the verdict transitions) and opens the address list behind each number in the shared dialog (analytics-dialog.js). The state of the page is its address: ?base=A&target=B. */ (function () { 'use strict'; var D = JSON.parse(document.getElementById('analytics-data').textContent); var R = D.report, M = D.meta, G = R.groups, T = R.transitions; var A = window.AnalyticsDialog, $ = A.$, fmt = A.fmt, esc = A.esc, tipAttr = A.tipAttr; function pct(a, b) { return b ? Math.round(a / b * 100) : 0; } var NAME = {}; R.indicators.forEach(function (d) { NAME[d.key] = d.name; }); /* ---- what each list says about itself ---- */ var GROUP = { new: ['Новые адреса', 'Есть в запуске B, в A их не было: прибыли в проект и подключились к анализу. Столбцы показывают состояние в B.'], left: ['Выбывшие адреса', 'Были в запуске A, в B их нет: вышли из состава проекта. Столбцы показывают состояние в A.'], common: ['Общие адреса', 'Есть в обоих запусках. Столбцы показывают состояние в A → B.'], changed: ['Изменившиеся адреса', 'Есть в обоих запусках, принадлежность хотя бы к одному из семи индикаторов в A и в B разная.'], same: ['Адреса без изменений', 'Есть в обоих запусках, принадлежность ко всем индикаторам та же. Другой набор проваленных целей или площадок изменением не считается.'], entered: ['Вошли в индикатор', 'Общие адреса, которые в A не входили в этот индикатор, а в B входят.'], exited: ['Вышли из индикатора', 'Общие адреса, которые в A входили в этот индикатор, а в B не входят.'] }; var HINT_ONE = { 2: 'Вердикт адреса, который система выставила при агрегации, а не итог всех проверок.', 3: 'Сколько egress-проверок адреса успешно из всех записанных в последнем цикле запуска. «—» — egress-проверок нет.', 4: 'Сколько ingress-проверок адреса успешно из всех записанных в последнем цикле запуска. «—» — ingress-проверок нет.', 5: 'Индикаторы страницы «Аналитика», в которые входит адрес. Один адрес может входить в несколько; вердикт — ровно в один из трёх.' }; var HINT_PAIR = { 2: 'Вердикт адреса в запуске A и в запуске B.', 3: 'Сколько egress-проверок адреса успешно из всех записанных в последнем цикле: в A → в B.', 4: 'Сколько ingress-проверок адреса успешно из всех записанных в последнем цикле: в A → в B.', 5: 'Что изменилось между запусками, по шагам: вердикт, вход в индикатор и выход из него, проваленные цели https и площадки ssh, валидатор. У адреса без изменений в индикаторах — «без изменений», даже если набор проваленных целей другой.' }; function describe(f) { var g = GROUP[f.group], ind = f.ind ? '«' + (NAME[f.ind] || f.ind) + '»' : ''; var title = g[0] + (f.ind ? ': ' + (NAME[f.ind] || f.ind) : '') + (f.from ? ' · вердикт ' + f.from + ' → ' + f.to : ''); var note = g[1]; if (f.ind && f.group === 'new') note += ' Только входящие в индикатор ' + ind + ' в запуске B.'; if (f.ind && f.group === 'left') note += ' Только входившие в индикатор ' + ind + ' в запуске A.'; if (f.ind && (f.group === 'common' || f.group === 'changed' || f.group === 'same')) note += ' Только входящие в индикатор ' + ind + ' хотя бы в одном из запусков.'; if (f.from) note += ' Только адреса с вердиктом ' + f.from + ' в A и ' + f.to + ' в B.'; return { title: title, note: note, hints: f.group === 'new' || f.group === 'left' ? HINT_ONE : HINT_PAIR }; } function listURL(root, f) { return root + encodeURIComponent(f.group) + '?base=' + M.base.id + '&target=' + M.target.id + (f.ind ? '&indicator=' + encodeURIComponent(f.ind) : '') + (f.from ? '&from=' + encodeURIComponent(f.from) + '&to=' + encodeURIComponent(f.to) : ''); } function openList(f) { var t = describe(f); A.load(listURL(M.list_url, f), t.title).then(function (l) { if (!l) return; A.fill({ title: t.title, note: t.note, hints: t.hints, scope: 'Запуски', runLabel: 'A — ' + M.base.label + '; B — ' + M.target.label, cols: l.columns, rows: l.rows, csvURL: listURL(M.csv_url, f) }); }); } /* ---- the page ---- */ function renderNote() { function line(name, m, r, cancelled) { return '
' + esc(name + ': запуск ' + m.id + ', ' + m.kind + ', начало ' + m.start + ', завершён ' + m.end + ', длительность ' + m.duration + ', ' + fmt(r.addresses) + ' адр.' + (r.rechecked ? ', перепроверено внутри запуска: ' + fmt(r.rechecked) : '') + (cancelled ? ', отменено (не участвуют в сравнении): ' + fmt(cancelled) : '') + '.') + '
'; } $('an-cmp-note').innerHTML = line('A', M.base, R.runs.base, R.cancelled.base) + line('B', M.target, R.runs.target, R.cancelled.target); } function renderKpis() { var GO = ' список →'; var tiles = [ ['Новые', G.new, 'есть в B, не было в A', 'new'], ['Выбывшие', G.left, 'были в A, нет в B', 'left'], ['Общие', G.common, 'есть в обоих запусках', 'common'], ['Изменились', G.changed, pct(G.changed, G.common) + '% общих адресов', 'changed'], ['Без изменений', G.same, pct(G.same, G.common) + '% общих адресов', 'same'] ]; $('an-cmp-kpis').innerHTML = tiles.map(function (t) { var inner = '
' + t[0] + '
' + fmt(t[1]) + '
' + t[2] + (t[1] ? GO : '') + '
'; return t[1] ? '' : '
' + inner + '
'; }).join(''); } /* a number that opens a list; zero is plain text */ function link(n, attrs, tip) { return n ? '' : '0'; } function renderIndicators() { var COLS = [['new', 'Новые', 'новые адреса, входящие в индикатор'], ['left', 'Выбывшие', 'выбывшие адреса, входившие в индикатор'], ['entered', 'Вошли', 'общие адреса, вошедшие в индикатор'], ['exited', 'Вышли', 'общие адреса, вышедшие из индикатора']]; $('an-cmp-ind').innerHTML = 'ИндикаторABΔ' + COLS.map(function (c) { return '' + c[1] + ''; }).join('') + '' + R.indicators.map(function (d) { var good = d.key === 'verdict_pass' ? d.delta > 0 : d.delta < 0; var delta = d.delta > 0 ? '+' + fmt(d.delta) : d.delta < 0 ? '−' + fmt(-d.delta) : '0'; return '' + esc(d.name) + '' + fmt(d.base) + '' + fmt(d.target) + '' + '' + delta + '' + COLS.map(function (c) { return '' + link(d[c[0]], 'data-group="' + c[0] + '" data-ind="' + d.key + '"', d.name + ': ' + c[2]) + ''; }).join('') + ''; }).join('') + ''; } function renderTransitions() { var V = T.verdicts; $('an-cmp-tr').innerHTML = '' + V.map(function (v) { return 'B: ' + esc(v) + ''; }).join('') + 'нет в B' + V.map(function (from, i) { return 'A: ' + esc(from) + '' + V.map(function (to, j) { return '' + link(T.matrix[i][j], 'data-group="common" data-from="' + from + '" data-to="' + to + '"', 'вердикт ' + from + ' в A → ' + to + ' в B') + ''; }).join('') + '' + link(T.left[i], 'data-group="left" data-ind="verdict_' + from + '"', 'выбыли, вердикт в A: ' + from) + ''; }).join('') + 'нет в A' + V.map(function (to, j) { return '' + link(T.new[j], 'data-group="new" data-ind="verdict_' + to + '"', 'новые, вердикт в B: ' + to) + ''; }).join('') + '—'; } /* ---- wiring ---- */ function onOpen(e) { var b = e.target.closest('[data-group]'); if (b) openList({ group: b.dataset.group, ind: b.dataset.ind || '', from: b.dataset.from || '', to: b.dataset.to || '' }); } renderNote(); renderKpis(); renderIndicators(); renderTransitions(); ['an-cmp-kpis', 'an-cmp-ind', 'an-cmp-tr'].forEach(function (id) { $(id).addEventListener('click', onOpen); }); })();