diff --git a/src/BeaconInspector.ts b/src/BeaconInspector.ts index c0c51f3..30c580a 100644 --- a/src/BeaconInspector.ts +++ b/src/BeaconInspector.ts @@ -57,7 +57,7 @@ const BeaconInspector = () => { }), m('section.columns.section', [ m('div.column.is-narrow.timeline', - m('div.panel', + m('div.panel.filterPanel', m('input#filter[type=text][placeholder=Filter]', { onkeyup: (e) => { const t = e.currentTarget; @@ -73,15 +73,15 @@ const BeaconInspector = () => { ), requests.map((x) => m(Timeline, { setActive, isActive, filter, request: x })), ), - m('div.column.tile.is-ancestor.is-vertical.inspector', - m(Beacon, { activeBeacon: active })), + m('div#beacon.column', + m('div.tile.is-ancestor.is-vertical.inspector', + m(Beacon, { activeBeacon: active }))), ]), m(BadRowsModal, { addRequests: (pagename, reqs) => requests.push({ page: pagename, entries: reqs }), modal, setModal, }), - m('div.jumper', {onclick: () => scrollTo(0, 0), title: 'Jump to Top'}), ]), }; }; diff --git a/src/Timeline.ts b/src/Timeline.ts index f2741db..fbba0c5 100644 --- a/src/Timeline.ts +++ b/src/Timeline.ts @@ -23,10 +23,10 @@ const filterRequest = (beacon, filter) => { || filter.test(beacon.eventName) || filter.test(beacon.method) || filter.test(beacon.page) - || Array.from(beacon.payload.values()).filter((x) => { + || Array.from(beacon.payload.values()).filter((x: string) => { let decoded; try { - decoded = atob(String(x).replace(/-/g, '+').replace(/_/g, '/')); + decoded = util.b64d(x); } catch (e) { decoded = null; } @@ -36,6 +36,36 @@ const filterRequest = (beacon, filter) => { ; }; +const nameEvent = (params: Map): string => { + const result = protocol.paramMap.e.values[params.get('e')] || params.get('e'); + + switch (result) { + case 'Self-Describing Event': + const payload = params.get('ue_pr') || params.get('ue_px'); + let sdeName = 'Unstructured'; + let sde = null; + + try { + sde = JSON.parse(util.b64d(payload)); + } catch (e) { + sde = JSON.parse(payload); + } finally { + if (typeof sde === 'object' && sde !== null && sde.hasOwnProperty('schema') && sde.hasOwnProperty('data')) { + sdeName = sde.data.schema || 'Unstructured'; + if (sdeName.startsWith('iglu:')) { + sdeName = sdeName.split('/')[1]; + } + } + } + + return 'SD Event: ' + sdeName; + case 'Structured Event': + return result + ': ' + params.get('se_ca'); + default: + return result; + } +}; + const summariseBeacons = (entry, index, filter) => { const reqs = extractRequests(entry, index); const [[id, collector, method], requests] = reqs; @@ -46,7 +76,7 @@ const summariseBeacons = (entry, index, filter) => { const result = { appId: req.get('aid'), collector, - eventName: protocol.paramMap.e.values[req.get('e')] || req.get('e'), + eventName: nameEvent(req), id: `#${id}-${i}`, method, page: req.get('url'), diff --git a/src/panel.html b/src/panel.html index 2722c6f..820973e 100644 --- a/src/panel.html +++ b/src/panel.html @@ -4,14 +4,17 @@ Snowflake Analytics - Snowplow Beacon Analyser diff --git a/src/protocol.ts b/src/protocol.ts index d818039..539ff08 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -2,26 +2,26 @@ export = { groupPriorities: [ - {name: 'Beacon', - fields: ['evn', 'e', 'aid', 'eid', 'ttm', 'dtm', 'stm', 'p', 'u', 'tid', 'tna', 'tv']}, - {name: 'User', - fields: ['duid', 'nuid', 'tnuid', 'uid']}, - {name: 'Session', - fields: ['vid', 'sid']}, - {name: 'Transaction', - fields: ['tr_id', 'tr_af', 'tr_tt', 'tr_tx', 'tr_sh', 'tr_ci', 'tr_st', 'tr_co', 'tr_cu', 'ti_id', 'ti_sk', 'ti_na', 'ti_ca', 'ti_pr', 'ti_qu', 'ti_cu']}, {name: 'Event', fields: ['se_ca', 'se_ac', 'se_la', 'se_pr', 'se_va', 'ue_pr', 'ue_px']}, - {name: 'Page', - fields: ['url', 'page', 'refr', 'ds', 'cs']}, + {name: 'Transaction', + fields: ['tr_id', 'tr_af', 'tr_tt', 'tr_tx', 'tr_sh', 'tr_ci', 'tr_st', 'tr_co', 'tr_cu', 'ti_id', 'ti_sk', 'ti_na', 'ti_ca', 'ti_pr', 'ti_qu', 'ti_cu']}, {name: 'Ad', fields: ['ad_ad', 'ad_ca', 'ad_ba', 'ad_uid']}, {name: 'Social', fields: ['sn', 'sa', 'st', 'sp']}, {name: 'Ping', fields: ['pp_mix', 'pp_max', 'pp_miy', 'pp_may']}, + {name: 'Beacon', + fields: ['evn', 'e', 'aid', 'eid', 'ttm', 'dtm', 'stm', 'p', 'u', 'tid', 'tna', 'tv']}, {name: 'Context', fields: ['cv', 'co', 'cx']}, + {name: 'User', + fields: ['duid', 'nuid', 'tnuid', 'uid']}, + {name: 'Session', + fields: ['vid', 'sid']}, + {name: 'Page', + fields: ['url', 'page', 'refr', 'ds', 'cs']}, {name: 'Browser', fields: ['ua', 'fp', 'cookie', 'lang', 'vp', 'f_pdf', 'f_fla', 'f_java', 'f_ag', 'f_qt', 'f_realp', 'f_wma', 'f_dir', 'f_gears']}, {name: 'Device', diff --git a/src/util.ts b/src/util.ts index 71b93d5..fb86b4a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -const hash = (bytes) => { +const hash = (bytes): string => { let h = 5381; for (let i = 0; i < bytes.length; i++) { @@ -26,7 +26,11 @@ const hasMembers = (obj) => { return false; }; -const nameType = (val) => { +const b64d = (s: string): string => { + return atob(s.replace(/-/g, '+').replace(/_/g, '/')); +}; + +const nameType = (val): string => { if (val === null) { return 'null'; } @@ -51,7 +55,7 @@ const nameType = (val) => { return typeof val; }; -const copyToClipboard = (text) => { +const copyToClipboard = (text: string): void => { let cb = document.getElementById('clipboard') as HTMLInputElement; if (cb === null) { cb = document.createElement('input') as HTMLInputElement; @@ -69,10 +73,10 @@ const copyToClipboard = (text) => { const tryb64 = (text: string): string => { if (typeof text === 'string' && /^([A-Za-z0-9/_+-]{4})+([A-Za-z0-9/_+=-]{1,4})?$/.test(text)) { - return atob(text.replace(/_/g, '/').replace(/-/g, '+')); + return b64d(text); } else { return text; } }; -export = {hash, hasMembers, nameType, copyToClipboard, tryb64}; +export = {b64d, hash, hasMembers, nameType, copyToClipboard, tryb64};