Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Adding preview end and download attempt metrics #781

Merged
merged 5 commits into from
Apr 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/lib/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,16 @@ import {
X_REP_HINT_VIDEO_MP4,
FILE_OPTION_FILE_VERSION_ID
} from './constants';
import { VIEWER_EVENT, ERROR_CODE, PREVIEW_ERROR, PREVIEW_METRIC, LOAD_METRIC } from './events';
import {
VIEWER_EVENT,
ERROR_CODE,
PREVIEW_ERROR,
PREVIEW_METRIC,
LOAD_METRIC,
DURATION_METRIC,
PREVIEW_END_EVENT,
PREVIEW_DOWNLOAD_ATTEMPT_EVENT
} from './events';
import { getClientLogDetails, getISOTime } from './logUtils';
import './Preview.scss';

Expand Down Expand Up @@ -186,6 +195,25 @@ class Preview extends EventEmitter {

// Destroy viewer
if (this.viewer && typeof this.viewer.destroy === 'function') {
// Log a preview end event
if (this.file && this.file.id) {
const previewDurationTag = Timer.createTag(this.file.id, DURATION_METRIC);
const previewDurationTimer = Timer.get(previewDurationTag);
Timer.stop(previewDurationTag);

const event = {
event_name: PREVIEW_END_EVENT,
value: {
duration: previewDurationTimer ? previewDurationTimer.elapsed : null,
viewer_status: this.viewer.getLoadStatus()
},
...this.createLogEvent()
};

Timer.reset(previewDurationTag);
this.emit(PREVIEW_METRIC, event);
}

this.viewer.destroy();
}

Expand Down Expand Up @@ -522,6 +550,14 @@ class Preview extends EventEmitter {
DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
});
}

const downloadAttemptEvent = {
event_name: PREVIEW_DOWNLOAD_ATTEMPT_EVENT,
value: this.viewer ? this.viewer.getLoadStatus() : null,
...this.createLogEvent()
};

this.emit(PREVIEW_METRIC, downloadAttemptEvent);
}

