patch: remove analytics

This commit is contained in:
Edwin Joassart 2025-05-07 14:57:50 +02:00
parent fdd082b9cd
commit aa6d526fea
No known key found for this signature in database
GPG Key ID: 6337EBE5AC716051
18 changed files with 29 additions and 485 deletions

View File

@ -12,7 +12,7 @@ inputs:
# --- custom environment # --- custom environment
NODE_VERSION: NODE_VERSION:
type: string type: string
default: '20.10' default: '20.19'
VERBOSE: VERBOSE:
type: string type: string
default: 'true' default: 'true'

View File

@ -1,10 +1,8 @@
Maintaining Etcher # Maintaining Etcher
==================
This document is meant to serve as a guide for maintainers to perform common tasks. This document is meant to serve as a guide for maintainers to perform common tasks.
Releasing ## Releasing
---------
### Release Types ### Release Types
@ -13,10 +11,9 @@ Releasing
- **release**: Full releases - **release**: Full releases
Draft release is created from each PR, tagged with the branch name. Draft release is created from each PR, tagged with the branch name.
All merged PR will generate a new tag/version as a *pre-release*. All merged PR will generate a new tag/version as a _pre-release_.
Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary. Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary.
#### Preparation #### Preparation
- [Prepare the new version](#preparing-a-new-version) - [Prepare the new version](#preparing-a-new-version)
@ -35,7 +32,7 @@ Mark the pre-release as final when it is necessary, then distribute the packages
- [Post release note to forums](https://forums.balena.io/c/etcher) - [Post release note to forums](https://forums.balena.io/c/etcher)
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec) - [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
- [Update the website](https://github.com/balena-io/etcher-homepage) - [Update the website](https://github.com/balena-io/etcher-homepage)
- Wait 2-3 hours for analytics (Sentry, Amplitude) to trickle in and check for elevated error rates, or regressions - Wait 2-3 hours for analytics (Sentry) to trickle in and check for elevated error rates, or regressions
- If regressions arise; pull the release, and release a patched version, else: - If regressions arise; pull the release, and release a patched version, else:
- [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront) - [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
- Post changelog with `#release-notes` tag on internal chat - Post changelog with `#release-notes` tag on internal chat
@ -51,7 +48,6 @@ Make sure to set the analytics tokens when generating production release binarie
```bash ```bash
export ANALYTICS_SENTRY_TOKEN="xxxxxx" export ANALYTICS_SENTRY_TOKEN="xxxxxx"
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
``` ```
#### Linux #### Linux
@ -71,7 +67,6 @@ npm run make
Our CI will appropriately sign artifacts for macOS and some Windows targets. Our CI will appropriately sign artifacts for macOS and some Windows targets.
### Uploading packages to Cloudfront ### Uploading packages to Cloudfront
Log in to cloudfront and upload the `rpm` and `deb` files. Log in to cloudfront and upload the `rpm` and `deb` files.
@ -99,7 +94,6 @@ aws s3api delete-object --bucket <bucket name> --key <file name>
The Bintray dashboard provides an easy way to delete a version's files. The Bintray dashboard provides an easy way to delete a version's files.
### Submitting binaries to Symantec ### Submitting binaries to Symantec
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/) - [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)

View File

@ -1,11 +1,9 @@
Manual Testing # Manual Testing
==============
This document describes a high-level script of manual tests to check for. We This document describes a high-level script of manual tests to check for. We
should aim to replace items on this list with automated Spectron test cases. should aim to replace items on this list with automated Spectron test cases.
Image Selection ## Image Selection
---------------
- [ ] Cancel image selection dialog - [ ] Cancel image selection dialog
- [ ] Select an unbootable image (without a partition table), and expect a - [ ] Select an unbootable image (without a partition table), and expect a
@ -15,8 +13,7 @@ Image Selection
- [ ] Change image selection - [ ] Change image selection
- [ ] Select a Windows image, and expect a sensible warning - [ ] Select a Windows image, and expect a sensible warning
Drive Selection ## Drive Selection
---------------
- [ ] Open the drive selection modal - [ ] Open the drive selection modal
- [ ] Switch drive selection - [ ] Switch drive selection
@ -33,8 +30,7 @@ Drive Selection
- [ ] Enable "Unsafe Mode", and if there is only one system drive (and no - [ ] Enable "Unsafe Mode", and if there is only one system drive (and no
removable ones), don't expect autoselection removable ones), don't expect autoselection
Image Support ## Image Support
-------------
Run the following tests with and without validation enabled: Run the following tests with and without validation enabled:
@ -51,8 +47,7 @@ Run the following tests with and without validation enabled:
- [ ] Flash an archive image containing a blockmap file - [ ] Flash an archive image containing a blockmap file
- [ ] Flash an archive image containing a manifest metadata file - [ ] Flash an archive image containing a manifest metadata file
Flashing Process ## Flashing Process
----------------
- [ ] Unplug the drive during flash or validation - [ ] Unplug the drive during flash or validation
- [ ] Click "Flash", cancel elevation dialog, and click "Flash" again - [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
@ -72,8 +67,7 @@ In all these cases, the child writer process should not remain alive. Note that
in some systems you need to open your process monitor tool of choice with extra in some systems you need to open your process monitor tool of choice with extra
permissions to see the elevated child writer process. permissions to see the elevated child writer process.
GUI ## GUI
----
- [ ] Close application from the terminal using Ctrl-C while the application is - [ ] Close application from the terminal using Ctrl-C while the application is
idle idle
@ -85,31 +79,20 @@ GUI
- [ ] Minimize the application - [ ] Minimize the application
- [ ] Start the application given no internet connection - [ ] Start the application given no internet connection
Success Banner ## Success Banner
--------------
- [ ] Click an external link on the success banner (with and without internet - [ ] Click an external link on the success banner (with and without internet
connection) connection)
Elevation Prompt ## Elevation Prompt
----------------
- [ ] Flash an image as `root`/administrator - [ ] Flash an image as `root`/administrator
- [ ] Reject elevation prompt - [ ] Reject elevation prompt
- [ ] Put incorrect elevation prompt password - [ ] Put incorrect elevation prompt password
- [ ] Unplug the drive during elevation - [ ] Unplug the drive during elevation
Unmounting ## Unmounting
----------
- [ ] Disable unmounting and flash an image - [ ] Disable unmounting and flash an image
- [ ] Flash an image with a file system that is readable by the host OS, and - [ ] Flash an image with a file system that is readable by the host OS, and
check that is unmounted correctly check that is unmounted correctly
Analytics
---------
- [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
check that no request is sent
- [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
F5), and check that initial events are not sent to Amplitude**

View File

@ -64,9 +64,6 @@ store.dispatch({
data: uuidV4(), data: uuidV4(),
}); });
const applicationSessionUuid = store.getState().toJS().applicationSessionUuid;
const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid;
console.log(outdent` console.log(outdent`
${outdent} ${outdent}
_____ _ _ _____ _ _
@ -82,13 +79,6 @@ console.log(outdent`
Version = ${packageJSON.version}, Type = ${packageJSON.packageType} Version = ${packageJSON.version}, Type = ${packageJSON.packageType}
`); `);
const currentVersion = packageJSON.version;
analytics.logEvent('Application start', {
packageType: packageJSON.packageType,
version: currentVersion,
});
const debouncedLog = debounce(console.log, 1000, { maxWait: 1000 }); const debouncedLog = debounce(console.log, 1000, { maxWait: 1000 });
function pluralize(word: string, quantity: number) { function pluralize(word: string, quantity: number) {
@ -172,9 +162,6 @@ analytics.initAnalytics();
window.addEventListener('beforeunload', async (event) => { window.addEventListener('beforeunload', async (event) => {
if (!flashState.isFlashing() || popupExists) { if (!flashState.isFlashing() || popupExists) {
analytics.logEvent('Close application', {
isFlashing: flashState.isFlashing(),
});
return; return;
} }
@ -184,8 +171,6 @@ window.addEventListener('beforeunload', async (event) => {
// Don't open any more popups // Don't open any more popups
popupExists = true; popupExists = true;
analytics.logEvent('Close attempt while flashing');
try { try {
const confirmed = await osDialog.showWarning({ const confirmed = await osDialog.showWarning({
confirmationLabel: i18next.t('yesExit'), confirmationLabel: i18next.t('yesExit'),
@ -194,19 +179,11 @@ window.addEventListener('beforeunload', async (event) => {
description: messages.warning.exitWhileFlashing(), description: messages.warning.exitWhileFlashing(),
}); });
if (confirmed) { if (confirmed) {
analytics.logEvent('Close confirmed while flashing', {
flashInstanceUuid: flashState.getFlashUuid(),
});
// This circumvents the 'beforeunload' event unlike // This circumvents the 'beforeunload' event unlike
// remote.app.quit() which does not. // remote.app.quit() which does not.
remote.process.exit(EXIT_CODES.SUCCESS); remote.process.exit(EXIT_CODES.SUCCESS);
} }
analytics.logEvent('Close rejected while flashing', {
applicationSessionUuid,
flashingWorkflowUuid,
});
popupExists = false; popupExists = false;
} catch (error: any) { } catch (error: any) {
exceptionReporter.report(error); exceptionReporter.report(error);

View File

@ -36,7 +36,7 @@ import prettyBytes from 'pretty-bytes';
import { getDrives, hasAvailableDrives } from '../../models/available-drives'; import { getDrives, hasAvailableDrives } from '../../models/available-drives';
import { getImage, isDriveSelected } from '../../models/selection-state'; import { getImage, isDriveSelected } from '../../models/selection-state';
import { store } from '../../models/store'; import { store } from '../../models/store';
import { logEvent, logException } from '../../modules/analytics'; import { logException } from '../../modules/analytics';
import { open as openExternal } from '../../os/open-external/services/open-external'; import { open as openExternal } from '../../os/open-external/services/open-external';
import type { GenericTableProps } from '../../styled-components'; import type { GenericTableProps } from '../../styled-components';
import { Alert, Modal, Table } from '../../styled-components'; import { Alert, Modal, Table } from '../../styled-components';
@ -355,9 +355,6 @@ export class DriveSelector extends React.Component<
private installMissingDrivers(drive: DriverlessDrive) { private installMissingDrivers(drive: DriverlessDrive) {
if (drive.link) { if (drive.link) {
logEvent('Open driver link modal', {
url: drive.link,
});
this.setState({ missingDriversModal: { drive } }); this.setState({ missingDriversModal: { drive } });
} }
} }

View File

@ -22,7 +22,6 @@ import * as flashState from '../../models/flash-state';
import * as selectionState from '../../models/selection-state'; import * as selectionState from '../../models/selection-state';
import * as settings from '../../models/settings'; import * as settings from '../../models/settings';
import { Actions, store } from '../../models/store'; import { Actions, store } from '../../models/store';
import * as analytics from '../../modules/analytics';
import { FlashAnother } from '../flash-another/flash-another'; import { FlashAnother } from '../flash-another/flash-another';
import type { FlashError } from '../flash-results/flash-results'; import type { FlashError } from '../flash-results/flash-results';
import { FlashResults } from '../flash-results/flash-results'; import { FlashResults } from '../flash-results/flash-results';
@ -30,7 +29,6 @@ import { SafeWebview } from '../safe-webview/safe-webview';
function restart(goToMain: () => void) { function restart(goToMain: () => void) {
selectionState.deselectAllDrives(); selectionState.deselectAllDrives();
analytics.logEvent('Restart');
// Reset the flashing workflow uuid // Reset the flashing workflow uuid
store.dispatch({ store.dispatch({

View File

@ -21,7 +21,6 @@ import * as React from 'react';
import * as packageJSON from '../../../../../package.json'; import * as packageJSON from '../../../../../package.json';
import * as settings from '../../models/settings'; import * as settings from '../../models/settings';
import * as analytics from '../../modules/analytics';
/** /**
* @summary Electron session identifier * @summary Electron session identifier
@ -196,10 +195,6 @@ export class SafeWebview extends React.PureComponent<
// only care about this event if it's a request for the main frame // only care about this event if it's a request for the main frame
if (event.resourceType === 'mainFrame') { if (event.resourceType === 'mainFrame') {
const HTTP_OK = 200; const HTTP_OK = 200;
const { webContents, ...webviewEvent } = event;
analytics.logEvent('SafeWebview loaded', {
...webviewEvent,
});
this.setState({ this.setState({
shouldShow: event.statusCode === HTTP_OK, shouldShow: event.statusCode === HTTP_OK,
}); });

View File

@ -21,7 +21,6 @@ import { Box, Checkbox, Flex, Txt } from 'rendition';
import { version, packageType } from '../../../../../package.json'; import { version, packageType } from '../../../../../package.json';
import * as settings from '../../models/settings'; import * as settings from '../../models/settings';
import * as analytics from '../../modules/analytics';
import { open as openExternal } from '../../os/open-external/services/open-external'; import { open as openExternal } from '../../os/open-external/services/open-external';
import { Modal } from '../../styled-components'; import { Modal } from '../../styled-components';
import * as i18next from 'i18next'; import * as i18next from 'i18next';
@ -89,7 +88,6 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
const toggleSetting = async (setting: string) => { const toggleSetting = async (setting: string) => {
const value = currentSettings[setting]; const value = currentSettings[setting];
analytics.logEvent('Toggle setting', { setting, value });
await settings.set(setting, !value); await settings.set(setting, !value);
setCurrentSettings({ setCurrentSettings({
...currentSettings, ...currentSettings,

View File

@ -392,10 +392,6 @@ export class SourceSelector extends React.Component<
} }
private reselectSource() { private reselectSource() {
analytics.logEvent('Reselect image', {
previousImage: selectionState.getImage(),
});
selectionState.deselectImage(); selectionState.deselectImage();
this.props.hideAnalyticsAlert(); this.props.hideAnalyticsAlert();
} }
@ -426,7 +422,6 @@ export class SourceSelector extends React.Component<
} }
if (supportedFormats.looksLikeWindowsImage(selected)) { if (supportedFormats.looksLikeWindowsImage(selected)) {
analytics.logEvent('Possibly Windows image', { image: selected });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.looksLikeWindowsImage(), message: messages.warning.looksLikeWindowsImage(),
@ -450,7 +445,6 @@ export class SourceSelector extends React.Component<
metadata = await requestMetadata({ selected, SourceType, auth }); metadata = await requestMetadata({ selected, SourceType, auth });
if (!metadata?.hasMBR && this.state.warning === null) { if (!metadata?.hasMBR && this.state.warning === null) {
analytics.logEvent('Missing partition table', { metadata });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.missingPartitionTable(), message: messages.warning.missingPartitionTable(),
@ -468,7 +462,6 @@ export class SourceSelector extends React.Component<
} }
} else { } else {
if (selected.partitionTableType === null) { if (selected.partitionTableType === null) {
analytics.logEvent('Missing partition table', { selected });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.driveMissingPartitionTable(), message: messages.warning.driveMissingPartitionTable(),
@ -490,15 +483,6 @@ export class SourceSelector extends React.Component<
metadata.auth = auth; metadata.auth = auth;
metadata.SourceType = SourceType; metadata.SourceType = SourceType;
selectionState.selectSource(metadata); selectionState.selectSource(metadata);
analytics.logEvent('Select image', {
// An easy way so we can quickly identify if we're making use of
// certain features without printing pages of text to DevTools.
image: {
...metadata,
logo: Boolean(metadata.logo),
blockMap: Boolean(metadata.blockMap),
},
});
} }
})(), })(),
}; };
@ -519,11 +503,9 @@ export class SourceSelector extends React.Component<
analytics.logException(error); analytics.logException(error);
return; return;
} }
analytics.logEvent(title, { path: sourcePath });
} }
private async openImageSelector() { private async openImageSelector() {
analytics.logEvent('Open image selector');
this.setState({ imageSelectorOpen: true }); this.setState({ imageSelectorOpen: true });
try { try {
@ -531,7 +513,6 @@ export class SourceSelector extends React.Component<
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imagePath) { if (!imagePath) {
analytics.logEvent('Image selector closed');
return; return;
} }
await this.selectSource(imagePath, 'File').promise; await this.selectSource(imagePath, 'File').promise;
@ -550,16 +531,12 @@ export class SourceSelector extends React.Component<
} }
private openURLSelector() { private openURLSelector() {
analytics.logEvent('Open image URL selector');
this.setState({ this.setState({
showURLSelector: true, showURLSelector: true,
}); });
} }
private openDriveSelector() { private openDriveSelector() {
analytics.logEvent('Open drive selector');
this.setState({ this.setState({
showDriveSelector: true, showDriveSelector: true,
}); });
@ -576,10 +553,6 @@ export class SourceSelector extends React.Component<
} }
private showSelectedImageDetails() { private showSelectedImageDetails() {
analytics.logEvent('Show selected image tooltip', {
imagePath: selectionState.getImage()?.path,
});
this.setState({ this.setState({
showImageDetails: true, showImageDetails: true,
}); });
@ -759,9 +732,7 @@ export class SourceSelector extends React.Component<
done={async (imageURL: string, auth?: Authentication) => { done={async (imageURL: string, auth?: Authentication) => {
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imageURL) { if (imageURL) {
analytics.logEvent('URL selector closed');
} else {
let promise; let promise;
({ promise, cancel: cancelURLSelection } = this.selectSource( ({ promise, cancel: cancelURLSelection } = this.selectSource(
imageURL, imageURL,

View File

@ -20,7 +20,6 @@ import { Flex, Txt } from 'rendition';
import type { DriveSelectorProps } from '../drive-selector/drive-selector'; import type { DriveSelectorProps } from '../drive-selector/drive-selector';
import { DriveSelector } from '../drive-selector/drive-selector'; import { DriveSelector } from '../drive-selector/drive-selector';
import { import {
isDriveSelected,
getImage, getImage,
getSelectedDrives, getSelectedDrives,
deselectDrive, deselectDrive,
@ -28,7 +27,6 @@ import {
deselectAllDrives, deselectAllDrives,
} from '../../models/selection-state'; } from '../../models/selection-state';
import { observe } from '../../models/store'; import { observe } from '../../models/store';
import * as analytics from '../../modules/analytics';
import { TargetSelectorButton } from './target-selector-button'; import { TargetSelectorButton } from './target-selector-button';
import TgtSvg from '../../../assets/tgt.svg'; import TgtSvg from '../../../assets/tgt.svg';
@ -77,21 +75,10 @@ export const selectAllTargets = (modalTargets: DrivelistDrive[]) => {
); );
// deselect drives // deselect drives
deselected.forEach((drive) => { deselected.forEach((drive) => {
analytics.logEvent('Toggle drive', {
drive,
previouslySelected: true,
});
deselectDrive(drive.device); deselectDrive(drive.device);
}); });
// select drives // select drives
modalTargets.forEach((drive) => { modalTargets.forEach((drive) => {
// Don't send events for drives that were already selected
if (!isDriveSelected(drive.device)) {
analytics.logEvent('Toggle drive', {
drive,
previouslySelected: false,
});
}
selectDrive(drive.device); selectDrive(drive.device);
}); });
}; };
@ -142,7 +129,6 @@ export const TargetSelector = ({
hideAnalyticsAlert(); hideAnalyticsAlert();
}} }}
reselectDrive={() => { reselectDrive={() => {
analytics.logEvent('Reselect drive');
setShowTargetSelectorModal(true); setShowTargetSelectorModal(true);
}} }}
flashing={flashing} flashing={flashing}

View File

@ -133,8 +133,7 @@ const translation = {
flashCompleted: 'Flash Completed!', flashCompleted: 'Flash Completed!',
}, },
settings: { settings: {
errorReporting: errorReporting: 'Anonymously report errors to balena.io',
'Anonymously report errors and usage statistics to balena.io',
autoUpdate: 'Auto-updates enabled', autoUpdate: 'Auto-updates enabled',
settings: 'Settings', settings: 'Settings',
systemInformation: 'System Information', systemInformation: 'System Information',

View File

@ -15,12 +15,8 @@
*/ */
import { findLastIndex, once } from 'lodash'; import { findLastIndex, once } from 'lodash';
import type { Client } from 'analytics-client';
import { createClient, createNoopClient } from 'analytics-client';
import * as SentryRenderer from '@sentry/electron/renderer'; import * as SentryRenderer from '@sentry/electron/renderer';
import * as settings from '../models/settings'; import * as settings from '../models/settings';
import { store } from '../models/store';
import { version } from '../../../../package.json';
type AnalyticsPayload = _.Dictionary<any>; type AnalyticsPayload = _.Dictionary<any>;
@ -115,7 +111,6 @@ export const anonymizeAnalyticsPayload = (
return data; return data;
}; };
let analyticsClient: Client;
/** /**
* @summary Init analytics configurations * @summary Init analytics configurations
*/ */
@ -127,95 +122,8 @@ export const initAnalytics = once(() => {
beforeSend: anonymizeSentryData, beforeSend: anonymizeSentryData,
debug: process.env.ETCHER_SENTRY_DEBUG === 'true', debug: process.env.ETCHER_SENTRY_DEBUG === 'true',
}); });
const projectName =
settings.getSync('analyticsAmplitudeToken') || process.env.AMPLITUDE_TOKEN;
const clientConfig = {
projectName,
endpoint: 'data.balena-cloud.com',
componentName: 'etcher',
componentVersion: version,
};
analyticsClient = projectName
? createClient(clientConfig)
: createNoopClient();
}); });
const getCircularReplacer = () => {
const seen = new WeakSet();
return (_key: any, value: any) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
function flattenObject(obj: any) {
const toReturn: AnalyticsPayload = {};
for (const i in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, i)) {
continue;
}
if (Array.isArray(obj[i])) {
toReturn[i] = obj[i];
continue;
}
if (typeof obj[i] === 'object' && obj[i] !== null) {
const flatObject = flattenObject(obj[i]);
for (const x in flatObject) {
if (!Object.prototype.hasOwnProperty.call(flatObject, x)) {
continue;
}
toReturn[i.toLowerCase() + '.' + x.toLowerCase()] = flatObject[x];
}
} else {
toReturn[i] = obj[i];
}
}
return toReturn;
}
function formatEvent(data: any): AnalyticsPayload {
const event = JSON.parse(JSON.stringify(data, getCircularReplacer()));
return anonymizeAnalyticsPayload(flattenObject(event));
}
function reportAnalytics(message: string, data: AnalyticsPayload = {}) {
const { applicationSessionUuid, flashingWorkflowUuid } = store
.getState()
.toJS();
const event = formatEvent({
...data,
applicationSessionUuid,
flashingWorkflowUuid,
});
analyticsClient.track(message, event);
}
/**
* @summary Log an event
*
* @description
* This function sends the debug message to product analytics services.
*/
export async function logEvent(message: string, data: AnalyticsPayload = {}) {
const shouldReportAnalytics = await settings.get('errorReporting');
if (shouldReportAnalytics) {
initAnalytics();
reportAnalytics(message, data);
}
}
/** /**
* @summary Log an exception * @summary Log an exception
* *

View File

@ -20,44 +20,11 @@ import type { Dictionary } from 'lodash';
import * as errors from '../../../shared/errors'; import * as errors from '../../../shared/errors';
import type { SourceMetadata } from '../../../shared/typings/source-selector'; import type { SourceMetadata } from '../../../shared/typings/source-selector';
import * as flashState from '../models/flash-state'; import * as flashState from '../models/flash-state';
import * as selectionState from '../models/selection-state';
import * as settings from '../models/settings'; import * as settings from '../models/settings';
import * as analytics from '../modules/analytics';
import * as windowProgress from '../os/window-progress'; import * as windowProgress from '../os/window-progress';
import { spawnChildAndConnect } from './api'; import { spawnChildAndConnect } from './api';
/**
* @summary Handle a flash error and log it to analytics
*/
function handleErrorLogging(
error: Error & { code: string },
analyticsData: any,
) {
const eventData = {
...analyticsData,
flashInstanceUuid: flashState.getFlashUuid(),
};
if (error.code === 'EVALIDATION') {
analytics.logEvent('Validation error', eventData);
} else if (error.code === 'EUNPLUGGED') {
analytics.logEvent('Drive unplugged', eventData);
} else if (error.code === 'EIO') {
analytics.logEvent('Input/output error', eventData);
} else if (error.code === 'ENOSPC') {
analytics.logEvent('Out of space', eventData);
} else if (error.code === 'ECHILDDIED') {
analytics.logEvent('Child died unexpectedly', eventData);
} else {
analytics.logEvent('Flash error', {
...eventData,
error: errors.toJSON(error),
});
}
}
let cancelEmitter: (type: string) => void | undefined; let cancelEmitter: (type: string) => void | undefined;
interface FlashResults { interface FlashResults {
skip?: boolean; skip?: boolean;
cancelled?: boolean; cancelled?: boolean;
@ -88,14 +55,6 @@ async function performWrite(
const flashResults: FlashResults = {}; const flashResults: FlashResults = {};
const analyticsData = {
image,
drives,
driveCount: drives.length,
uuid: flashState.getFlashUuid(),
flashInstanceUuid: flashState.getFlashUuid(),
};
const onFail = ({ device, error }: { device: any; error: any }) => { const onFail = ({ device, error }: { device: any; error: any }) => {
console.log('fail event'); console.log('fail event');
console.log(device); console.log(device);
@ -103,7 +62,6 @@ async function performWrite(
if (device.devicePath) { if (device.devicePath) {
flashState.addFailedDeviceError({ device, error }); flashState.addFailedDeviceError({ device, error });
} }
handleErrorLogging(error, analyticsData);
finish(); finish();
}; };
@ -195,17 +153,6 @@ export async function flash(
drives.map((d) => d.devicePath).filter((p) => p != null) as string[], drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
); );
const analyticsData = {
image,
drives,
driveCount: drives.length,
uuid: flashState.getFlashUuid(),
status: 'started',
flashInstanceUuid: flashState.getFlashUuid(),
};
analytics.logEvent('Flash', analyticsData);
// start api and call the flasher // start api and call the flasher
try { try {
const result = await write(image, drives, flashState.setProgressState); const result = await write(image, drives, flashState.setProgressState);
@ -220,39 +167,10 @@ export async function flash(
windowProgress.clear(); windowProgress.clear();
const { results = {} } = flashState.getFlashResults();
const eventData = {
...analyticsData,
errors: results.errors,
devices: results.devices,
status: 'failed',
error,
};
analytics.logEvent('Write failed', eventData);
throw error; throw error;
} }
windowProgress.clear(); windowProgress.clear();
if (flashState.wasLastFlashCancelled()) {
const eventData = {
...analyticsData,
status: 'cancel',
};
analytics.logEvent('Elevation cancelled', eventData);
} else {
const { results = {} } = flashState.getFlashResults();
const eventData = {
...analyticsData,
errors: results.errors,
devices: results.devices,
status: 'finished',
bytesWritten: results.bytesWritten,
sourceMetadata: results.sourceMetadata,
};
analytics.logEvent('Done', eventData);
}
} }
/** /**
@ -261,16 +179,6 @@ export async function flash(
*/ */
export async function cancel(type: string) { export async function cancel(type: string) {
const status = type.toLowerCase(); const status = type.toLowerCase();
const drives = selectionState.getSelectedDevices();
const analyticsData = {
image: selectionState.getImage()?.path,
drives,
driveCount: drives.length,
uuid: flashState.getFlashUuid(),
flashInstanceUuid: flashState.getFlashUuid(),
status,
};
analytics.logEvent('Cancel', analyticsData);
if (cancelEmitter) { if (cancelEmitter) {
cancelEmitter(status); cancelEmitter(status);

View File

@ -16,7 +16,6 @@
import * as electron from 'electron'; import * as electron from 'electron';
import * as settings from '../../../models/settings'; import * as settings from '../../../models/settings';
import { logEvent } from '../../../modules/analytics';
/** /**
* @summary Open an external resource * @summary Open an external resource
@ -27,8 +26,6 @@ export async function open(url: string) {
return; return;
} }
logEvent('Open external link', { url });
if (url) { if (url) {
electron.shell.openExternal(url); electron.shell.openExternal(url);
} }

View File

@ -198,9 +198,7 @@ export class FlashStep extends React.PureComponent<
private handleFlashErrorResponse(shouldRetry: boolean) { private handleFlashErrorResponse(shouldRetry: boolean) {
this.setState({ errorMessage: '' }); this.setState({ errorMessage: '' });
flashState.resetState(); flashState.resetState();
if (shouldRetry) { if (!shouldRetry) {
analytics.logEvent('Restart after failure');
} else {
selection.clear(); selection.clear();
} }
} }

161
npm-shrinkwrap.json generated
View File

@ -13,7 +13,6 @@
"@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free": "^6.5.2",
"@ronomon/direct-io": "^3.0.1", "@ronomon/direct-io": "^3.0.1",
"@sentry/electron": "^4.24.0", "@sentry/electron": "^4.24.0",
"analytics-client": "^2.0.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"debug": "4.3.4", "debug": "4.3.4",
"drivelist": "^12.0.2", "drivelist": "^12.0.2",
@ -101,148 +100,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/@amplitude/analytics-browser": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-1.13.4.tgz",
"integrity": "sha512-FyNlrhLZUFI+lDHxbDGMoZED80iARS6VjTP+zZcfk0GWI7+lt0Meu5jD7G8xms21Ioxrg1+Lf1Q4v3CC6XN2Nw==",
"dependencies": {
"@amplitude/analytics-client-common": "^1.2.2",
"@amplitude/analytics-core": "^1.2.5",
"@amplitude/analytics-types": "^1.3.4",
"@amplitude/plugin-page-view-tracking-browser": "^1.0.12",
"@amplitude/plugin-web-attribution-browser": "^1.0.12",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/analytics-client-common": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-1.2.2.tgz",
"integrity": "sha512-vwGgVXl9FKEi99OzjqqhX8RrulQQ55aAllhgbdyxpyyAQ5NbbZOPdrxp1ow0oliCVvbSDgUYOAeAwTChIgnStA==",
"dependencies": {
"@amplitude/analytics-connector": "^1.5.0",
"@amplitude/analytics-core": "^1.2.5",
"@amplitude/analytics-types": "^1.3.4",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/analytics-connector": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
"integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
},
"node_modules/@amplitude/analytics-core": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-1.2.5.tgz",
"integrity": "sha512-V7CVlHVN+1diKiOpdp2bCPZ0mbS4CmUYF+v+eXDwVfJL3M/t3sVcT1apXnmVYGYi14cGu9hQOD11rD6qKbUOsw==",
"dependencies": {
"@amplitude/analytics-types": "^1.3.4",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/analytics-types": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-1.3.4.tgz",
"integrity": "sha512-tR70gzqFkEzX9QpxvWYMfLCledT7vMhgd3d4/bkp3nnGXTOORaVUOCcSgOyxyuFdSx84T61aP/eZPKIcZcaP+A=="
},
"node_modules/@amplitude/marketing-analytics-browser": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@amplitude/marketing-analytics-browser/-/marketing-analytics-browser-0.2.9.tgz",
"integrity": "sha512-xOx5tCqV2A1r9+pYP7PPDfBqzQpKvhmIPR/CF4blpo7ZTYqCIWLg7QG1pN3uWFQuq5d4MWWz5QH+TUvKXifsHA==",
"dependencies": {
"@amplitude/analytics-browser": "^1.6.3",
"@amplitude/analytics-client-common": "^0.4.1",
"@amplitude/analytics-core": "^0.10.1",
"@amplitude/analytics-types": "^0.13.0",
"@amplitude/plugin-page-view-tracking-browser": "^0.4.9",
"@amplitude/plugin-web-attribution-browser": "^0.4.2",
"tslib": "^2.3.1"
}
},
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-client-common": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-0.4.1.tgz",
"integrity": "sha512-cwKHZVNfBt8kNmhXuSZ/BkEwdOSsCVQDXKgQysb4sp5AYkwqYV/bVd7yvWxffrrkK4N2PsYLnvODeTwANH/4UQ==",
"dependencies": {
"@amplitude/analytics-connector": "^1.4.5",
"@amplitude/analytics-core": "^0.10.1",
"@amplitude/analytics-types": "^0.13.0",
"tslib": "^2.3.1"
}
},
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-core": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-0.10.1.tgz",
"integrity": "sha512-XYJavGCnf0Y28chswEGNjSM2MqCMafsQvHpSgRD1JYTNrv+j/CTkj7P3TwyxriaXCTSfFcWTDPKRHd3SruU3Aw==",
"dependencies": {
"@amplitude/analytics-types": "^0.13.0",
"tslib": "^2.3.1"
}
},
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-types": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-0.13.0.tgz",
"integrity": "sha512-yti2SytTIh0R5QknuKO1RMgB+r8CGjauhPfFaaYiTm4keAvqYxDdG9ULarPDoOx2VPSfB5Za779Kt1Muc+34PA=="
},
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/plugin-page-view-tracking-browser": {
"version": "0.4.9",
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-0.4.9.tgz",
"integrity": "sha512-dPeMativPA+UitDQbRv/FtBfAZtddbu9tgezbtSR90yubK+bS3oYWXETxR6X7P73qNBIZGUpH9i2nWVY6EXVQQ==",
"dependencies": {
"@amplitude/analytics-client-common": "^0.4.1",
"@amplitude/analytics-types": "^0.13.0",
"tslib": "^2.3.1"
}
},
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/plugin-web-attribution-browser": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-0.4.2.tgz",
"integrity": "sha512-9N1Qe4fTBmS7uDcgCA5PbIryJCf2V+BUhwP8n6BSwH1XOujx/sK0UQRgPlGRFSmmm2eH7QG9RKJkQj6VB3/zrQ==",
"dependencies": {
"@amplitude/analytics-client-common": "^0.4.1",
"@amplitude/analytics-types": "^0.13.0",
"tslib": "^2.3.1"
}
},
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-1.0.12.tgz",
"integrity": "sha512-zbFbBi/+QrsWm1rPcFIAcxQ3t7uZwTuHCnHHzyZU/nQB/gyOgRh4U4uqt5DekLf4Tp3V2a+hmhmTE0KfRFuXLw==",
"dependencies": {
"@amplitude/analytics-client-common": "^1.2.2",
"@amplitude/analytics-types": "^1.3.4",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/plugin-web-attribution-browser": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-1.0.12.tgz",
"integrity": "sha512-zoIqgIT34xbE3V2TyQYoRVCs7j3biY/AXkGzYphiriuUvKmQtRjBoP2o08nZHYFzVSOSq4Ixk7OlelilW10Krg==",
"dependencies": {
"@amplitude/analytics-client-common": "^1.2.2",
"@amplitude/analytics-core": "^1.2.5",
"@amplitude/analytics-types": "^1.3.4",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/ua-parser-js": {
"version": "0.7.33",
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@ -7026,16 +6883,6 @@
"ajv": "^6.9.1" "ajv": "^6.9.1"
} }
}, },
"node_modules/analytics-client": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/analytics-client/-/analytics-client-2.0.2.tgz",
"integrity": "sha512-03Qo4r86wzw7NV0voG7xNwZjbba7h0wC6A8Dd85Slgt1bMg0jWKBXS9DnWIMiUMT4vfm8HtLaBDJyJIKlu1glQ==",
"dependencies": {
"@amplitude/analytics-browser": "^1.5.4",
"@amplitude/marketing-analytics-browser": "^0.2.4",
"js-cookie": "^3.0.1"
}
},
"node_modules/ansi-align": { "node_modules/ansi-align": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
@ -16848,14 +16695,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@ -34,7 +34,6 @@
"@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free": "^6.5.2",
"@ronomon/direct-io": "^3.0.1", "@ronomon/direct-io": "^3.0.1",
"@sentry/electron": "^4.24.0", "@sentry/electron": "^4.24.0",
"analytics-client": "^2.0.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"debug": "4.3.4", "debug": "4.3.4",
"drivelist": "^12.0.2", "drivelist": "^12.0.2",

View File

@ -63,9 +63,6 @@ const rules: Required<ModuleOptions>['rules'] = [
const injectAnalyticsToken = new DefinePlugin({ const injectAnalyticsToken = new DefinePlugin({
'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''), 'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''),
'process.env.AMPLITUDE_TOKEN': JSON.stringify(
process.env.AMPLITUDE_TOKEN || '',
),
}); });
export const rendererConfig: Configuration = { export const rendererConfig: Configuration = {