mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-09 20:36:31 +00:00
patch: remove analytics
This commit is contained in:
parent
fdd082b9cd
commit
aa6d526fea
2
.github/actions/test/action.yml
vendored
2
.github/actions/test/action.yml
vendored
@ -12,7 +12,7 @@ inputs:
|
||||
# --- custom environment
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '20.10'
|
||||
default: '20.19'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: 'true'
|
||||
|
@ -1,10 +1,8 @@
|
||||
Maintaining Etcher
|
||||
==================
|
||||
# Maintaining Etcher
|
||||
|
||||
This document is meant to serve as a guide for maintainers to perform common tasks.
|
||||
|
||||
Releasing
|
||||
---------
|
||||
## Releasing
|
||||
|
||||
### Release Types
|
||||
|
||||
@ -13,16 +11,15 @@ Releasing
|
||||
- **release**: Full releases
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Preparation
|
||||
|
||||
- [Prepare the new version](#preparing-a-new-version)
|
||||
- [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
|
||||
- [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
|
||||
- Upload build artifacts to GitHub release draft
|
||||
- Upload build artifacts to GitHub release draft
|
||||
|
||||
#### Testing
|
||||
|
||||
@ -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)
|
||||
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
||||
- [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:
|
||||
- [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
|
||||
- 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
|
||||
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
|
||||
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
|
||||
```
|
||||
|
||||
#### Linux
|
||||
@ -71,7 +67,6 @@ npm run make
|
||||
|
||||
Our CI will appropriately sign artifacts for macOS and some Windows targets.
|
||||
|
||||
|
||||
### Uploading packages to Cloudfront
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Submitting binaries to Symantec
|
||||
|
||||
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
|
||||
|
@ -1,22 +1,19 @@
|
||||
Manual Testing
|
||||
==============
|
||||
# Manual Testing
|
||||
|
||||
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.
|
||||
|
||||
Image Selection
|
||||
---------------
|
||||
## Image Selection
|
||||
|
||||
- [ ] Cancel image selection dialog
|
||||
- [ ] Select an unbootable image (without a partition table), and expect a
|
||||
sensible warning
|
||||
sensible warning
|
||||
- [ ] Attempt to select a ZIP archive with more than one image
|
||||
- [ ] Attempt to select a tar archive (with any compression method)
|
||||
- [ ] Change image selection
|
||||
- [ ] Select a Windows image, and expect a sensible warning
|
||||
|
||||
Drive Selection
|
||||
---------------
|
||||
## Drive Selection
|
||||
|
||||
- [ ] Open the drive selection modal
|
||||
- [ ] Switch drive selection
|
||||
@ -25,16 +22,15 @@ Drive Selection
|
||||
- [ ] Insert a locked SD Card and expect a warning
|
||||
- [ ] Insert a too small drive and expect a warning
|
||||
- [ ] Put an image into a drive and attempt to flash the image to the drive
|
||||
that contains it
|
||||
that contains it
|
||||
- [ ] Attempt to flash a compressed image (for which we can get the
|
||||
uncompressed size) into a drive that is big enough to hold the compressed
|
||||
image, but not big enough to hold the uncompressed version
|
||||
uncompressed size) into a drive that is big enough to hold the compressed
|
||||
image, but not big enough to hold the uncompressed version
|
||||
- [ ] Enable "Unsafe Mode" and attempt to select a system drive
|
||||
- [ ] 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:
|
||||
|
||||
@ -51,18 +47,17 @@ Run the following tests with and without validation enabled:
|
||||
- [ ] Flash an archive image containing a blockmap file
|
||||
- [ ] Flash an archive image containing a manifest metadata file
|
||||
|
||||
Flashing Process
|
||||
----------------
|
||||
## Flashing Process
|
||||
|
||||
- [ ] Unplug the drive during flash or validation
|
||||
- [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
|
||||
- [ ] Start flashing an image, try to close Etcher, cancel the application
|
||||
close warning dialog, and check that Etcher continues to flash the image
|
||||
close warning dialog, and check that Etcher continues to flash the image
|
||||
|
||||
### Child Writer
|
||||
|
||||
- [ ] Kill the child writer process (i.e. with `SIGINT` or `SIGKILL`), and
|
||||
check that the UI reacts appropriately
|
||||
check that the UI reacts appropriately
|
||||
- [ ] Close the application while flashing using the window manager close icon
|
||||
- [ ] Close the application while flashing using the OS keyboard shortcut
|
||||
- [ ] Close the application from the terminal using Ctrl-C while flashing
|
||||
@ -72,11 +67,10 @@ 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
|
||||
permissions to see the elevated child writer process.
|
||||
|
||||
GUI
|
||||
----
|
||||
## GUI
|
||||
|
||||
- [ ] Close application from the terminal using Ctrl-C while the application is
|
||||
idle
|
||||
idle
|
||||
- [ ] Click footer links that take you to an external website
|
||||
- [ ] Attempt to change image or drive selection while flashing
|
||||
- [ ] Go to the settings page while flashing and come back
|
||||
@ -85,31 +79,20 @@ GUI
|
||||
- [ ] Minimize the application
|
||||
- [ ] Start the application given no internet connection
|
||||
|
||||
Success Banner
|
||||
--------------
|
||||
## Success Banner
|
||||
|
||||
- [ ] Click an external link on the success banner (with and without internet
|
||||
connection)
|
||||
connection)
|
||||
|
||||
Elevation Prompt
|
||||
----------------
|
||||
## Elevation Prompt
|
||||
|
||||
- [ ] Flash an image as `root`/administrator
|
||||
- [ ] Reject elevation prompt
|
||||
- [ ] Put incorrect elevation prompt password
|
||||
- [ ] Unplug the drive during elevation
|
||||
|
||||
Unmounting
|
||||
----------
|
||||
## Unmounting
|
||||
|
||||
- [ ] Disable unmounting and flash an image
|
||||
- [ ] Flash an image with a file system that is readable by the host OS, and
|
||||
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**
|
||||
check that is unmounted correctly
|
||||
|
@ -64,9 +64,6 @@ store.dispatch({
|
||||
data: uuidV4(),
|
||||
});
|
||||
|
||||
const applicationSessionUuid = store.getState().toJS().applicationSessionUuid;
|
||||
const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid;
|
||||
|
||||
console.log(outdent`
|
||||
${outdent}
|
||||
_____ _ _
|
||||
@ -82,13 +79,6 @@ console.log(outdent`
|
||||
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 });
|
||||
|
||||
function pluralize(word: string, quantity: number) {
|
||||
@ -172,9 +162,6 @@ analytics.initAnalytics();
|
||||
|
||||
window.addEventListener('beforeunload', async (event) => {
|
||||
if (!flashState.isFlashing() || popupExists) {
|
||||
analytics.logEvent('Close application', {
|
||||
isFlashing: flashState.isFlashing(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -184,8 +171,6 @@ window.addEventListener('beforeunload', async (event) => {
|
||||
// Don't open any more popups
|
||||
popupExists = true;
|
||||
|
||||
analytics.logEvent('Close attempt while flashing');
|
||||
|
||||
try {
|
||||
const confirmed = await osDialog.showWarning({
|
||||
confirmationLabel: i18next.t('yesExit'),
|
||||
@ -194,19 +179,11 @@ window.addEventListener('beforeunload', async (event) => {
|
||||
description: messages.warning.exitWhileFlashing(),
|
||||
});
|
||||
if (confirmed) {
|
||||
analytics.logEvent('Close confirmed while flashing', {
|
||||
flashInstanceUuid: flashState.getFlashUuid(),
|
||||
});
|
||||
|
||||
// This circumvents the 'beforeunload' event unlike
|
||||
// remote.app.quit() which does not.
|
||||
remote.process.exit(EXIT_CODES.SUCCESS);
|
||||
}
|
||||
|
||||
analytics.logEvent('Close rejected while flashing', {
|
||||
applicationSessionUuid,
|
||||
flashingWorkflowUuid,
|
||||
});
|
||||
popupExists = false;
|
||||
} catch (error: any) {
|
||||
exceptionReporter.report(error);
|
||||
|
@ -36,7 +36,7 @@ import prettyBytes from 'pretty-bytes';
|
||||
import { getDrives, hasAvailableDrives } from '../../models/available-drives';
|
||||
import { getImage, isDriveSelected } from '../../models/selection-state';
|
||||
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 type { GenericTableProps } from '../../styled-components';
|
||||
import { Alert, Modal, Table } from '../../styled-components';
|
||||
@ -355,9 +355,6 @@ export class DriveSelector extends React.Component<
|
||||
|
||||
private installMissingDrivers(drive: DriverlessDrive) {
|
||||
if (drive.link) {
|
||||
logEvent('Open driver link modal', {
|
||||
url: drive.link,
|
||||
});
|
||||
this.setState({ missingDriversModal: { drive } });
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import * as flashState from '../../models/flash-state';
|
||||
import * as selectionState from '../../models/selection-state';
|
||||
import * as settings from '../../models/settings';
|
||||
import { Actions, store } from '../../models/store';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import { FlashAnother } from '../flash-another/flash-another';
|
||||
import type { FlashError } 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) {
|
||||
selectionState.deselectAllDrives();
|
||||
analytics.logEvent('Restart');
|
||||
|
||||
// Reset the flashing workflow uuid
|
||||
store.dispatch({
|
||||
|
@ -21,7 +21,6 @@ import * as React from 'react';
|
||||
|
||||
import * as packageJSON from '../../../../../package.json';
|
||||
import * as settings from '../../models/settings';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
|
||||
/**
|
||||
* @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
|
||||
if (event.resourceType === 'mainFrame') {
|
||||
const HTTP_OK = 200;
|
||||
const { webContents, ...webviewEvent } = event;
|
||||
analytics.logEvent('SafeWebview loaded', {
|
||||
...webviewEvent,
|
||||
});
|
||||
this.setState({
|
||||
shouldShow: event.statusCode === HTTP_OK,
|
||||
});
|
||||
|
@ -21,7 +21,6 @@ import { Box, Checkbox, Flex, Txt } from 'rendition';
|
||||
|
||||
import { version, packageType } from '../../../../../package.json';
|
||||
import * as settings from '../../models/settings';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||
import { Modal } from '../../styled-components';
|
||||
import * as i18next from 'i18next';
|
||||
@ -89,7 +88,6 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||
|
||||
const toggleSetting = async (setting: string) => {
|
||||
const value = currentSettings[setting];
|
||||
analytics.logEvent('Toggle setting', { setting, value });
|
||||
await settings.set(setting, !value);
|
||||
setCurrentSettings({
|
||||
...currentSettings,
|
||||
|
@ -392,10 +392,6 @@ export class SourceSelector extends React.Component<
|
||||
}
|
||||
|
||||
private reselectSource() {
|
||||
analytics.logEvent('Reselect image', {
|
||||
previousImage: selectionState.getImage(),
|
||||
});
|
||||
|
||||
selectionState.deselectImage();
|
||||
this.props.hideAnalyticsAlert();
|
||||
}
|
||||
@ -426,7 +422,6 @@ export class SourceSelector extends React.Component<
|
||||
}
|
||||
|
||||
if (supportedFormats.looksLikeWindowsImage(selected)) {
|
||||
analytics.logEvent('Possibly Windows image', { image: selected });
|
||||
this.setState({
|
||||
warning: {
|
||||
message: messages.warning.looksLikeWindowsImage(),
|
||||
@ -450,7 +445,6 @@ export class SourceSelector extends React.Component<
|
||||
metadata = await requestMetadata({ selected, SourceType, auth });
|
||||
|
||||
if (!metadata?.hasMBR && this.state.warning === null) {
|
||||
analytics.logEvent('Missing partition table', { metadata });
|
||||
this.setState({
|
||||
warning: {
|
||||
message: messages.warning.missingPartitionTable(),
|
||||
@ -468,7 +462,6 @@ export class SourceSelector extends React.Component<
|
||||
}
|
||||
} else {
|
||||
if (selected.partitionTableType === null) {
|
||||
analytics.logEvent('Missing partition table', { selected });
|
||||
this.setState({
|
||||
warning: {
|
||||
message: messages.warning.driveMissingPartitionTable(),
|
||||
@ -490,15 +483,6 @@ export class SourceSelector extends React.Component<
|
||||
metadata.auth = auth;
|
||||
metadata.SourceType = SourceType;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
analytics.logEvent(title, { path: sourcePath });
|
||||
}
|
||||
|
||||
private async openImageSelector() {
|
||||
analytics.logEvent('Open image selector');
|
||||
this.setState({ imageSelectorOpen: true });
|
||||
|
||||
try {
|
||||
@ -531,7 +513,6 @@ export class SourceSelector extends React.Component<
|
||||
// Avoid analytics and selection state changes
|
||||
// if no file was resolved from the dialog.
|
||||
if (!imagePath) {
|
||||
analytics.logEvent('Image selector closed');
|
||||
return;
|
||||
}
|
||||
await this.selectSource(imagePath, 'File').promise;
|
||||
@ -550,16 +531,12 @@ export class SourceSelector extends React.Component<
|
||||
}
|
||||
|
||||
private openURLSelector() {
|
||||
analytics.logEvent('Open image URL selector');
|
||||
|
||||
this.setState({
|
||||
showURLSelector: true,
|
||||
});
|
||||
}
|
||||
|
||||
private openDriveSelector() {
|
||||
analytics.logEvent('Open drive selector');
|
||||
|
||||
this.setState({
|
||||
showDriveSelector: true,
|
||||
});
|
||||
@ -576,10 +553,6 @@ export class SourceSelector extends React.Component<
|
||||
}
|
||||
|
||||
private showSelectedImageDetails() {
|
||||
analytics.logEvent('Show selected image tooltip', {
|
||||
imagePath: selectionState.getImage()?.path,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
showImageDetails: true,
|
||||
});
|
||||
@ -759,9 +732,7 @@ export class SourceSelector extends React.Component<
|
||||
done={async (imageURL: string, auth?: Authentication) => {
|
||||
// Avoid analytics and selection state changes
|
||||
// if no file was resolved from the dialog.
|
||||
if (!imageURL) {
|
||||
analytics.logEvent('URL selector closed');
|
||||
} else {
|
||||
if (imageURL) {
|
||||
let promise;
|
||||
({ promise, cancel: cancelURLSelection } = this.selectSource(
|
||||
imageURL,
|
||||
|
@ -20,7 +20,6 @@ import { Flex, Txt } from 'rendition';
|
||||
import type { DriveSelectorProps } from '../drive-selector/drive-selector';
|
||||
import { DriveSelector } from '../drive-selector/drive-selector';
|
||||
import {
|
||||
isDriveSelected,
|
||||
getImage,
|
||||
getSelectedDrives,
|
||||
deselectDrive,
|
||||
@ -28,7 +27,6 @@ import {
|
||||
deselectAllDrives,
|
||||
} from '../../models/selection-state';
|
||||
import { observe } from '../../models/store';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import { TargetSelectorButton } from './target-selector-button';
|
||||
|
||||
import TgtSvg from '../../../assets/tgt.svg';
|
||||
@ -77,21 +75,10 @@ export const selectAllTargets = (modalTargets: DrivelistDrive[]) => {
|
||||
);
|
||||
// deselect drives
|
||||
deselected.forEach((drive) => {
|
||||
analytics.logEvent('Toggle drive', {
|
||||
drive,
|
||||
previouslySelected: true,
|
||||
});
|
||||
deselectDrive(drive.device);
|
||||
});
|
||||
// select drives
|
||||
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);
|
||||
});
|
||||
};
|
||||
@ -142,7 +129,6 @@ export const TargetSelector = ({
|
||||
hideAnalyticsAlert();
|
||||
}}
|
||||
reselectDrive={() => {
|
||||
analytics.logEvent('Reselect drive');
|
||||
setShowTargetSelectorModal(true);
|
||||
}}
|
||||
flashing={flashing}
|
||||
|
@ -133,8 +133,7 @@ const translation = {
|
||||
flashCompleted: 'Flash Completed!',
|
||||
},
|
||||
settings: {
|
||||
errorReporting:
|
||||
'Anonymously report errors and usage statistics to balena.io',
|
||||
errorReporting: 'Anonymously report errors to balena.io',
|
||||
autoUpdate: 'Auto-updates enabled',
|
||||
settings: 'Settings',
|
||||
systemInformation: 'System Information',
|
||||
|
@ -15,12 +15,8 @@
|
||||
*/
|
||||
|
||||
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 settings from '../models/settings';
|
||||
import { store } from '../models/store';
|
||||
import { version } from '../../../../package.json';
|
||||
|
||||
type AnalyticsPayload = _.Dictionary<any>;
|
||||
|
||||
@ -115,7 +111,6 @@ export const anonymizeAnalyticsPayload = (
|
||||
return data;
|
||||
};
|
||||
|
||||
let analyticsClient: Client;
|
||||
/**
|
||||
* @summary Init analytics configurations
|
||||
*/
|
||||
@ -127,95 +122,8 @@ export const initAnalytics = once(() => {
|
||||
beforeSend: anonymizeSentryData,
|
||||
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
|
||||
*
|
||||
|
@ -20,44 +20,11 @@ import type { Dictionary } from 'lodash';
|
||||
import * as errors from '../../../shared/errors';
|
||||
import type { SourceMetadata } from '../../../shared/typings/source-selector';
|
||||
import * as flashState from '../models/flash-state';
|
||||
import * as selectionState from '../models/selection-state';
|
||||
import * as settings from '../models/settings';
|
||||
import * as analytics from '../modules/analytics';
|
||||
import * as windowProgress from '../os/window-progress';
|
||||
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;
|
||||
|
||||
interface FlashResults {
|
||||
skip?: boolean;
|
||||
cancelled?: boolean;
|
||||
@ -88,14 +55,6 @@ async function performWrite(
|
||||
|
||||
const flashResults: FlashResults = {};
|
||||
|
||||
const analyticsData = {
|
||||
image,
|
||||
drives,
|
||||
driveCount: drives.length,
|
||||
uuid: flashState.getFlashUuid(),
|
||||
flashInstanceUuid: flashState.getFlashUuid(),
|
||||
};
|
||||
|
||||
const onFail = ({ device, error }: { device: any; error: any }) => {
|
||||
console.log('fail event');
|
||||
console.log(device);
|
||||
@ -103,7 +62,6 @@ async function performWrite(
|
||||
if (device.devicePath) {
|
||||
flashState.addFailedDeviceError({ device, error });
|
||||
}
|
||||
handleErrorLogging(error, analyticsData);
|
||||
finish();
|
||||
};
|
||||
|
||||
@ -195,17 +153,6 @@ export async function flash(
|
||||
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
|
||||
try {
|
||||
const result = await write(image, drives, flashState.setProgressState);
|
||||
@ -220,39 +167,10 @@ export async function flash(
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
cancelEmitter(status);
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import * as electron from 'electron';
|
||||
import * as settings from '../../../models/settings';
|
||||
import { logEvent } from '../../../modules/analytics';
|
||||
|
||||
/**
|
||||
* @summary Open an external resource
|
||||
@ -27,8 +26,6 @@ export async function open(url: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
logEvent('Open external link', { url });
|
||||
|
||||
if (url) {
|
||||
electron.shell.openExternal(url);
|
||||
}
|
||||
|
@ -198,9 +198,7 @@ export class FlashStep extends React.PureComponent<
|
||||
private handleFlashErrorResponse(shouldRetry: boolean) {
|
||||
this.setState({ errorMessage: '' });
|
||||
flashState.resetState();
|
||||
if (shouldRetry) {
|
||||
analytics.logEvent('Restart after failure');
|
||||
} else {
|
||||
if (!shouldRetry) {
|
||||
selection.clear();
|
||||
}
|
||||
}
|
||||
|
161
npm-shrinkwrap.json
generated
161
npm-shrinkwrap.json
generated
@ -13,7 +13,6 @@
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@ronomon/direct-io": "^3.0.1",
|
||||
"@sentry/electron": "^4.24.0",
|
||||
"analytics-client": "^2.0.1",
|
||||
"axios": "^1.6.8",
|
||||
"debug": "4.3.4",
|
||||
"drivelist": "^12.0.2",
|
||||
@ -101,148 +100,6 @@
|
||||
"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": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
@ -7026,16 +6883,6 @@
|
||||
"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": {
|
||||
"version": "3.0.1",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
@ -34,7 +34,6 @@
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@ronomon/direct-io": "^3.0.1",
|
||||
"@sentry/electron": "^4.24.0",
|
||||
"analytics-client": "^2.0.1",
|
||||
"axios": "^1.6.8",
|
||||
"debug": "4.3.4",
|
||||
"drivelist": "^12.0.2",
|
||||
|
@ -63,9 +63,6 @@ const rules: Required<ModuleOptions>['rules'] = [
|
||||
|
||||
const injectAnalyticsToken = new DefinePlugin({
|
||||
'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''),
|
||||
'process.env.AMPLITUDE_TOKEN': JSON.stringify(
|
||||
process.env.AMPLITUDE_TOKEN || '',
|
||||
),
|
||||
});
|
||||
|
||||
export const rendererConfig: Configuration = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user