feat(GUI): add uuid to flash events (#1344)

Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
Ștefan Daniel Mihăilă 2017-04-23 01:40:05 +01:00 committed by Juan Cruz Viotti
parent 78ab59a55e
commit 9e7e5de63a
8 changed files with 149 additions and 49 deletions

View File

@ -206,7 +206,9 @@ app.run(($window, AnalyticsService, WarningModalService, ErrorService, OSDialogS
description: messages.warning.exitWhileFlashing()
}).then((confirmed) => {
if (confirmed) {
AnalyticsService.logEvent('Close confirmed while flashing');
AnalyticsService.logEvent('Close confirmed while flashing', {
uuid: flashState.getFlashUuid()
});
// This circumvents the 'beforeunload' event unlike
// electron.remote.app.quit() which does not.

View File

@ -220,3 +220,20 @@ exports.getLastFlashSourceChecksum = () => {
exports.getLastFlashErrorCode = () => {
return exports.getFlashResults().errorCode;
};
/**
* @summary Get current (or last) flash uuid
* @function
* @public
*
* @description
* This function returns undefined if no flash has been started yet.
*
* @returns {String} the last flash uuid
*
* @example
* const uuid = flashState.getFlashUuid();
*/
exports.getFlashUuid = () => {
return Store.getState().toJS().flashUuid;
};

View File

@ -20,6 +20,7 @@ const Immutable = require('immutable');
const _ = require('lodash');
const redux = require('redux');
const persistState = require('redux-localstorage');
const uuidV4 = require('uuid/v4');
const constraints = require('../../shared/drive-constraints');
const errors = require('../../shared/errors');
@ -226,13 +227,16 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
case ACTIONS.RESET_FLASH_STATE: {
return state
.set('isFlashing', false)
.set('flashState', DEFAULT_STATE.get('flashState'))
.set('flashResults', DEFAULT_STATE.get('flashResults'));
.set('flashResults', DEFAULT_STATE.get('flashResults'))
.delete('flashUuid');
}
case ACTIONS.SET_FLASHING_FLAG: {
return state
.set('isFlashing', true)
.set('flashUuid', uuidV4())
.set('flashResults', DEFAULT_STATE.get('flashResults'));
}

View File

@ -21,16 +21,18 @@
*/
const angular = require('angular');
const _ = require('lodash');
const childWriter = require('../../child-writer');
const settings = require('../models/settings');
const flashState = require('../models/flash-state');
const windowProgress = require('../os/window-progress');
const MODULE_NAME = 'Etcher.Modules.ImageWriter';
const imageWriter = angular.module(MODULE_NAME, [
require('../models/selection-state')
require('./analytics')
]);
imageWriter.service('ImageWriterService', function($q, $rootScope) {
imageWriter.service('ImageWriterService', function($q, $rootScope, AnalyticsService) {
/**
* @summary Perform write operation
@ -92,6 +94,16 @@ imageWriter.service('ImageWriterService', function($q, $rootScope) {
flashState.setFlashingFlag();
const analyticsData = {
image,
drive,
uuid: flashState.getFlashUuid(),
unmountOnSuccess: settings.get('unmountOnSuccess'),
validateWriteOnSuccess: settings.get('validateWriteOnSuccess')
};
AnalyticsService.logEvent('Flash', analyticsData);
return this.performWrite(image, drive, (state) => {
// Bring this value to the world of angular.
@ -102,12 +114,34 @@ imageWriter.service('ImageWriterService', function($q, $rootScope) {
flashState.setProgressState(state);
});
}).then(flashState.unsetFlashingFlag).catch((error) => {
}).then(flashState.unsetFlashingFlag).then(() => {
if (flashState.wasLastFlashCancelled()) {
AnalyticsService.logEvent('Elevation cancelled', analyticsData);
} else {
AnalyticsService.logEvent('Done', analyticsData);
}
}).catch((error) => {
flashState.unsetFlashingFlag({
errorCode: error.code
});
if (error.code === 'EVALIDATION') {
AnalyticsService.logEvent('Validation error', analyticsData);
} else if (error.code === 'EUNPLUGGED') {
AnalyticsService.logEvent('Drive unplugged', analyticsData);
} else if (error.code === 'EIO') {
AnalyticsService.logEvent('Input/output error', analyticsData);
} else if (error.code === 'ENOSPC') {
AnalyticsService.logEvent('Out of space', analyticsData);
} else {
AnalyticsService.logEvent('Flash error', _.merge({
error
}, analyticsData));
}
return $q.reject(error);
}).finally(() => {
windowProgress.clear();
});
};

View File

@ -19,13 +19,11 @@
const messages = require('../../../../shared/messages');
const settings = require('../../../models/settings');
const flashState = require('../../../models/flash-state');
const windowProgress = require('../../../os/window-progress');
module.exports = function(
$state,
DriveScannerService,
ImageWriterService,
AnalyticsService,
FlashErrorModalService,
ErrorService,
OSNotificationService
@ -60,69 +58,33 @@ module.exports = function(
// otherwise Windows throws EPERM
DriveScannerService.stop();
AnalyticsService.logEvent('Flash', {
image,
drive,
unmountOnSuccess: settings.get('unmountOnSuccess'),
validateWriteOnSuccess: settings.get('validateWriteOnSuccess')
});
ImageWriterService.flash(image.path, drive).then(() => {
if (flashState.wasLastFlashCancelled()) {
AnalyticsService.logEvent('Elevation cancelled', {
image,
drive
});
return;
if (!flashState.wasLastFlashCancelled()) {
OSNotificationService.send('Success!', messages.info.flashComplete());
$state.go('success');
}
OSNotificationService.send('Success!', messages.info.flashComplete());
AnalyticsService.logEvent('Done', {
image,
drive
});
$state.go('success');
})
.catch((error) => {
OSNotificationService.send('Oops!', messages.error.flashFailure());
// TODO: All these error codes to messages translations
// should go away if the writer emitted user friendly
// messages on the first place.
if (error.code === 'EVALIDATION') {
FlashErrorModalService.show(messages.error.validation());
AnalyticsService.logEvent('Validation error', {
image,
drive
});
} else if (error.code === 'EUNPLUGGED') {
FlashErrorModalService.show(messages.error.driveUnplugged());
AnalyticsService.logEvent('Drive unplugged', {
image,
drive
});
} else if (error.code === 'EIO') {
FlashErrorModalService.show(messages.error.inputOutput());
AnalyticsService.logEvent('Input/output error', {
image,
drive
});
} else if (error.code === 'ENOSPC') {
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive());
AnalyticsService.logEvent('Out of space', {
image,
drive
});
} else {
FlashErrorModalService.show(messages.error.genericFlashError());
ErrorService.reportException(error);
AnalyticsService.logEvent('Flash error', {
error,
image,
drive
});
}
})
.finally(() => {
windowProgress.clear();
DriveScannerService.start();
});
};

5
npm-shrinkwrap.json generated
View File

@ -6942,6 +6942,11 @@
}
}
},
"uuid": {
"version": "3.0.1",
"from": "uuid@latest",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz"
},
"validate-npm-package-license": {
"version": "3.0.1",
"from": "validate-npm-package-license@>=3.0.1 <4.0.0",

View File

@ -98,6 +98,7 @@
"trackjs": "^2.1.16",
"udif": "^0.9.0",
"unbzip2-stream": "^1.0.11",
"uuid": "^3.0.1",
"yargs": "^4.6.0",
"yauzl": "^2.6.0"
},

View File

@ -5,6 +5,10 @@ const flashState = require('../../../lib/gui/models/flash-state');
describe('Browser: flashState', function() {
beforeEach(function() {
flashState.resetState();
});
describe('flashState', function() {
describe('.resetState()', function() {
@ -36,6 +40,18 @@ describe('Browser: flashState', function() {
m.chai.expect(flashState.getFlashResults()).to.deep.equal({});
});
it('should unset the flashing flag', function() {
flashState.setFlashingFlag();
flashState.resetState();
m.chai.expect(flashState.isFlashing()).to.be.false;
});
it('should unset the flash uuid', function() {
flashState.setFlashingFlag();
flashState.resetState();
m.chai.expect(flashState.getFlashUuid()).to.be.undefined;
});
});
describe('.isFlashing()', function() {
@ -337,6 +353,19 @@ describe('Browser: flashState', function() {
});
});
it('should not reset the flash uuid', function() {
flashState.setFlashingFlag();
const uuidBeforeUnset = flashState.getFlashUuid();
flashState.unsetFlashingFlag({
sourceChecksum: '1234',
cancelled: false
});
const uuidAfterUnset = flashState.getFlashUuid();
m.chai.expect(uuidBeforeUnset).to.equal(uuidAfterUnset);
});
});
describe('.setFlashingFlag()', function() {
@ -441,6 +470,52 @@ describe('Browser: flashState', function() {
});
describe('.getFlashUuid()', function() {
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
it('should be initially undefined', function() {
m.chai.expect(flashState.getFlashUuid()).to.be.undefined;
});
it('should be a valid uuid if the flashing flag is set', function() {
flashState.setFlashingFlag();
const uuid = flashState.getFlashUuid();
m.chai.expect(UUID_REGEX.test(uuid)).to.be.true;
});
it('should return different uuids every time the flashing flag is set', function() {
flashState.setFlashingFlag();
const uuid1 = flashState.getFlashUuid();
flashState.unsetFlashingFlag({
sourceChecksum: '1234',
cancelled: false
});
flashState.setFlashingFlag();
const uuid2 = flashState.getFlashUuid();
flashState.unsetFlashingFlag({
cancelled: true
});
flashState.setFlashingFlag();
const uuid3 = flashState.getFlashUuid();
flashState.unsetFlashingFlag({
sourceChecksum: '1234',
cancelled: false
});
m.chai.expect(UUID_REGEX.test(uuid1)).to.be.true;
m.chai.expect(UUID_REGEX.test(uuid2)).to.be.true;
m.chai.expect(UUID_REGEX.test(uuid3)).to.be.true;
m.chai.expect(uuid1).to.not.equal(uuid2);
m.chai.expect(uuid2).to.not.equal(uuid3);
m.chai.expect(uuid3).to.not.equal(uuid1);
});
});
});
});