Wire up Tracking to different ant.design elements
Summary:
This wires up tracking directly to the ANT component library for the following components:
1. `Button`
2. `Collapse.Panel`
3. `Tabs`
Other less commonly used elements can be connected in the future if needed.
I played a bit with different patterns, but in testing the patch-package patching give the most reliable results. Alternatives considered:
1. Expect users to explicitly wrap there components, e.g. `<Tracked><Button>Hi</Button></Tracked>`
1. Didn't implement this because it would be very common to forget, and at the moment you want to make some analysis you'll discover there is no interesting data available. I think for tracking we want to have opt-out rather than opt-in
2. The additional wrapping can cause some subtile layout issues due to static field inspection / forwarded refs (e.g. Ant often has an assumption that relevant children types are _directly_ nested under their parent element. For examle `<Tooltip><Tracked><Button>` does not work as expected
2. Expose our own `Button` / `Collapse` / `Tabs` that applies `Tracked` to an underlying Ant component.
1. also suffers from 1.b.
2. It is gonna be quite confusing for other devs that some elements would need to be imported from `flipper-plugin`, ant some from `antd`, and that this is likely to change over time. We could lint against it, but it will be still suboptimal
Reviewed By: jknoxville
Differential Revision: D25196321
fbshipit-source-id: b559356498c3191a283062a88daacb354b0f79f4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3394f85fc7
commit
dd6f39c2b3
@@ -202,3 +202,4 @@ export {checkIdbIsInstalled} from './utils/iOSContainerUtility';
|
|||||||
export {default as SidebarExtensions} from './fb-stubs/LayoutInspectorSidebarExtensions';
|
export {default as SidebarExtensions} from './fb-stubs/LayoutInspectorSidebarExtensions';
|
||||||
export {IDEFileResolver, IDEType} from './fb-stubs/IDEFileResolver';
|
export {IDEFileResolver, IDEType} from './fb-stubs/IDEFileResolver';
|
||||||
export {renderMockFlipperWithPlugin} from './test-utils/createMockFlipperWithPlugin';
|
export {renderMockFlipperWithPlugin} from './test-utils/createMockFlipperWithPlugin';
|
||||||
|
export {Tracked} from 'flipper-plugin'; // To be able to use it in legacy plugins
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ function ClassicButton(props: Props) {
|
|||||||
/**
|
/**
|
||||||
* A simple button, used in many parts of the application.
|
* A simple button, used in many parts of the application.
|
||||||
*/
|
*/
|
||||||
export function SandyButton({
|
function SandyButton({
|
||||||
compact,
|
compact,
|
||||||
disabled,
|
disabled,
|
||||||
icon,
|
icon,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ test('Correct top level API exposed', () => {
|
|||||||
"styled",
|
"styled",
|
||||||
"theme",
|
"theme",
|
||||||
"usePlugin",
|
"usePlugin",
|
||||||
|
"useTrackedCallback",
|
||||||
"useValue",
|
"useValue",
|
||||||
"withTrackingScope",
|
"withTrackingScope",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export {
|
|||||||
TrackingScope,
|
TrackingScope,
|
||||||
setGlobalInteractionReporter as _setGlobalInteractionReporter,
|
setGlobalInteractionReporter as _setGlobalInteractionReporter,
|
||||||
withTrackingScope,
|
withTrackingScope,
|
||||||
|
useTrackedCallback,
|
||||||
} from './ui/Tracked';
|
} from './ui/Tracked';
|
||||||
|
|
||||||
export {sleep} from './utils/sleep';
|
export {sleep} from './utils/sleep';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React 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';
|
||||||
|
|
||||||
@@ -97,28 +97,49 @@ export function Tracked({
|
|||||||
}) as any;
|
}) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useTrackedCallback<T extends Function>(
|
||||||
|
action: string,
|
||||||
|
fn: T,
|
||||||
|
deps: any[],
|
||||||
|
): T {
|
||||||
|
const scope = useContext(TrackingScopeContext);
|
||||||
|
return useMemo(() => {
|
||||||
|
return wrapInteractionHandler(fn, null, '', scope, action);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, deps) as any;
|
||||||
|
}
|
||||||
|
|
||||||
// Exported for test
|
// Exported for test
|
||||||
export function wrapInteractionHandler(
|
export function wrapInteractionHandler<T extends Function>(
|
||||||
fn: Function,
|
fn: T,
|
||||||
element: React.ReactElement,
|
element: React.ReactElement | null | string,
|
||||||
event: string,
|
event: string,
|
||||||
scope: string,
|
scope: string,
|
||||||
action?: string,
|
action?: string,
|
||||||
) {
|
): T {
|
||||||
function report(start: number, initialEnd: number, error?: any) {
|
function report(start: number, initialEnd: number, error?: any) {
|
||||||
globalInteractionReporter({
|
globalInteractionReporter({
|
||||||
duration: initialEnd - start,
|
duration: initialEnd - start,
|
||||||
totalDuration: Date.now() - start,
|
totalDuration: Date.now() - start,
|
||||||
success: error ? 0 : 1,
|
success: error ? 0 : 1,
|
||||||
error: error ? '' + error : undefined,
|
error: error ? '' + error : undefined,
|
||||||
componentType: describeElementType(element),
|
componentType:
|
||||||
action: action ?? describeElement(element),
|
element === null
|
||||||
|
? 'unknown'
|
||||||
|
: typeof element === 'string'
|
||||||
|
? element
|
||||||
|
: describeElementType(element),
|
||||||
|
action:
|
||||||
|
action ??
|
||||||
|
(element && typeof element != 'string'
|
||||||
|
? describeElement(element)
|
||||||
|
: 'unknown'),
|
||||||
scope,
|
scope,
|
||||||
event,
|
event,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return function trappedInteractionHandler(this: any) {
|
const res = function trappedInteractionHandler(this: any) {
|
||||||
let res: any;
|
let res: any;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const r = report.bind(null, start);
|
const r = report.bind(null, start);
|
||||||
@@ -144,7 +165,9 @@ export function wrapInteractionHandler(
|
|||||||
r(initialEnd);
|
r(initialEnd);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
} as any;
|
||||||
|
res.flipperTracked = true; // Avoid double wrapping / handling, if e.g. Button is wrapped in Tracked
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeElement(element: React.ReactElement): string {
|
export function describeElement(element: React.ReactElement): string {
|
||||||
@@ -155,7 +178,7 @@ export function describeElement(element: React.ReactElement): string {
|
|||||||
if (typeof element.key === 'string') {
|
if (typeof element.key === 'string') {
|
||||||
return element.key;
|
return element.key;
|
||||||
}
|
}
|
||||||
return reactElementToJSXString(element).substr(0, 200).replace(/\n/g, ' ');
|
return stringifyElement(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeElementType(element: React.ReactElement): string {
|
function describeElementType(element: React.ReactElement): string {
|
||||||
@@ -171,7 +194,7 @@ export function withTrackingScope(Component: any) {
|
|||||||
return function WithTrackingScope(props: any) {
|
return function WithTrackingScope(props: any) {
|
||||||
const scope =
|
const scope =
|
||||||
Component.displayName ?? Component.name ?? Component.constructor?.name;
|
Component.displayName ?? Component.name ?? Component.constructor?.name;
|
||||||
if (!scope) {
|
if (!scope || typeof scope !== 'string') {
|
||||||
throw new Error('Failed to find component name for trackingScope');
|
throw new Error('Failed to find component name for trackingScope');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -181,3 +204,42 @@ export function withTrackingScope(Component: any) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
global.FlipperTrackingScopeContext = TrackingScopeContext;
|
||||||
|
//@ts-ignore
|
||||||
|
global.FlipperTracked = Tracked;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
global.flipperTrackInteraction = function flipperTrackInteraction(
|
||||||
|
elementType: string,
|
||||||
|
event: string,
|
||||||
|
scope: string,
|
||||||
|
action: string | React.ReactElement | null,
|
||||||
|
fn: Function,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
// @ts-ignore
|
||||||
|
if (fn.flipperTracked) {
|
||||||
|
return fn(...args);
|
||||||
|
}
|
||||||
|
return wrapInteractionHandler(
|
||||||
|
fn,
|
||||||
|
elementType,
|
||||||
|
event,
|
||||||
|
scope,
|
||||||
|
!action
|
||||||
|
? 'unknown action'
|
||||||
|
: typeof action === 'string'
|
||||||
|
? action
|
||||||
|
: stringifyElement(action),
|
||||||
|
)(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
function stringifyElement(element: any): string {
|
||||||
|
if (!element) return 'unknown element';
|
||||||
|
if (typeof element === 'string') return element;
|
||||||
|
if (Array.isArray(element))
|
||||||
|
return element.filter(Boolean).map(stringifyElement).join('');
|
||||||
|
return reactElementToJSXString(element).substr(0, 200).replace(/\n/g, ' ');
|
||||||
|
}
|
||||||
|
|||||||
26
desktop/patches/antd+4.6.6.patch
Normal file
26
desktop/patches/antd+4.6.6.patch
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
diff --git a/node_modules/antd/es/button/button.js b/node_modules/antd/es/button/button.js
|
||||||
|
index 8496110..35cce19 100644
|
||||||
|
--- a/node_modules/antd/es/button/button.js
|
||||||
|
+++ b/node_modules/antd/es/button/button.js
|
||||||
|
@@ -186,6 +186,8 @@ var InternalButton = function InternalButton(props, ref) {
|
||||||
|
fixTwoCNChar();
|
||||||
|
}, [buttonRef]);
|
||||||
|
|
||||||
|
+ var scope = React.useContext(global.FlipperTrackingScopeContext);
|
||||||
|
+
|
||||||
|
var handleClick = function handleClick(e) {
|
||||||
|
var onClick = props.onClick;
|
||||||
|
|
||||||
|
@@ -194,7 +196,11 @@ var InternalButton = function InternalButton(props, ref) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
- onClick(e);
|
||||||
|
+ global.flipperTrackInteraction(
|
||||||
|
+ 'Button', 'onClick', scope, props.title || props.children || props.icon,
|
||||||
|
+ onClick,
|
||||||
|
+ e
|
||||||
|
+ );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
21
desktop/patches/rc-collapse+2.0.0.patch
Normal file
21
desktop/patches/rc-collapse+2.0.0.patch
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
diff --git a/node_modules/rc-collapse/es/Panel.js b/node_modules/rc-collapse/es/Panel.js
|
||||||
|
index 13dc708..c5145e7 100644
|
||||||
|
--- a/node_modules/rc-collapse/es/Panel.js
|
||||||
|
+++ b/node_modules/rc-collapse/es/Panel.js
|
||||||
|
@@ -82,6 +82,7 @@ var CollapsePanel = function (_Component) {
|
||||||
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: itemCls, style: style, id: id },
|
||||||
|
+ React.createElement(global.FlipperTracked, { action: 'collapse:' + header },
|
||||||
|
React.createElement(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
@@ -99,7 +100,7 @@ var CollapsePanel = function (_Component) {
|
||||||
|
{ className: prefixCls + '-extra' },
|
||||||
|
extra
|
||||||
|
)
|
||||||
|
- ),
|
||||||
|
+ )),
|
||||||
|
React.createElement(
|
||||||
|
Animate,
|
||||||
|
{
|
||||||
25
desktop/patches/rc-tabs+11.6.1.patch
Normal file
25
desktop/patches/rc-tabs+11.6.1.patch
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
diff --git a/node_modules/rc-tabs/es/TabNavList/TabNode.js b/node_modules/rc-tabs/es/TabNavList/TabNode.js
|
||||||
|
index 2a69e83..e00ada1 100644
|
||||||
|
--- a/node_modules/rc-tabs/es/TabNavList/TabNode.js
|
||||||
|
+++ b/node_modules/rc-tabs/es/TabNavList/TabNode.js
|
||||||
|
@@ -37,10 +37,19 @@ function TabNode(_ref, ref) {
|
||||||
|
}
|
||||||
|
|
||||||
|
var removable = editable && closable !== false && !disabled;
|
||||||
|
+ var scope = React.useContext(global.FlipperTrackingScopeContext);
|
||||||
|
|
||||||
|
function onInternalClick(e) {
|
||||||
|
if (disabled) return;
|
||||||
|
- onClick(e);
|
||||||
|
+
|
||||||
|
+ global.flipperTrackInteraction(
|
||||||
|
+ 'Tabs',
|
||||||
|
+ 'onTabClick',
|
||||||
|
+ scope,
|
||||||
|
+ 'tab:' + key + ':' + tab,
|
||||||
|
+ onClick,
|
||||||
|
+ e
|
||||||
|
+ );
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemoveTab(event) {
|
||||||
Reference in New Issue
Block a user