| import {assert} from 'chai'; |
| import sinon from 'sinon'; |
| |
| import ClientLogger from './client-logger.js'; |
| import MonorailTSMon from './monorail-ts-mon.js'; |
| |
| describe('ClientLogger', () => { |
| const startedKey = 'ClientLogger.rutabaga.started'; |
| let c; |
| |
| beforeEach(() => { |
| window.CS_env = { |
| token: 'rutabaga-token', |
| tokenExpiresSec: 1234, |
| app_version: 'rutabaga-version', |
| }; |
| window.chops = {rpc: {PrpcClient: sinon.spy()}}; |
| window.ga = sinon.spy(); |
| MonorailTSMon.prototype.disableAfterNextFlush = sinon.spy(); |
| c = new ClientLogger('rutabaga'); |
| }); |
| |
| afterEach(() => { |
| sessionStorage.clear(); |
| }); |
| |
| describe('constructor', () => { |
| it('assigns this.category', () => { |
| assert.equal(c.category, 'rutabaga'); |
| }); |
| |
| it('gets started events from sessionStorage', () => { |
| const startedEvents = { |
| event1: { |
| time: 12345678, |
| labels: ['label1', 'label2'], |
| }, |
| event2: { |
| time: 87654321, |
| labels: ['label2'], |
| }, |
| }; |
| sessionStorage[startedKey] = JSON.stringify(startedEvents); |
| |
| c = new ClientLogger('rutabaga'); |
| assert.deepEqual(startedEvents, c.startedEvents); |
| }); |
| }); |
| |
| describe('records ts_mon metrics', () => { |
| let issueCreateMetric; |
| let issueUpdateMetric; |
| let autocompleteMetric; |
| let c; |
| |
| beforeEach(() => { |
| window.ga = sinon.spy(); |
| c = new ClientLogger('issues'); |
| issueCreateMetric = c.tsMon._userTimingMetrics[0].metric; |
| issueCreateMetric.add = sinon.spy(); |
| |
| issueUpdateMetric = c.tsMon._userTimingMetrics[1].metric; |
| issueUpdateMetric.add = sinon.spy(); |
| |
| autocompleteMetric = c.tsMon._userTimingMetrics[2].metric; |
| autocompleteMetric.add = sinon.spy(); |
| }); |
| |
| it('bogus', () => { |
| c.logStart('rutabaga'); |
| c.logEnd('rutabaga'); |
| sinon.assert.notCalled(issueCreateMetric.add); |
| sinon.assert.notCalled(issueUpdateMetric.add); |
| sinon.assert.notCalled(autocompleteMetric.add); |
| }); |
| |
| it('new-issue', () => { |
| c.logStart('new-issue', 'server-time'); |
| c.logEnd('new-issue', 'server-time'); |
| sinon.assert.notCalled(issueUpdateMetric.add); |
| sinon.assert.notCalled(autocompleteMetric.add); |
| |
| sinon.assert.calledOnce(issueCreateMetric.add); |
| assert.isNumber(issueCreateMetric.add.getCall(0).args[0]); |
| assert.isString(issueCreateMetric.add.getCall(0).args[1].get('client_id')); |
| assert.equal(issueCreateMetric.add.getCall(0).args[1].get('host_name'), |
| 'rutabaga-version'); |
| }); |
| |
| it('issue-update', () => { |
| c.logStart('issue-update', 'computer-time'); |
| c.logEnd('issue-update', 'computer-time'); |
| sinon.assert.notCalled(issueCreateMetric.add); |
| sinon.assert.notCalled(autocompleteMetric.add); |
| |
| sinon.assert.calledOnce(issueUpdateMetric.add); |
| assert.isNumber(issueUpdateMetric.add.getCall(0).args[0]); |
| assert.isString(issueUpdateMetric.add.getCall(0).args[1].get('client_id')); |
| assert.equal(issueUpdateMetric.add.getCall(0).args[1].get('host_name'), |
| 'rutabaga-version'); |
| }); |
| |
| it('populate-options', () => { |
| c.logStart('populate-options'); |
| c.logEnd('populate-options'); |
| sinon.assert.notCalled(issueCreateMetric.add); |
| sinon.assert.notCalled(issueUpdateMetric.add); |
| // Autocomplete is not called in issues category. |
| sinon.assert.notCalled(autocompleteMetric.add); |
| |
| c = new ClientLogger('autocomplete'); |
| autocompleteMetric = c.tsMon._userTimingMetrics[2].metric; |
| autocompleteMetric.add = sinon.spy(); |
| |
| c.logStart('populate-options', 'user-time'); |
| c.logEnd('populate-options', 'user-time'); |
| sinon.assert.notCalled(issueCreateMetric.add); |
| sinon.assert.notCalled(issueUpdateMetric.add); |
| |
| sinon.assert.calledOnce(autocompleteMetric.add); |
| assert.isNumber(autocompleteMetric.add.getCall(0).args[0]); |
| assert.isString(autocompleteMetric.add.getCall(0).args[1].get('client_id')); |
| assert.equal(autocompleteMetric.add.getCall(0).args[1].get('host_name'), |
| 'rutabaga-version'); |
| }); |
| }); |
| |
| describe('logStart', () => { |
| let c; |
| let clock; |
| const currentTime = 5000; |
| |
| beforeEach(() => { |
| c = new ClientLogger('rutabaga'); |
| clock = sinon.useFakeTimers(currentTime); |
| }); |
| |
| afterEach(() => { |
| clock.restore(); |
| sessionStorage.clear(); |
| }); |
| |
| it('creates a new startedEvent if none', () => { |
| c.logStart('event-name', 'event-label'); |
| |
| sinon.assert.calledOnce(ga); |
| sinon.assert.calledWith(ga, 'send', 'event', 'rutabaga', |
| 'event-name-start', 'event-label'); |
| |
| const expectedStartedEvents = { |
| 'event-name': { |
| time: currentTime, |
| labels: { |
| 'event-label': currentTime, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), |
| expectedStartedEvents); |
| }); |
| |
| it('uses an existing startedEvent', () => { |
| c.startedEvents['event-name'] = { |
| time: 1234, |
| labels: { |
| 'event-label': 1000, |
| }, |
| }; |
| c.logStart('event-name', 'event-label'); |
| |
| sinon.assert.calledOnce(ga); |
| sinon.assert.calledWith(ga, 'send', 'event', 'rutabaga', |
| 'event-name-start', 'event-label'); |
| |
| // TODO(jeffcarp): Audit is this wanted behavior? Replacing event time |
| // but not label time? |
| const expectedStartedEvents = { |
| 'event-name': { |
| time: 1234, |
| labels: { |
| 'event-label': currentTime, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), |
| expectedStartedEvents); |
| }); |
| }); |
| |
| describe('logPause', () => { |
| const startTime = 1234; |
| const currentTime = 5000; |
| let c; |
| let clock; |
| |
| beforeEach(() => { |
| clock = sinon.useFakeTimers(currentTime); |
| c = new ClientLogger('rutabaga'); |
| c.startedEvents['event-name'] = { |
| time: startTime, |
| labels: { |
| 'event-label': startTime, |
| }, |
| }; |
| }); |
| |
| afterEach(() => { |
| clock.restore(); |
| sessionStorage.clear(); |
| }); |
| |
| it('throws if no label given', () => { |
| assert.throws(() => { |
| c.logPause('bogus'); |
| }, 'event with no label'); |
| }); |
| |
| it('exits early if no start event exists', () => { |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logPause('bogus', 'fogus'); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('exits early if no label exists', () => { |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logPause('event-name', 'fogus'); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('adds elapsed time to start event', () => { |
| c.logPause('event-name', 'event-label'); |
| |
| const expectedStartedEvents = { |
| 'event-name': { |
| time: startTime, |
| labels: { |
| 'event-label': startTime, |
| }, |
| elapsed: { |
| 'event-label': currentTime - startTime, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual( |
| JSON.parse(sessionStorage['ClientLogger.rutabaga.started']), |
| expectedStartedEvents); |
| }); |
| }); |
| |
| describe('logResume', () => { |
| let c; |
| let clock; |
| const startTimeEvent = 1234; |
| const startTimeLabel = 2345; |
| const labelElapsed = 4321; |
| const currentTime = 6000; |
| |
| beforeEach(() => { |
| clock = sinon.useFakeTimers(currentTime); |
| c = new ClientLogger('rutabaga'); |
| c.startedEvents['event-name'] = { |
| time: startTimeEvent, |
| labels: { |
| 'event-label': startTimeLabel, |
| }, |
| elapsed: { |
| 'event-label': labelElapsed, |
| }, |
| }; |
| }); |
| |
| afterEach(() => { |
| clock.restore(); |
| sessionStorage.clear(); |
| }); |
| |
| it('throws if no label given', () => { |
| assert.throws(() => { |
| c.logResume('bogus'); |
| }, 'no label'); |
| }); |
| |
| it('exits early if no start event exists', () => { |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logResume('bogus', 'fogus'); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('exits early if the label was never paused', () => { |
| c.startedEvents['event-name'] = { |
| time: startTimeEvent, |
| labels: { |
| 'event-label': startTimeLabel, |
| }, |
| elapsed: {}, |
| }; |
| |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logResume('event-name', 'event-label'); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('sets start event time to current time', () => { |
| c.logResume('event-name', 'event-label'); |
| |
| const expectedStartedEvents = { |
| 'event-name': { |
| time: startTimeEvent, |
| labels: { |
| 'event-label': currentTime, |
| }, |
| elapsed: { |
| 'event-label': labelElapsed, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual( |
| JSON.parse(sessionStorage['ClientLogger.rutabaga.started']), |
| expectedStartedEvents); |
| }); |
| }); |
| |
| describe('logEnd', () => { |
| let c; |
| let clock; |
| const startTimeEvent = 1234; |
| const startTimeLabel1 = 2345; |
| const startTimeLabel2 = 3456; |
| const currentTime = 10000; |
| |
| beforeEach(() => { |
| c = new ClientLogger('rutabaga'); |
| clock = sinon.useFakeTimers(currentTime); |
| c.tsMon.recordUserTiming = sinon.spy(); |
| c.startedEvents = { |
| someEvent: { |
| time: startTimeEvent, |
| labels: { |
| label1: startTimeLabel1, |
| label2: startTimeLabel2, |
| }, |
| }, |
| }; |
| }); |
| |
| afterEach(() => { |
| clock.restore(); |
| }); |
| |
| it('returns early if no event was started', () => { |
| c.startedEvents = {someEvent: {}}; |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logEnd('bogus'); |
| sinon.assert.notCalled(window.ga); |
| assert.isNull(sessionStorage.getItem(startedKey)); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('returns early if label was not started', () => { |
| c.startedEvents = {someEvent: {labels: {}}}; |
| const originalStartedEvents = Object.assign(c.startedEvents, {}); |
| c.logEnd('someEvent', 'bogus'); |
| sinon.assert.notCalled(window.ga); |
| assert.isNull(sessionStorage.getItem(startedKey)); |
| assert.deepEqual(c.startedEvents, originalStartedEvents); |
| }); |
| |
| it('does not log non-labeled events over threshold', () => { |
| c.startedEvents = {someEvent: {time: currentTime - 1000}}; |
| c.logEnd('someEvent', null, 999); |
| |
| sinon.assert.calledOnce(window.ga); |
| sinon.assert.calledWith(window.ga, 'send', 'event', 'rutabaga', |
| 'someEvent-end', null, undefined); |
| sinon.assert.notCalled(c.tsMon.recordUserTiming); |
| assert.equal(sessionStorage.getItem(startedKey), '{}'); |
| }); |
| |
| it('does not log labeled events over threshold', () => { |
| const elapsedLabel2 = 2000; |
| c.startedEvents.someEvent.elapsed = { |
| label1: currentTime - 1000, |
| label2: elapsedLabel2, |
| }; |
| c.logEnd('someEvent', 'label1', 999); |
| |
| sinon.assert.calledOnce(window.ga); |
| sinon.assert.calledWith(window.ga, 'send', 'event', 'rutabaga', |
| 'someEvent-end', 'label1', undefined); |
| // TODO(jeffcarp): Feature: add GA event if over threshold. |
| sinon.assert.notCalled(c.tsMon.recordUserTiming); |
| |
| const expectedStartedEvents = { |
| someEvent: { |
| time: startTimeEvent, |
| labels: { |
| label2: startTimeLabel2, |
| }, |
| elapsed: { |
| label2: elapsedLabel2, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), |
| expectedStartedEvents); |
| }); |
| |
| it('calls ga() with timing and event info for all labels', () => { |
| const label1Elapsed = 1000; |
| const label2Elapsed = 2500; |
| c.startedEvents.someEvent.elapsed = { |
| label1: label1Elapsed, |
| label2: label2Elapsed, |
| }; |
| c.logEnd('someEvent'); |
| |
| assert.deepEqual(ga.getCall(0).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: currentTime - startTimeEvent, |
| timingVar: 'someEvent', |
| }]); |
| |
| assert.deepEqual(ga.getCall(1).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: (currentTime - startTimeLabel1) + label1Elapsed, |
| timingVar: 'someEvent', |
| timingLabel: 'label1', |
| }]); |
| assert.deepEqual(ga.getCall(2).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: (currentTime - startTimeLabel2) + label2Elapsed, |
| timingVar: 'someEvent', |
| timingLabel: 'label2', |
| }]); |
| assert.deepEqual(ga.getCall(3).args, [ |
| 'send', 'event', 'rutabaga', 'someEvent-end', undefined, undefined, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(0).args, [ |
| 'rutabaga', 'someEvent', null, currentTime - startTimeEvent, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(1).args, [ |
| 'rutabaga', 'someEvent', 'label1', |
| (currentTime - startTimeLabel1) + label1Elapsed, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(2).args, [ |
| 'rutabaga', 'someEvent', 'label2', |
| (currentTime - startTimeLabel2) + label2Elapsed, |
| ]); |
| |
| assert.deepEqual(c.startedEvents, {}); |
| assert.equal(sessionStorage.getItem(startedKey), '{}'); |
| }); |
| |
| it('calling with a label calls ga() only for that label', () => { |
| const label1Elapsed = 1000; |
| const label2Elapsed = 2500; |
| c.startedEvents.someEvent.elapsed = { |
| label1: label1Elapsed, |
| label2: label2Elapsed, |
| }; |
| c.logEnd('someEvent', 'label2'); |
| |
| assert.deepEqual(ga.getCall(0).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: (currentTime - startTimeLabel2) + label2Elapsed, |
| timingVar: 'someEvent', |
| timingLabel: 'label2', |
| }]); |
| assert.deepEqual(window.ga.getCall(1).args, [ |
| 'send', 'event', 'rutabaga', 'someEvent-end', 'label2', undefined, |
| ]); |
| sinon.assert.calledOnce(c.tsMon.recordUserTiming); |
| sinon.assert.calledWith(c.tsMon.recordUserTiming, 'rutabaga', |
| 'someEvent', 'label2', (currentTime - startTimeLabel2) + label2Elapsed); |
| |
| const expectedStartedEvents = { |
| someEvent: { |
| time: startTimeEvent, |
| labels: { |
| label1: startTimeLabel1, |
| }, |
| elapsed: { |
| label1: label1Elapsed, |
| }, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), |
| expectedStartedEvents); |
| }); |
| |
| it('calling logStart, logPause, logResume, and logEnd works for labels', |
| () => { |
| let countedElapsedTime = 0; |
| c.logStart('someEvent', 'label1'); |
| clock.tick(1000); |
| countedElapsedTime += 1000; |
| c.logPause('someEvent', 'label1'); |
| clock.tick(1000); |
| c.logResume('someEvent', 'label1'); |
| clock.tick(1000); |
| countedElapsedTime += 1000; |
| c.logEnd('someEvent', 'label1'); |
| |
| assert.deepEqual(ga.getCall(0).args, [ |
| 'send', 'event', 'rutabaga', 'someEvent-start', 'label1', undefined, |
| ]); |
| assert.deepEqual(ga.getCall(1).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: countedElapsedTime, |
| timingVar: 'someEvent', |
| timingLabel: 'label1', |
| }]); |
| assert.deepEqual(window.ga.getCall(2).args, [ |
| 'send', 'event', 'rutabaga', 'someEvent-end', 'label1', undefined, |
| ]); |
| sinon.assert.calledOnce(c.tsMon.recordUserTiming); |
| sinon.assert.calledWith(c.tsMon.recordUserTiming, 'rutabaga', |
| 'someEvent', 'label1', countedElapsedTime); |
| |
| const expectedStartedEvents = { |
| someEvent: { |
| time: startTimeEvent, |
| labels: { |
| label2: startTimeLabel2, |
| }, |
| elapsed: {}, |
| }, |
| }; |
| assert.deepEqual(c.startedEvents, expectedStartedEvents); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), |
| expectedStartedEvents); |
| }); |
| |
| it('logs some events when others are above threshold', () => { |
| c.startedEvents = { |
| someEvent: { |
| time: 9500, |
| labels: { |
| overThresholdWithoutElapsed: 8000, |
| overThresholdWithElapsed: 9500, |
| underThresholdWithoutElapsed: 9750, |
| underThresholdWithElapsed: 9650, |
| exactlyOnThresholdWithoutElapsed: 9001, |
| exactlyOnThresholdWithElapsed: 9002, |
| }, |
| elapsed: { |
| overThresholdWithElapsed: 1000, |
| underThresholdWithElapsed: 100, |
| exactlyOnThresholdWithElapsed: 1, |
| }, |
| }, |
| }; |
| c.logEnd('someEvent', null, 999); |
| |
| // Verify ga() calls. |
| assert.equal(window.ga.getCalls().length, 6); |
| assert.deepEqual(ga.getCall(0).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: 500, |
| timingVar: 'someEvent', |
| }]); |
| assert.deepEqual(ga.getCall(1).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: 250, |
| timingVar: 'someEvent', |
| timingLabel: 'underThresholdWithoutElapsed', |
| }]); |
| assert.deepEqual(ga.getCall(2).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: 450, |
| timingVar: 'someEvent', |
| timingLabel: 'underThresholdWithElapsed', |
| }]); |
| assert.deepEqual(ga.getCall(3).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: 999, |
| timingVar: 'someEvent', |
| timingLabel: 'exactlyOnThresholdWithoutElapsed', |
| }]); |
| assert.deepEqual(ga.getCall(4).args, [ |
| 'send', 'timing', { |
| timingCategory: 'rutabaga', |
| timingValue: 999, |
| timingVar: 'someEvent', |
| timingLabel: 'exactlyOnThresholdWithElapsed', |
| }]); |
| assert.deepEqual(ga.getCall(5).args, [ |
| 'send', 'event', 'rutabaga', 'someEvent-end', null, undefined, |
| ]); |
| |
| // Verify ts_mon.recordUserTiming() calls. |
| assert.equal(c.tsMon.recordUserTiming.getCalls().length, 5); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(0).args, [ |
| 'rutabaga', 'someEvent', null, 500, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(1).args, [ |
| 'rutabaga', 'someEvent', 'underThresholdWithoutElapsed', 250, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(2).args, [ |
| 'rutabaga', 'someEvent', 'underThresholdWithElapsed', 450, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(3).args, [ |
| 'rutabaga', 'someEvent', 'exactlyOnThresholdWithoutElapsed', 999, |
| ]); |
| assert.deepEqual(c.tsMon.recordUserTiming.getCall(4).args, [ |
| 'rutabaga', 'someEvent', 'exactlyOnThresholdWithElapsed', 999, |
| ]); |
| assert.deepEqual(c.startedEvents, {}); |
| assert.deepEqual(JSON.parse(sessionStorage[startedKey]), {}); |
| }); |
| }); |
| }); |