Add info about interactions to error reports
Summary: When reporting errors we could add info about interactions which caused errors. Ability to connect errors and interactions could be quite helpful for analysing and debugging errors and where they are coming from. Reviewed By: passy, mweststrate Differential Revision: D28467575 fbshipit-source-id: bef69917a4d6c786d762a2f6eb75a47fd4e46b0f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
03a1add092
commit
853ee24c9b
16
desktop/app/src/__mocks__/uuid.tsx
Normal file
16
desktop/app/src/__mocks__/uuid.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function v4() {
|
||||||
|
return '00000000-0000-0000-0000-000000000000';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function v1() {
|
||||||
|
return '00000000-0000-0000-0000-000000000000';
|
||||||
|
}
|
||||||
@@ -15,14 +15,7 @@
|
|||||||
export function cleanStack(_stack: string, _loc?: string) {}
|
export function cleanStack(_stack: string, _loc?: string) {}
|
||||||
import ScribeLogger from './ScribeLogger';
|
import ScribeLogger from './ScribeLogger';
|
||||||
|
|
||||||
export type ObjectError =
|
|
||||||
| Error
|
|
||||||
| {
|
|
||||||
message: string;
|
|
||||||
stack?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class ErrorReporter {
|
export default class ErrorReporter {
|
||||||
constructor(_scribeLogger: ScribeLogger) {}
|
constructor(_scribeLogger: ScribeLogger) {}
|
||||||
report(_err: ObjectError) {}
|
report(_err: Error) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,20 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {InteractionReport} from 'flipper-plugin';
|
||||||
|
|
||||||
export class CancelledPromiseError extends Error {
|
export class CancelledPromiseError extends Error {
|
||||||
constructor(msg: string) {
|
constructor(msg: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
this.name = 'CancelledPromiseError';
|
this.name = 'CancelledPromiseError';
|
||||||
}
|
}
|
||||||
|
name: 'CancelledPromiseError';
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Error {
|
||||||
|
interaction?: InteractionReport;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isError(obj: any): obj is Error {
|
export function isError(obj: any): obj is Error {
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
"@emotion/css": "^11.1.3",
|
"@emotion/css": "^11.1.3",
|
||||||
"@emotion/react": "^11.4.0",
|
"@emotion/react": "^11.4.0",
|
||||||
"@reach/observe-rect": "^1.2.0",
|
"@reach/observe-rect": "^1.2.0",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
"immer": "^9.0.2",
|
"immer": "^9.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-element-to-jsx-string": "^14.3.2",
|
"react-element-to-jsx-string": "^14.3.2",
|
||||||
"react-virtual": "^2.7.1",
|
"react-virtual": "^2.7.1",
|
||||||
"string-natural-compare": "^3.0.0"
|
"string-natural-compare": "^3.0.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ test('Correct top level API exposed', () => {
|
|||||||
"FlipperLib",
|
"FlipperLib",
|
||||||
"HighlightManager",
|
"HighlightManager",
|
||||||
"Idler",
|
"Idler",
|
||||||
|
"InteractionReport",
|
||||||
|
"InteractionReporter",
|
||||||
"LogLevel",
|
"LogLevel",
|
||||||
"LogTypes",
|
"LogTypes",
|
||||||
"Logger",
|
"Logger",
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ export {
|
|||||||
withTrackingScope,
|
withTrackingScope,
|
||||||
useTrackedCallback,
|
useTrackedCallback,
|
||||||
wrapInteractionHandler as _wrapInteractionHandler,
|
wrapInteractionHandler as _wrapInteractionHandler,
|
||||||
|
InteractionReport,
|
||||||
|
InteractionReporter,
|
||||||
} from './ui/Tracked';
|
} from './ui/Tracked';
|
||||||
|
|
||||||
export {DataFormatter} from './ui/DataFormatter';
|
export {DataFormatter} from './ui/DataFormatter';
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import React, {useMemo} from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import {Children, cloneElement, createContext, useContext} from 'react';
|
import {Children, cloneElement, createContext, useContext} from 'react';
|
||||||
import reactElementToJSXString from 'react-element-to-jsx-string';
|
import reactElementToJSXString from 'react-element-to-jsx-string';
|
||||||
|
import {v4 as uuid} from 'uuid';
|
||||||
|
|
||||||
export type InteractionReport = {
|
export type InteractionReport = {
|
||||||
// Duration of the event handler itself, not including any time the promise handler might have been pending
|
// Duration of the event handler itself, not including any time the promise handler might have been pending
|
||||||
@@ -22,6 +23,7 @@ export type InteractionReport = {
|
|||||||
action: string;
|
action: string;
|
||||||
componentType: string;
|
componentType: string;
|
||||||
event: string;
|
event: string;
|
||||||
|
uuid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InteractionReporter = (report: InteractionReport) => void;
|
export type InteractionReporter = (report: InteractionReport) => void;
|
||||||
@@ -124,8 +126,9 @@ export function wrapInteractionHandler<T extends Function>(
|
|||||||
scope: string,
|
scope: string,
|
||||||
action?: string,
|
action?: string,
|
||||||
): T {
|
): T {
|
||||||
|
const interaction_uuid = uuid();
|
||||||
function report(start: number, initialEnd: number, error?: any) {
|
function report(start: number, initialEnd: number, error?: any) {
|
||||||
globalInteractionReporter({
|
const interactionReport: InteractionReport = {
|
||||||
duration: initialEnd - start,
|
duration: initialEnd - start,
|
||||||
totalDuration: Date.now() - start,
|
totalDuration: Date.now() - start,
|
||||||
success: error ? 0 : 1,
|
success: error ? 0 : 1,
|
||||||
@@ -143,7 +146,13 @@ export function wrapInteractionHandler<T extends Function>(
|
|||||||
: 'unknown'),
|
: 'unknown'),
|
||||||
scope,
|
scope,
|
||||||
event,
|
event,
|
||||||
});
|
uuid: interaction_uuid,
|
||||||
|
};
|
||||||
|
if (error && typeof error === 'object') {
|
||||||
|
// associate the error with the interaction caused it
|
||||||
|
error.interaction = interactionReport;
|
||||||
|
}
|
||||||
|
globalInteractionReporter(interactionReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = function trappedInteractionHandler(this: any) {
|
const res = function trappedInteractionHandler(this: any) {
|
||||||
@@ -168,7 +177,7 @@ export function wrapInteractionHandler<T extends Function>(
|
|||||||
(error: any) => r(initialEnd, error),
|
(error: any) => r(initialEnd, error),
|
||||||
);
|
);
|
||||||
res = res.catch((error: any) => {
|
res = res.catch((error: any) => {
|
||||||
// we need to create another rejected promise so error is again marked as "unhandled"
|
// we need to create another rejected promise so error is again marked as "unhandled".
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ test('Tracked button', () => {
|
|||||||
event: 'onClick',
|
event: 'onClick',
|
||||||
scope: 'Flipper',
|
scope: 'Flipper',
|
||||||
success: 1,
|
success: 1,
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ test('Tracked button - custom handler', () => {
|
|||||||
event: 'onDoubleClick',
|
event: 'onDoubleClick',
|
||||||
scope: 'Flipper',
|
scope: 'Flipper',
|
||||||
success: 1,
|
success: 1,
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,6 +101,7 @@ test('Throwing action', () => {
|
|||||||
event: 'click',
|
event: 'click',
|
||||||
scope: 'test',
|
scope: 'test',
|
||||||
success: 0,
|
success: 0,
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,6 +127,7 @@ test('Async action', async () => {
|
|||||||
event: 'click',
|
event: 'click',
|
||||||
scope: 'test',
|
scope: 'test',
|
||||||
success: 1,
|
success: 1,
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,6 +159,7 @@ test('Throwing async action', async () => {
|
|||||||
event: 'click',
|
event: 'click',
|
||||||
scope: 'test',
|
scope: 'test',
|
||||||
success: 0,
|
success: 0,
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3143,7 +3143,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-4.0.0.tgz#72eff71648a429c7d4acf94e03780e06671369bd"
|
resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-4.0.0.tgz#72eff71648a429c7d4acf94e03780e06671369bd"
|
||||||
integrity sha512-awrJu8yML4E/xTwr2EMatC+HBnHGoDxc2+ImA9QyeUELI1S7dOCIZcyjki1rkwoA8P2D2NVgLAJLjnclkdLtAw==
|
integrity sha512-awrJu8yML4E/xTwr2EMatC+HBnHGoDxc2+ImA9QyeUELI1S7dOCIZcyjki1rkwoA8P2D2NVgLAJLjnclkdLtAw==
|
||||||
|
|
||||||
"@types/uuid@^8.0.0", "@types/uuid@^8.0.1":
|
"@types/uuid@^8.0.0", "@types/uuid@^8.0.1", "@types/uuid@^8.3.0":
|
||||||
version "8.3.0"
|
version "8.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
|
||||||
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
|
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
|
||||||
|
|||||||
Reference in New Issue
Block a user