/**
Expand Down Expand Up @@ -793,6 +829,10 @@ class Preview extends EventEmitter {
// Setup loading UI and progress bar
this.ui.showLoadingIndicator();
this.ui.startProgressBar();

// Start the preview duration timer when the user starts to perceive preview's load
const previewDurationTag = Timer.createTag(this.file.id, DURATION_METRIC);
Timer.start(previewDurationTag);
}

/**
Expand Down
66 changes: 59 additions & 7 deletions src/lib/__tests__/Preview-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,21 @@ describe('lib/Preview', () => {
};

stubs.viewer = {
destroy: sandbox.stub()
destroy: sandbox.stub(),
getLoadStatus: sandbox.stub()
};
});

it('should invoke emitLoadMetrics()', () => {
stubs.emitLoadMetrics = sandbox.stub(preview, 'emitLoadMetrics');
preview.destroy();
expect(stubs.emitLoadMetrics).to.be.called;
});

it('should destroy the viewer if it exists', () => {
preview.viewer = {
destroy: undefined
destroy: undefined,
getLoadStatus: sandbox.stub()
};

preview.destroy();
Expand All @@ -93,15 +101,41 @@ describe('lib/Preview', () => {
expect(stubs.viewer.destroy).to.be.called;
});

it('should clear the viewer', () => {
it('should stop the duration timer, reset it, and log a preview end event', () => {
preview.file = {
id: 1
};
stubs.viewer.getLoadStatus.returns('loaded');
sandbox.stub(preview, 'createLogEvent');
const durationTimer = {
elapsed: 7
};

const mockEventObject = {
event_name: 'preview_end',
value: {
duration: durationTimer.elapsed,
viewer_status: 'loaded'
}
};

sandbox.stub(Timer, 'createTag').returns('duration_tag');
sandbox.stub(Timer, 'get').returns(durationTimer);
sandbox.stub(Timer, 'stop');
sandbox.stub(Timer, 'reset');
sandbox.stub(preview, 'emit');
preview.viewer = stubs.viewer;

preview.destroy();
expect(preview.viewer).to.equal(undefined);
expect(Timer.createTag).to.be.called;
expect(Timer.stop).to.be.calledWith('duration_tag');
expect(stubs.viewer.getLoadStatus).to.be.called;
expect(preview.emit).to.be.calledWith(PREVIEW_METRIC, mockEventObject);
});

it('should invoke emitLoadMetrics()', () => {
stubs.emitLoadMetrics = sandbox.stub(preview, 'emitLoadMetrics');
it('should clear the viewer', () => {
preview.destroy();
expect(stubs.emitLoadMetrics).to.be.called;
expect(preview.viewer).to.equal(undefined);
});
});

Expand Down Expand Up @@ -767,13 +801,15 @@ describe('lib/Preview', () => {
preview.viewer = {
getRepresentation: sandbox.stub(),
getAssetPath: sandbox.stub(),
getLoadStatus: sandbox.stub(),
createContentUrlWithAuthParams: sandbox.stub(),
options: {
viewer: {
ASSET: ''
}
}
};
sandbox.stub(preview, 'emit');
sandbox.stub(file, 'canDownload');
sandbox.stub(file, 'shouldDownloadWM');
sandbox.stub(util, 'openUrlInsideIframe');
Expand Down Expand Up @@ -845,6 +881,22 @@ describe('lib/Preview', () => {
expect(DownloadReachability.downloadWithReachabilityCheck).to.be.calledWith(url);
});
});

it('should emit the download attempted metric', () => {
file.canDownload.returns(true);
file.shouldDownloadWM.returns(false);

const url = 'someurl';
util.appendQueryParams.returns(url);

const promise = Promise.resolve({
download_url: url
});

util.get.returns(promise);
preview.download();
expect(preview.emit).to.be.calledWith('preview_metric');
});
});

describe('updateToken()', () => {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export const LOAD_METRIC = {
fullDocumentLoadTime: 'full_document_load_time' // How long it took to load the document so it could be previewed.
};

export const DURATION_METRIC = 'preview_duration_metric';
// Event fired from preview with preview duration metrics
export const PREVIEW_END_EVENT = 'preview_end';
// Event fired when the user attempts to download the file
export const PREVIEW_DOWNLOAD_ATTEMPT_EVENT = 'preview_download_attempt';
// Events around download reachability
export const DOWNLOAD_REACHABILITY_METRICS = {
NOTIFICATION_SHOWN: 'dl_reachability_notification_shown',
Expand Down
20 changes: 20 additions & 0 deletions src/lib/viewers/BaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ import { VIEWER_EVENT, ERROR_CODE, LOAD_METRIC, DOWNLOAD_REACHABILITY_METRICS }
import PreviewError from '../PreviewError';
import Timer from '../Timer';

const VIEWER_STATUSES = {
error: 'error',
loaded: 'loaded',
loading: 'loading'
};

const ANNOTATIONS_JS = 'annotations.js';
const ANNOTATIONS_CSS = 'annotations.css';

Expand Down Expand Up @@ -750,6 +756,20 @@ class BaseViewer extends EventEmitter {
return repStatus;
}

/**
* Returns a string representing the viewer's loading status. Either loading, loaded, or error
*
* @public
* @return {string} A string representing the viewer's load status
*/
getLoadStatus() {
if (this.loaded) {
return this.options.viewer.NAME === 'Error' ? VIEWER_STATUSES.error : VIEWER_STATUSES.loaded;
}

return VIEWER_STATUSES.loading;
}

/**
* Returns if representation status is considered success
*
Expand Down
17 changes: 17 additions & 0 deletions src/lib/viewers/__tests__/BaseViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,23 @@ describe('lib/viewers/BaseViewer', () => {
});
});

describe('getLoadStatus()', () => {
it('should return the correct string based on load status and viewer type', () => {
base.loaded = false;
expect(base.getLoadStatus()).to.equal('loading');

base.loaded = true;
base.options.viewer = {
NAME: 'Error'
};

expect(base.getLoadStatus()).to.equal('error');

base.options.viewer.NAME = 'Dash';
expect(base.getLoadStatus()).to.equal('loaded');
});
});

describe('isRepresentationReady()', () => {
it('should return whether the representation has a successful status', () => {
const representation = {
Expand Down