Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removes corvus in favor of sentry and analytics client #3891

Merged
merged 6 commits into from Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
Removes corvus in favor of sentry and analytics client
Change-type: patch
Signed-off-by: Otavio Jacobi
  • Loading branch information
otaviojacobi committed Jan 12, 2023
commit 10caf8f1b6a174762192b13ce7bb4eaa71e90fcc
2 changes: 1 addition & 1 deletion .github/actions/publish/action.yml
Expand Up @@ -172,7 +172,7 @@ runs:
for target in ${TARGETS}; do
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
--c.extraMetadata.analytics.sentry.token='${{ steps.sentry.outputs.dsn }}' \
--c.extraMetadata.analytics.mixpanel.token='balena-etcher' \
--c.extraMetadata.analytics.amplitude.token='balena-etcher' \
--c.extraMetadata.packageType="${target}"
find dist -type f -maxdepth 1
Expand Down
4 changes: 2 additions & 2 deletions docs/MAINTAINERS.md
Expand Up @@ -31,7 +31,7 @@ Releasing
- [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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently that repo is now read-only ?

- Wait 2-3 hours for analytics (Sentry, Mixpanel) to trickle in and check for elevated error rates, or regressions
- Wait 2-3 hours for analytics (Sentry, Amplitude) 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 Bintray](#uploading-packages-to-bintray)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL, and IIRC you now use Cloudsmith instead of Bintray? 🤷

- [Upload build artifacts to Amazon S3](#uploading-binaries-to-amazon-s3)
Expand All @@ -48,7 +48,7 @@ Make sure to set the analytics tokens when generating production release binarie

```bash
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
export ANALYTICS_MIXPANEL_TOKEN="xxxxxx"
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
```

#### Linux
Expand Down
2 changes: 1 addition & 1 deletion docs/MANUAL-TESTING.md
Expand Up @@ -112,4 +112,4 @@ 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 Mixpanel**
F5), and check that initial events are not sent to Amplitude**
2 changes: 2 additions & 0 deletions lib/gui/app/app.ts
Expand Up @@ -296,6 +296,8 @@ driveScanner.start();

let popupExists = false;

analytics.initAnalytics();

window.addEventListener('beforeunload', async (event) => {
if (!flashState.isFlashing() || popupExists) {
analytics.logEvent('Close application', {
Expand Down
126 changes: 46 additions & 80 deletions lib/gui/app/modules/analytics.ts
Expand Up @@ -15,108 +15,74 @@
*/

import * as _ from 'lodash';
import * as resinCorvus from 'resin-corvus/browser';

import * as packageJSON from '../../../../package.json';
import { getConfig } from '../../../shared/utils';
import { Client, createClient, createNoopClient } from 'analytics-client';
import * as SentryRenderer from '@sentry/electron/renderer';
import * as settings from '../models/settings';
import { store } from '../models/store';
import * as packageJSON from '../../../../package.json';

const DEFAULT_PROBABILITY = 0.1;

async function installCorvus(): Promise<void> {
const sentryToken =
(await settings.get('analyticsSentryToken')) ||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
const mixpanelToken =
(await settings.get('analyticsMixpanelToken')) ||
_.get(packageJSON, ['analytics', 'mixpanel', 'token']);
resinCorvus.install({
services: {
sentry: sentryToken,
mixpanel: mixpanelToken,
},
options: {
release: packageJSON.version,
shouldReport: () => {
return settings.getSync('errorReporting');
},
mixpanelDeferred: true,
},
});
}

let mixpanelSample = DEFAULT_PROBABILITY;

let analyticsClient: Client;
/**
* @summary Init analytics configurations
*/
async function initConfig() {
await installCorvus();
let validatedConfig = null;
try {
const configUrl = await settings.get('configUrl');
const config = await getConfig(configUrl);
const mixpanel = _.get(config, ['analytics', 'mixpanel'], {});
mixpanelSample = mixpanel.probability || DEFAULT_PROBABILITY;
if (isClientEligible(mixpanelSample)) {
validatedConfig = validateMixpanelConfig(mixpanel);
}
} catch (err) {
resinCorvus.logException(err);
}
resinCorvus.setConfigs({
mixpanel: validatedConfig,
});
}

initConfig();
export const initAnalytics = _.once(() => {
const dsn =
settings.getSync('analyticsSentryToken') ||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
SentryRenderer.init({ dsn });

/**
* @summary Check that the client is eligible for analytics
*/
function isClientEligible(probability: number) {
return Math.random() < probability;
}
const projectName =
settings.getSync('analyticsAmplitudeToken') ||
_.get(packageJSON, ['analytics', 'amplitude', 'token']);

/**
* @summary Check that config has at least HTTP_PROTOCOL and api_host
*/
function validateMixpanelConfig(config: {
api_host?: string;
HTTP_PROTOCOL?: string;
}) {
const mixpanelConfig = {
api_host: 'https://api.mixpanel.com',
const clientConfig = {
projectName,
endpoint: 'data.balena-cloud',
componentName: 'etcher',
componentVersion: packageJSON.version,
};
if (config.HTTP_PROTOCOL !== undefined && config.api_host !== undefined) {
mixpanelConfig.api_host = `${config.HTTP_PROTOCOL}://${config.api_host}`;
}
return mixpanelConfig;
}
analyticsClient = projectName
? createClient(clientConfig)
: createNoopClient();
});

/**
* @summary Log an event
*
* @description
* This function sends the debug message to product analytics services.
*/
export function logEvent(message: string, data: _.Dictionary<any> = {}) {
function reportAnalytics(message: string, data: _.Dictionary<any> = {}) {
const { applicationSessionUuid, flashingWorkflowUuid } = store
.getState()
.toJS();
resinCorvus.logEvent(message, {

analyticsClient.track(message, {
...data,
sample: mixpanelSample,
applicationSessionUuid,
flashingWorkflowUuid,
});
}

/**
* @summary Log an event
*
* @description
* This function sends the debug message to product analytics services.
*/
export async function logEvent(message: string, data: _.Dictionary<any> = {}) {
const shouldReportAnalytics = await settings.get('errorReporting');
if (shouldReportAnalytics) {
initAnalytics();
reportAnalytics(message, data);
}
}

/**
* @summary Log an exception
*
* @description
* This function logs an exception to error reporting services.
*/
export const logException = resinCorvus.logException;
export function logException(error: any) {
const shouldReportErrors = settings.getSync('errorReporting');
if (shouldReportErrors) {
initAnalytics();
console.error(error);
SentryRenderer.captureException(error);
}
aethernet marked this conversation as resolved.
Show resolved Hide resolved
}
29 changes: 24 additions & 5 deletions lib/gui/etcher.ts
Expand Up @@ -20,16 +20,18 @@ import { promises as fs } from 'fs';
import { platform } from 'os';
import * as path from 'path';
import * as semver from 'semver';
import * as _ from 'lodash';

import './app/i18n';

import { packageType, version } from '../../package.json';
import * as EXIT_CODES from '../shared/exit-codes';
import { delay, getConfig } from '../shared/utils';
import * as settings from './app/models/settings';
import { logException } from './app/modules/analytics';
import { buildWindowMenu } from './menu';
import * as i18n from 'i18next';
import * as SentryMain from '@sentry/electron/main';
import * as packageJSON from '../../package.json';

const customProtocol = 'etcher';
const scheme = `${customProtocol}://`;
Expand All @@ -53,13 +55,21 @@ async function checkForUpdates(interval: number) {
packageUpdated = true;
}
} catch (err) {
logException(err);
logMainProcessException(err);
}
}
await delay(interval);
}
}

function logMainProcessException(error: any) {
const shouldReportErrors = settings.getSync('errorReporting');
if (shouldReportErrors) {
console.error(error);
SentryMain.captureException(error);
}
}
aethernet marked this conversation as resolved.
Show resolved Hide resolved

async function isFile(filePath: string): Promise<boolean> {
try {
const stat = await fs.stat(filePath);
Expand Down Expand Up @@ -94,6 +104,14 @@ async function getCommandLineURL(argv: string[]): Promise<string | undefined> {
}
}

const initSentryMain = _.once(() => {
const dsn =
settings.getSync('analyticsSentryToken') ||
_.get(packageJSON, ['analytics', 'sentry', 'token']);

SentryMain.init({ dsn });
});

const sourceSelectorReady = new Promise((resolve) => {
electron.ipcMain.on('source-selector-ready', resolve);
});
Expand Down Expand Up @@ -190,8 +208,9 @@ async function createMainWindow() {
const page = mainWindow.webContents;

page.once('did-frame-finish-load', async () => {
console.log('packageUpdatable', packageUpdatable);
autoUpdater.on('error', (err) => {
logException(err);
logMainProcessException(err);
});
if (packageUpdatable) {
try {
Expand All @@ -208,7 +227,7 @@ async function createMainWindow() {
onlineConfig?.autoUpdates?.checkForUpdatesTimer ?? 300000;
checkForUpdates(checkForUpdatesTimer);
} catch (err) {
logException(err);
logMainProcessException(err);
}
}
});
Expand All @@ -233,6 +252,7 @@ async function main(): Promise<void> {
if (!electron.app.requestSingleInstanceLock()) {
electron.app.quit();
} else {
initSentryMain();
await electron.app.whenReady();
const window = await createMainWindow();
electron.app.on('second-instance', async (_event, argv) => {
Expand All @@ -256,7 +276,6 @@ async function main(): Promise<void> {
});
}
}

main();

console.time('ready-to-show');