Skip to content

Commit

Permalink
Merge pull request #24081 from storybookjs/yann/improve-telemetry-met…
Browse files Browse the repository at this point in the history
…adata

Telemetry: Add platform info to telemetry event
  • Loading branch information
yannbf authored Sep 7, 2023
2 parents 08e371c + a1fd317 commit d2fc6de
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 58 deletions.
10 changes: 7 additions & 3 deletions code/lib/core-events/src/errors/manager-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ export class UncaughtManagerError extends StorybookError {

readonly code = 1;

constructor(public error: Error) {
super(error.message);
this.stack = error.stack;
constructor(
public data: {
error: Error;
}
) {
super(data.error.message);
this.stack = data.error.stack;
}

template() {
Expand Down
4 changes: 2 additions & 2 deletions code/lib/core-server/src/withTelemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ describe('sendTelemetryError', () => {
error: mockError,
eventType,
isErrorInstance: false,
errorHash: 'no-message',
errorHash: 'NO_MESSAGE',
}),
expect.any(Object)
);
Expand All @@ -338,7 +338,7 @@ describe('sendTelemetryError', () => {
error: mockError,
eventType,
isErrorInstance: true,
errorHash: 'empty-message',
errorHash: 'EMPTY_MESSAGE',
}),
expect.any(Object)
);
Expand Down
29 changes: 7 additions & 22 deletions code/lib/core-server/src/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,37 +80,22 @@ export async function sendTelemetryError(
if (errorLevel !== 'none') {
const precedingUpgrade = await getPrecedingUpgrade();

const error = _error as Error | Record<string, any>;

let storybookErrorProperties = {};
// if it's an UNCATEGORIZED error, it won't have a coded name, so we just pass the category and source
if ((error as any).category) {
const { category } = error as any;
storybookErrorProperties = {
category,
};
}

if ((error as any).fromStorybook) {
const { code, name } = error as any;
storybookErrorProperties = {
...storybookErrorProperties,
code,
name,
};
}
const error = _error as Error & Record<string, any>;

let errorHash;
if ('message' in error) {
errorHash = error.message ? oneWayHash(error.message) : 'empty-message';
errorHash = error.message ? oneWayHash(error.message) : 'EMPTY_MESSAGE';
} else {
errorHash = 'no-message';
errorHash = 'NO_MESSAGE';
}

const { code, name, category } = error;
await telemetry(
'error',
{
...storybookErrorProperties,
code,
name,
category,
eventType,
precedingUpgrade,
error: errorLevel === 'full' ? error : undefined,
Expand Down
1 change: 1 addition & 0 deletions code/lib/preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@storybook/core-events": "workspace:*",
"@storybook/global": "^5.0.0",
"@storybook/preview-api": "workspace:*",
"browser-dtector": "^3.4.0",
"typescript": "~4.9.3"
},
"publishConfig": {
Expand Down
3 changes: 2 additions & 1 deletion code/lib/preview/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { global } from '@storybook/global';

import { values } from './globals/runtime';
import { globals } from './globals/types';
import { prepareForTelemetry } from './utils';

const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;

Expand All @@ -13,7 +14,7 @@ getKeys(globals).forEach((key) => {

global.sendTelemetryError = (error: any) => {
const channel = global.__STORYBOOK_ADDONS_CHANNEL__;
channel.emit(TELEMETRY_ERROR, error);
channel.emit(TELEMETRY_ERROR, prepareForTelemetry(error));
};

// handle all uncaught StorybookError at the root of the application and log to telemetry if applicable
Expand Down
29 changes: 29 additions & 0 deletions code/lib/preview/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { global } from '@storybook/global';
import type { BrowserInfo } from 'browser-dtector';
import BrowserDetector from 'browser-dtector';

let browserInfo: BrowserInfo | undefined;

function getBrowserInfo() {
if (!browserInfo) {
browserInfo = new BrowserDetector(global.navigator?.userAgent).getBrowserInfo();
}

return browserInfo;
}

export function prepareForTelemetry(
error: Error & {
fromStorybook?: boolean;
category?: string;
target?: any;
currentTarget?: any;
srcElement?: any;
browserInfo?: BrowserInfo;
}
) {
// eslint-disable-next-line no-param-reassign
error.browserInfo = getBrowserInfo();

return error;
}
22 changes: 22 additions & 0 deletions code/lib/telemetry/src/telemetry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="node" />

import * as os from 'os';
import originalFetch from 'node-fetch';
import retry from 'fetch-retry';
import { nanoid } from 'nanoid';
Expand All @@ -18,12 +19,33 @@ export const addToGlobalContext = (key: string, value: any) => {
globalContext[key] = value;
};

const getOperatingSystem = (): 'Windows' | 'macOS' | 'Linux' | `Other: ${string}` | 'Unknown' => {
try {
const platform = os.platform();

if (platform === 'win32') {
return 'Windows';
}
if (platform === 'darwin') {
return 'macOS';
}
if (platform === 'linux') {
return 'Linux';
}

return `Other: ${platform}`;
} catch (_err) {
return 'Unknown';
}
};

// context info sent with all events, provided
// by the app. currently:
// - cliVersion
const globalContext = {
inCI: Boolean(process.env.CI),
isTTY: process.stdout.isTTY,
platform: getOperatingSystem(),
} as Record<string, any>;

const prepareRequest = async (data: TelemetryData, context: Record<string, any>, options: any) => {
Expand Down
1 change: 1 addition & 0 deletions code/ui/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@storybook/types": "workspace:*",
"@testing-library/react": "^11.2.2",
"@types/semver": "^7.3.4",
"browser-dtector": "^3.4.0",
"copy-to-clipboard": "^3.3.1",
"downshift": "^6.0.15",
"fs-extra": "^11.1.0",
Expand Down
32 changes: 2 additions & 30 deletions code/ui/manager/src/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable local-rules/no-uncategorized-errors */

import { global } from '@storybook/global';

import type { Channel } from '@storybook/channels';
Expand All @@ -8,12 +6,12 @@ import { addons } from '@storybook/manager-api';
import type { Addon_Types, Addon_Config } from '@storybook/types';
import { createBrowserChannel } from '@storybook/channels';
import { CHANNEL_CREATED, TELEMETRY_ERROR } from '@storybook/core-events';
import { UncaughtManagerError } from '@storybook/core-events/manager-errors';
import Provider from './provider';
import { renderStorybookUI } from './index';

import { values } from './globals/runtime';
import { Keys } from './globals/types';
import { prepareForTelemetry } from './utils/prepareForTelemetry';

const { FEATURES, CONFIG_TYPE } = global;

Expand Down Expand Up @@ -64,35 +62,9 @@ Object.keys(Keys).forEach((key: keyof typeof Keys) => {
global[Keys[key]] = values[key];
});

function preprocessError(
originalError: Error & {
fromStorybook?: boolean;
category?: string;
target?: any;
currentTarget?: any;
srcElement?: any;
}
) {
let error = originalError;

if (!originalError.fromStorybook) {
error = new UncaughtManagerError(originalError);
}

// DOM manipulation errors and other similar errors are not serializable as they contain
// circular references to the window object. If that's the case, we make a simplified copy
if (error.target === window || error.currentTarget === window || error.srcElement === window) {
error = new Error(originalError.message);
error.name = originalError.name || error.name;
error.category = originalError.category;
}

return error;
}

global.sendTelemetryError = (error) => {
const channel = global.__STORYBOOK_ADDONS_CHANNEL__;
channel.emit(TELEMETRY_ERROR, preprocessError(error));
channel.emit(TELEMETRY_ERROR, prepareForTelemetry(error));
};

// handle all uncaught errors at the root of the application and log to telemetry
Expand Down
47 changes: 47 additions & 0 deletions code/ui/manager/src/utils/prepareForTelemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable local-rules/no-uncategorized-errors */
import { UncaughtManagerError } from '@storybook/core-events/manager-errors';
import { global } from '@storybook/global';
import type { BrowserInfo } from 'browser-dtector';
import BrowserDetector from 'browser-dtector';

let browserInfo: BrowserInfo | undefined;

function getBrowserInfo() {
if (!browserInfo) {
browserInfo = new BrowserDetector(global.navigator?.userAgent).getBrowserInfo();
}

return browserInfo;
}

export function prepareForTelemetry(
originalError: Error & {
fromStorybook?: boolean;
category?: string;
target?: any;
currentTarget?: any;
srcElement?: any;
browserInfo?: BrowserInfo;
}
) {
let error = originalError;

// DOM manipulation errors and other similar errors are not serializable as they contain
// circular references to the window object. If that's the case, we make a simplified copy
if (
originalError.target === global ||
originalError.currentTarget === global ||
originalError.srcElement === global
) {
error = new Error(originalError.message);
error.name = originalError.name || error.name;
}

if (!originalError.fromStorybook) {
error = new UncaughtManagerError({ error });
}

error.browserInfo = getBrowserInfo();

return error;
}
9 changes: 9 additions & 0 deletions code/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7249,6 +7249,7 @@ __metadata:
"@storybook/types": "workspace:*"
"@testing-library/react": ^11.2.2
"@types/semver": ^7.3.4
browser-dtector: ^3.4.0
copy-to-clipboard: ^3.3.1
downshift: ^6.0.15
fs-extra: ^11.1.0
Expand Down Expand Up @@ -7650,6 +7651,7 @@ __metadata:
"@storybook/core-events": "workspace:*"
"@storybook/global": ^5.0.0
"@storybook/preview-api": "workspace:*"
browser-dtector: ^3.4.0
typescript: ~4.9.3
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -12284,6 +12286,13 @@ __metadata:
languageName: node
linkType: hard

"browser-dtector@npm:^3.4.0":
version: 3.4.0
resolution: "browser-dtector@npm:3.4.0"
checksum: b2586d2fdccd9ab992b6cc254a65f10d54137b50edfd70bf80ecf80e8e7761e78482e10d7c3874609ab9b602bc6da7466a01b254d40ec721d341c723589aa288
languageName: node
linkType: hard

"browser-process-hrtime@npm:^1.0.0":
version: 1.0.0
resolution: "browser-process-hrtime@npm:1.0.0"
Expand Down

0 comments on commit d2fc6de

Please sign in to comment.