mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-26 20:56:34 +00:00
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:
parent
78ab59a55e
commit
9e7e5de63a
@ -206,7 +206,9 @@ app.run(($window, AnalyticsService, WarningModalService, ErrorService, OSDialogS
|
|||||||
description: messages.warning.exitWhileFlashing()
|
description: messages.warning.exitWhileFlashing()
|
||||||
}).then((confirmed) => {
|
}).then((confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
AnalyticsService.logEvent('Close confirmed while flashing');
|
AnalyticsService.logEvent('Close confirmed while flashing', {
|
||||||
|
uuid: flashState.getFlashUuid()
|
||||||
|
});
|
||||||
|
|
||||||
// This circumvents the 'beforeunload' event unlike
|
// This circumvents the 'beforeunload' event unlike
|
||||||
// electron.remote.app.quit() which does not.
|
// electron.remote.app.quit() which does not.
|
||||||
|
@ -220,3 +220,20 @@ exports.getLastFlashSourceChecksum = () => {
|
|||||||
exports.getLastFlashErrorCode = () => {
|
exports.getLastFlashErrorCode = () => {
|
||||||
return exports.getFlashResults().errorCode;
|
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;
|
||||||
|
};
|
||||||
|
@ -20,6 +20,7 @@ const Immutable = require('immutable');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const redux = require('redux');
|
const redux = require('redux');
|
||||||
const persistState = require('redux-localstorage');
|
const persistState = require('redux-localstorage');
|
||||||
|
const uuidV4 = require('uuid/v4');
|
||||||
const constraints = require('../../shared/drive-constraints');
|
const constraints = require('../../shared/drive-constraints');
|
||||||
const errors = require('../../shared/errors');
|
const errors = require('../../shared/errors');
|
||||||
|
|
||||||
@ -226,13 +227,16 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
|||||||
|
|
||||||
case ACTIONS.RESET_FLASH_STATE: {
|
case ACTIONS.RESET_FLASH_STATE: {
|
||||||
return state
|
return state
|
||||||
|
.set('isFlashing', false)
|
||||||
.set('flashState', DEFAULT_STATE.get('flashState'))
|
.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: {
|
case ACTIONS.SET_FLASHING_FLAG: {
|
||||||
return state
|
return state
|
||||||
.set('isFlashing', true)
|
.set('isFlashing', true)
|
||||||
|
.set('flashUuid', uuidV4())
|
||||||
.set('flashResults', DEFAULT_STATE.get('flashResults'));
|
.set('flashResults', DEFAULT_STATE.get('flashResults'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,16 +21,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const angular = require('angular');
|
const angular = require('angular');
|
||||||
|
const _ = require('lodash');
|
||||||
const childWriter = require('../../child-writer');
|
const childWriter = require('../../child-writer');
|
||||||
const settings = require('../models/settings');
|
const settings = require('../models/settings');
|
||||||
const flashState = require('../models/flash-state');
|
const flashState = require('../models/flash-state');
|
||||||
|
const windowProgress = require('../os/window-progress');
|
||||||
|
|
||||||
const MODULE_NAME = 'Etcher.Modules.ImageWriter';
|
const MODULE_NAME = 'Etcher.Modules.ImageWriter';
|
||||||
const imageWriter = angular.module(MODULE_NAME, [
|
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
|
* @summary Perform write operation
|
||||||
@ -92,6 +94,16 @@ imageWriter.service('ImageWriterService', function($q, $rootScope) {
|
|||||||
|
|
||||||
flashState.setFlashingFlag();
|
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) => {
|
return this.performWrite(image, drive, (state) => {
|
||||||
|
|
||||||
// Bring this value to the world of angular.
|
// Bring this value to the world of angular.
|
||||||
@ -102,12 +114,34 @@ imageWriter.service('ImageWriterService', function($q, $rootScope) {
|
|||||||
flashState.setProgressState(state);
|
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({
|
flashState.unsetFlashingFlag({
|
||||||
errorCode: error.code
|
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);
|
return $q.reject(error);
|
||||||
|
}).finally(() => {
|
||||||
|
windowProgress.clear();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,13 +19,11 @@
|
|||||||
const messages = require('../../../../shared/messages');
|
const messages = require('../../../../shared/messages');
|
||||||
const settings = require('../../../models/settings');
|
const settings = require('../../../models/settings');
|
||||||
const flashState = require('../../../models/flash-state');
|
const flashState = require('../../../models/flash-state');
|
||||||
const windowProgress = require('../../../os/window-progress');
|
|
||||||
|
|
||||||
module.exports = function(
|
module.exports = function(
|
||||||
$state,
|
$state,
|
||||||
DriveScannerService,
|
DriveScannerService,
|
||||||
ImageWriterService,
|
ImageWriterService,
|
||||||
AnalyticsService,
|
|
||||||
FlashErrorModalService,
|
FlashErrorModalService,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
OSNotificationService
|
OSNotificationService
|
||||||
@ -60,69 +58,33 @@ module.exports = function(
|
|||||||
// otherwise Windows throws EPERM
|
// otherwise Windows throws EPERM
|
||||||
DriveScannerService.stop();
|
DriveScannerService.stop();
|
||||||
|
|
||||||
AnalyticsService.logEvent('Flash', {
|
|
||||||
image,
|
|
||||||
drive,
|
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess'),
|
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess')
|
|
||||||
});
|
|
||||||
|
|
||||||
ImageWriterService.flash(image.path, drive).then(() => {
|
ImageWriterService.flash(image.path, drive).then(() => {
|
||||||
if (flashState.wasLastFlashCancelled()) {
|
if (!flashState.wasLastFlashCancelled()) {
|
||||||
AnalyticsService.logEvent('Elevation cancelled', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSNotificationService.send('Success!', messages.info.flashComplete());
|
OSNotificationService.send('Success!', messages.info.flashComplete());
|
||||||
AnalyticsService.logEvent('Done', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
$state.go('success');
|
$state.go('success');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
OSNotificationService.send('Oops!', messages.error.flashFailure());
|
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') {
|
if (error.code === 'EVALIDATION') {
|
||||||
FlashErrorModalService.show(messages.error.validation());
|
FlashErrorModalService.show(messages.error.validation());
|
||||||
AnalyticsService.logEvent('Validation error', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
} else if (error.code === 'EUNPLUGGED') {
|
} else if (error.code === 'EUNPLUGGED') {
|
||||||
FlashErrorModalService.show(messages.error.driveUnplugged());
|
FlashErrorModalService.show(messages.error.driveUnplugged());
|
||||||
AnalyticsService.logEvent('Drive unplugged', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
} else if (error.code === 'EIO') {
|
} else if (error.code === 'EIO') {
|
||||||
FlashErrorModalService.show(messages.error.inputOutput());
|
FlashErrorModalService.show(messages.error.inputOutput());
|
||||||
AnalyticsService.logEvent('Input/output error', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
} else if (error.code === 'ENOSPC') {
|
} else if (error.code === 'ENOSPC') {
|
||||||
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive());
|
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive());
|
||||||
AnalyticsService.logEvent('Out of space', {
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
FlashErrorModalService.show(messages.error.genericFlashError());
|
FlashErrorModalService.show(messages.error.genericFlashError());
|
||||||
ErrorService.reportException(error);
|
ErrorService.reportException(error);
|
||||||
AnalyticsService.logEvent('Flash error', {
|
|
||||||
error,
|
|
||||||
image,
|
|
||||||
drive
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
windowProgress.clear();
|
|
||||||
DriveScannerService.start();
|
DriveScannerService.start();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
5
npm-shrinkwrap.json
generated
5
npm-shrinkwrap.json
generated
@ -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": {
|
"validate-npm-package-license": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"from": "validate-npm-package-license@>=3.0.1 <4.0.0",
|
"from": "validate-npm-package-license@>=3.0.1 <4.0.0",
|
||||||
|
@ -98,6 +98,7 @@
|
|||||||
"trackjs": "^2.1.16",
|
"trackjs": "^2.1.16",
|
||||||
"udif": "^0.9.0",
|
"udif": "^0.9.0",
|
||||||
"unbzip2-stream": "^1.0.11",
|
"unbzip2-stream": "^1.0.11",
|
||||||
|
"uuid": "^3.0.1",
|
||||||
"yargs": "^4.6.0",
|
"yargs": "^4.6.0",
|
||||||
"yauzl": "^2.6.0"
|
"yauzl": "^2.6.0"
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,10 @@ const flashState = require('../../../lib/gui/models/flash-state');
|
|||||||
|
|
||||||
describe('Browser: flashState', function() {
|
describe('Browser: flashState', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
flashState.resetState();
|
||||||
|
});
|
||||||
|
|
||||||
describe('flashState', function() {
|
describe('flashState', function() {
|
||||||
|
|
||||||
describe('.resetState()', function() {
|
describe('.resetState()', function() {
|
||||||
@ -36,6 +40,18 @@ describe('Browser: flashState', function() {
|
|||||||
m.chai.expect(flashState.getFlashResults()).to.deep.equal({});
|
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() {
|
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() {
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user