mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-21 18:26:32 +00:00
refactor(GUI): unify error handling (#620)
Error handling is currently a mess. The knowledge to correctly report an error both to the end user and to us is scattered in many places. This PR introduces the following changes: - Rename `AnalyticsService.logDebug()` to `AnalyticsService.logDebug()` to clarify better the intention of the function. - Move `$log` decorators from an `AnalyticsService` run block to `AnalyticsService.logDebug()`. - Implement `AnalyticsService.logException()`, whose duty is to log an exception to TrackJS or any related service, and log it to DevTools. - Implement `ErrorService.reportException()`, whose duty is to report an exception to every interested party. This means logging the error to TrackJS, displaying it DevTools and showing a nice alert to the user. This function is based from `handleError()` from `MainController`. - Move global `$exceptionHandler` error handler to the entry point of the application, and make it simply call `ErrorService.reportException()`. - Replace every `handleError()` call in `MainController` with `ErrorService.reportException()`. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
be2413d8cb
commit
1a46ac0301
@ -35,6 +35,7 @@ const app = angular.module('Etcher', [
|
|||||||
|
|
||||||
// Etcher modules
|
// Etcher modules
|
||||||
require('./modules/analytics'),
|
require('./modules/analytics'),
|
||||||
|
require('./modules/error'),
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
require('./models/selection-state'),
|
require('./models/selection-state'),
|
||||||
@ -95,7 +96,7 @@ app.run((AnalyticsService, OSWindowProgressService, FlashStateModel) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsService.log([
|
AnalyticsService.logDebug([
|
||||||
`Progress (${flashState.type}):`,
|
`Progress (${flashState.type}):`,
|
||||||
`${flashState.percentage}% at ${flashState.speed} MB/s`,
|
`${flashState.percentage}% at ${flashState.speed} MB/s`,
|
||||||
`(eta ${flashState.eta}s)`
|
`(eta ${flashState.eta}s)`
|
||||||
@ -108,3 +109,13 @@ app.run((AnalyticsService, OSWindowProgressService, FlashStateModel) => {
|
|||||||
app.config(($urlRouterProvider) => {
|
app.config(($urlRouterProvider) => {
|
||||||
$urlRouterProvider.otherwise('/main');
|
$urlRouterProvider.otherwise('/main');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.config(($provide) => {
|
||||||
|
$provide.decorator('$exceptionHandler', ($delegate, $injector) => {
|
||||||
|
return (exception, cause) => {
|
||||||
|
const ErrorService = $injector.get('ErrorService');
|
||||||
|
ErrorService.reportException(exception);
|
||||||
|
$delegate(exception, cause);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -39,6 +39,9 @@ const analytics = angular.module(MODULE_NAME, [
|
|||||||
require('../models/settings')
|
require('../models/settings')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Mixpanel integration
|
||||||
|
// https://github.com/kuhnza/angular-mixpanel
|
||||||
|
|
||||||
analytics.config(($mixpanelProvider) => {
|
analytics.config(($mixpanelProvider) => {
|
||||||
$mixpanelProvider.apiKey('63e5fc4563e00928da67d1226364dd4c');
|
$mixpanelProvider.apiKey('63e5fc4563e00928da67d1226364dd4c');
|
||||||
|
|
||||||
@ -74,41 +77,7 @@ analytics.run(($window) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
analytics.config(($provide) => {
|
analytics.service('AnalyticsService', function($log, $window, $mixpanel, SettingsModel) {
|
||||||
$provide.decorator('$exceptionHandler', ($delegate, $window, $injector) => {
|
|
||||||
return (exception, cause) => {
|
|
||||||
const SettingsModel = $injector.get('SettingsModel');
|
|
||||||
|
|
||||||
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
|
||||||
$window.trackJs.track(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
$delegate(exception, cause);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
$provide.decorator('$log', ($delegate, $window, $injector) => {
|
|
||||||
|
|
||||||
// Save the original $log.debug()
|
|
||||||
const debugFn = $delegate.debug;
|
|
||||||
|
|
||||||
$delegate.debug = (message) => {
|
|
||||||
message = new Date() + ' ' + message;
|
|
||||||
|
|
||||||
const SettingsModel = $injector.get('SettingsModel');
|
|
||||||
|
|
||||||
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
|
||||||
$window.trackJs.console.debug(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugFn(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
return $delegate;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
analytics.service('AnalyticsService', function($log, $mixpanel, SettingsModel) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Log a debug message
|
* @summary Log a debug message
|
||||||
@ -123,7 +92,13 @@ analytics.service('AnalyticsService', function($log, $mixpanel, SettingsModel) {
|
|||||||
* @example
|
* @example
|
||||||
* AnalyticsService.log('Hello World');
|
* AnalyticsService.log('Hello World');
|
||||||
*/
|
*/
|
||||||
this.log = (message) => {
|
this.logDebug = (message) => {
|
||||||
|
message = new Date() + ' ' + message;
|
||||||
|
|
||||||
|
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
||||||
|
$window.trackJs.console.debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
$log.debug(message);
|
$log.debug(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,7 +133,28 @@ analytics.service('AnalyticsService', function($log, $mixpanel, SettingsModel) {
|
|||||||
message += ` (${JSON.stringify(data)})`;
|
message += ` (${JSON.stringify(data)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log(message);
|
this.logDebug(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Log an exception
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* This function logs an exception in TrackJS.
|
||||||
|
*
|
||||||
|
* @param {Error} exception - exception
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* AnalyticsService.logException(new Error('Something happened'));
|
||||||
|
*/
|
||||||
|
this.logException = (exception) => {
|
||||||
|
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
||||||
|
$window.trackJs.track(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$log.error(exception);
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
58
lib/gui/modules/error.js
Normal file
58
lib/gui/modules/error.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module Etcher.Modules.Error
|
||||||
|
*/
|
||||||
|
|
||||||
|
const angular = require('angular');
|
||||||
|
|
||||||
|
const MODULE_NAME = 'Etcher.Modules.Error';
|
||||||
|
const error = angular.module(MODULE_NAME, [
|
||||||
|
require('../modules/analytics'),
|
||||||
|
require('../os/dialog/dialog')
|
||||||
|
]);
|
||||||
|
|
||||||
|
error.service('ErrorService', function(AnalyticsService, OSDialogService) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Report an exception
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @param {Error} exception - exception
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ErrorService.reportException(new Error('Something happened'));
|
||||||
|
*/
|
||||||
|
this.reportException = (exception) => {
|
||||||
|
|
||||||
|
// This particular error is handled by the alert ribbon
|
||||||
|
// on the main application page.
|
||||||
|
if (exception.code === 'ENOSPC') {
|
||||||
|
AnalyticsService.logEvent('Drive ran out of space');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSDialogService.showError(exception, exception.description);
|
||||||
|
AnalyticsService.logException(exception);
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = MODULE_NAME;
|
@ -29,6 +29,7 @@ module.exports = function(
|
|||||||
DrivesModel,
|
DrivesModel,
|
||||||
ImageWriterService,
|
ImageWriterService,
|
||||||
AnalyticsService,
|
AnalyticsService,
|
||||||
|
ErrorService,
|
||||||
DriveSelectorService,
|
DriveSelectorService,
|
||||||
TooltipModalService,
|
TooltipModalService,
|
||||||
OSWindowProgressService,
|
OSWindowProgressService,
|
||||||
@ -44,25 +45,9 @@ module.exports = function(
|
|||||||
this.settings = SettingsModel;
|
this.settings = SettingsModel;
|
||||||
this.tooltipModal = TooltipModalService;
|
this.tooltipModal = TooltipModalService;
|
||||||
|
|
||||||
const handleError = (error) => {
|
|
||||||
|
|
||||||
// This particular error is handled by the alert ribbon
|
|
||||||
// on the main application page.
|
|
||||||
if (error.code === 'ENOSPC') {
|
|
||||||
AnalyticsService.logEvent('Drive ran out of space');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSDialogService.showError(error, error.description);
|
|
||||||
|
|
||||||
// Also throw it so it gets displayed in DevTools
|
|
||||||
// and its reported by TrackJS.
|
|
||||||
throw error;
|
|
||||||
};
|
|
||||||
|
|
||||||
DriveScannerService.start();
|
DriveScannerService.start();
|
||||||
|
|
||||||
DriveScannerService.on('error', handleError);
|
DriveScannerService.on('error', ErrorService.reportException);
|
||||||
|
|
||||||
DriveScannerService.on('drives', (drives) => {
|
DriveScannerService.on('drives', (drives) => {
|
||||||
|
|
||||||
@ -134,7 +119,7 @@ module.exports = function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectImage(image);
|
this.selectImage(image);
|
||||||
}).catch(handleError);
|
}).catch(ErrorService.reportException);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.selectDrive = (drive) => {
|
this.selectDrive = (drive) => {
|
||||||
@ -152,7 +137,7 @@ module.exports = function(
|
|||||||
this.openDriveSelector = () => {
|
this.openDriveSelector = () => {
|
||||||
DriveSelectorService.open()
|
DriveSelectorService.open()
|
||||||
.then(this.selectDrive)
|
.then(this.selectDrive)
|
||||||
.catch(handleError);
|
.catch(ErrorService.reportException);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reselectImage = () => {
|
this.reselectImage = () => {
|
||||||
@ -225,7 +210,7 @@ module.exports = function(
|
|||||||
AnalyticsService.logEvent('Flash error');
|
AnalyticsService.logEvent('Flash error');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(error);
|
ErrorService.reportException(error);
|
||||||
})
|
})
|
||||||
.finally(OSWindowProgressService.clear);
|
.finally(OSWindowProgressService.clear);
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,7 @@ const MainPage = angular.module(MODULE_NAME, [
|
|||||||
require('../../modules/drive-scanner'),
|
require('../../modules/drive-scanner'),
|
||||||
require('../../modules/image-writer'),
|
require('../../modules/image-writer'),
|
||||||
require('../../modules/analytics'),
|
require('../../modules/analytics'),
|
||||||
|
require('../../modules/error'),
|
||||||
require('../../models/selection-state'),
|
require('../../models/selection-state'),
|
||||||
require('../../models/flash-state'),
|
require('../../models/flash-state'),
|
||||||
require('../../models/settings'),
|
require('../../models/settings'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user