Wire up tracking to Sandy Chrome

Summary: Wired up tracking to all chrome sections and some import UI elements

Reviewed By: jknoxville

Differential Revision: D25219089

fbshipit-source-id: c75bed91894609dafc5fcc6423a5228211fb92d8
This commit is contained in:
Michel Weststrate
2020-12-03 04:13:07 -08:00
committed by Facebook GitHub Bot
parent dd6f39c2b3
commit 84c6e05b8a
18 changed files with 539 additions and 447 deletions

View File

@@ -26,7 +26,11 @@ import electron, {MenuItemConstructorOptions} from 'electron';
import {notNull} from './utils/typeUtils'; import {notNull} from './utils/typeUtils';
import constants from './fb-stubs/constants'; import constants from './fb-stubs/constants';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import {NormalizedMenuEntry, _buildInMenuEntries} from 'flipper-plugin'; import {
NormalizedMenuEntry,
_buildInMenuEntries,
_wrapInteractionHandler,
} from 'flipper-plugin';
import {StyleGuide} from './sandy-chrome/StyleGuide'; import {StyleGuide} from './sandy-chrome/StyleGuide';
import {showEmulatorLauncher} from './sandy-chrome/appinspect/LaunchEmulator'; import {showEmulatorLauncher} from './sandy-chrome/appinspect/LaunchEmulator';
@@ -191,7 +195,13 @@ export function addSandyPluginEntries(entries: NormalizedMenuEntry[]) {
if (parent) { if (parent) {
const item = new electron.remote.MenuItem({ const item = new electron.remote.MenuItem({
enabled: true, enabled: true,
click: () => pluginActionHandler?.(entry.action!), click: _wrapInteractionHandler(
() => pluginActionHandler?.(entry.action!),
'MenuItem',
'onClick',
'flipper:menu:' + entry.topLevelMenu,
entry.label,
),
label: entry.label, label: entry.label,
accelerator: entry.accelerator, accelerator: entry.accelerator,
}); });
@@ -206,6 +216,20 @@ export function addSandyPluginEntries(entries: NormalizedMenuEntry[]) {
} }
} }
function trackMenuItems(menu: string, items: MenuItemConstructorOptions[]) {
items.forEach((item) => {
if (item.label && item.click) {
item.click = _wrapInteractionHandler(
item.click,
'MenuItem',
'onClick',
'flipper:menu:' + menu,
item.label,
);
}
});
}
function getTemplate( function getTemplate(
app: electron.App, app: electron.App,
shell: electron.Shell, shell: electron.Shell,
@@ -226,6 +250,8 @@ function getTemplate(
click: () => startLinkExport(store.dispatch), click: () => startLinkExport(store.dispatch),
}); });
} }
trackMenuItems('export', exportSubmenu);
const fileSubmenu: MenuItemConstructorOptions[] = [ const fileSubmenu: MenuItemConstructorOptions[] = [
{ {
label: 'Launch Emulator...', label: 'Launch Emulator...',
@@ -250,6 +276,8 @@ function getTemplate(
submenu: exportSubmenu, submenu: exportSubmenu,
}, },
]; ];
trackMenuItems('file', fileSubmenu);
const supportRequestSubmenu = [ const supportRequestSubmenu = [
{ {
label: 'Create...', label: 'Create...',
@@ -259,11 +287,101 @@ function getTemplate(
}, },
}, },
]; ];
trackMenuItems('support', supportRequestSubmenu);
fileSubmenu.push({ fileSubmenu.push({
label: 'Support Requests', label: 'Support Requests',
submenu: supportRequestSubmenu, submenu: supportRequestSubmenu,
}); });
const viewMenu: MenuItemConstructorOptions[] = [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
logger.track('usage', 'reload');
focusedWindow.reload();
}
},
},
{
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F';
} else {
return 'F11';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Manage Plugins...',
click: function () {
store.dispatch(setActiveSheet(ACTIVE_SHEET_PLUGINS));
},
},
{
label: 'Flipper style guide',
click() {
store.dispatch(setStaticView(StyleGuide));
},
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
} else {
return 'Ctrl+Shift+I';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
// @ts-ignore: https://github.com/electron/electron/issues/7832
focusedWindow.toggleDevTools();
}
},
},
{
type: 'separator',
},
];
trackMenuItems('view', viewMenu);
const helpMenu: MenuItemConstructorOptions[] = [
{
label: 'Getting started',
click: function () {
shell.openExternal('https://fbflipper.com/docs/getting-started/index');
},
},
{
label: 'Create plugins',
click: function () {
shell.openExternal('https://fbflipper.com/docs/tutorial/intro');
},
},
{
label: 'Report problems',
click: function () {
shell.openExternal(constants.FEEDBACK_GROUP_LINK);
},
},
{
label: 'Changelog',
click() {
store.dispatch(setActiveSheet(ACTIVE_SHEET_CHANGELOG));
},
},
];
trackMenuItems('help', helpMenu);
const template: MenuItemConstructorOptions[] = [ const template: MenuItemConstructorOptions[] = [
{ {
label: 'File', label: 'File',
@@ -309,73 +427,7 @@ function getTemplate(
}, },
{ {
label: 'View', label: 'View',
submenu: [ submenu: viewMenu,
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function (
_,
focusedWindow: electron.BrowserWindow | undefined,
) {
if (focusedWindow) {
logger.track('usage', 'reload');
focusedWindow.reload();
}
},
},
{
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F';
} else {
return 'F11';
}
})(),
click: function (
_,
focusedWindow: electron.BrowserWindow | undefined,
) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Manage Plugins...',
click: function () {
store.dispatch(setActiveSheet(ACTIVE_SHEET_PLUGINS));
},
},
{
label: 'Flipper style guide',
click() {
store.dispatch(setStaticView(StyleGuide));
},
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
} else {
return 'Ctrl+Shift+I';
}
})(),
click: function (
_,
focusedWindow: electron.BrowserWindow | undefined,
) {
if (focusedWindow) {
// @ts-ignore: https://github.com/electron/electron/issues/7832
focusedWindow.toggleDevTools();
}
},
},
{
type: 'separator',
},
],
}, },
{ {
label: 'Window', label: 'Window',
@@ -396,36 +448,10 @@ function getTemplate(
{ {
label: 'Help', label: 'Help',
role: 'help', role: 'help',
submenu: [ submenu: helpMenu,
{
label: 'Getting started',
click: function () {
shell.openExternal(
'https://fbflipper.com/docs/getting-started/index',
);
},
},
{
label: 'Create plugins',
click: function () {
shell.openExternal('https://fbflipper.com/docs/tutorial/intro');
},
},
{
label: 'Report problems',
click: function () {
shell.openExternal(constants.FEEDBACK_GROUP_LINK);
},
},
{
label: 'Changelog',
click() {
store.dispatch(setActiveSheet(ACTIVE_SHEET_CHANGELOG));
},
},
],
}, },
]; ];
trackMenuItems('support', supportRequestSubmenu);
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const name = app.name; const name = app.name;

View File

@@ -48,15 +48,15 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
const handleScreenshot = useCallback(() => { const handleScreenshot = useCallback(() => {
setIsTakingScreenshot(true); setIsTakingScreenshot(true);
capture(selectedDevice!) const p = capture(selectedDevice!).then(openFile);
.then(openFile)
.catch((e) => { p.catch((e) => {
console.error('Taking screenshot failed:', e); console.error('Taking screenshot failed:', e);
message.error('Taking screenshot failed:' + e); message.error('Taking screenshot failed:' + e);
}) }).finally(() => {
.finally(() => {
setIsTakingScreenshot(false); setIsTakingScreenshot(false);
}); });
return p;
}, [selectedDevice]); }, [selectedDevice]);
const handleRecording = useCallback(() => { const handleRecording = useCallback(() => {
if (!selectedDevice) { if (!selectedDevice) {
@@ -65,13 +65,13 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
if (!isRecording) { if (!isRecording) {
setIsRecording(true); setIsRecording(true);
const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4')); const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4'));
selectedDevice.startScreenCapture(videoPath).catch((e) => { return selectedDevice.startScreenCapture(videoPath).catch((e) => {
console.error('Failed to start recording', e); console.error('Failed to start recording', e);
message.error('Failed to start recording' + e); message.error('Failed to start recording' + e);
setIsRecording(false); setIsRecording(false);
}); });
} else { } else {
selectedDevice return selectedDevice
.stopScreenCapture() .stopScreenCapture()
.then(openFile) .then(openFile)
.catch((e) => { .catch((e) => {

View File

@@ -28,7 +28,7 @@ import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
import SandySettingsPanel from '../fb-stubs/SandySettingsPanel'; import SandySettingsPanel from '../fb-stubs/SandySettingsPanel';
import {reportUsage} from '../utils/metrics'; import {reportUsage} from '../utils/metrics';
import {Modal} from 'antd'; import {Modal} from 'antd';
import {Layout, _NuxManagerContext} from 'flipper-plugin'; import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
padding: 20, padding: 20,
@@ -356,7 +356,7 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
isXcodeDetected: application.xcodeCommandLineToolsDetected, isXcodeDetected: application.xcodeCommandLineToolsDetected,
}), }),
{updateSettings, updateLauncherSettings}, {updateSettings, updateLauncherSettings},
)(SettingsSheet); )(withTrackingScope(SettingsSheet));
function ResetTooltips() { function ResetTooltips() {
const nuxManager = useContext(_NuxManagerContext); const nuxManager = useContext(_NuxManagerContext);

View File

@@ -27,6 +27,7 @@ import ContextMenu from '../ui/components/ContextMenu';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import {reportPlatformFailures} from '../utils/metrics'; import {reportPlatformFailures} from '../utils/metrics';
import {Modal} from 'antd'; import {Modal} from 'antd';
import {TrackingScope} from 'flipper-plugin';
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
padding: 20, padding: 20,
@@ -135,6 +136,7 @@ class SignInSheet extends Component<Props, State> {
footer: React.ReactElement, footer: React.ReactElement,
) { ) {
return ( return (
<TrackingScope scope="logindialog">
<Modal <Modal
visible visible
centered centered
@@ -144,6 +146,7 @@ class SignInSheet extends Component<Props, State> {
footer={footer}> footer={footer}>
<FlexColumn>{contents}</FlexColumn> <FlexColumn>{contents}</FlexColumn>
</Modal> </Modal>
</TrackingScope>
); );
} }

View File

@@ -21,6 +21,7 @@ const {shell, remote} = !isHeadless()
: {shell: undefined, remote: undefined}; : {shell: undefined, remote: undefined};
import {PureComponent} from 'react'; import {PureComponent} from 'react';
import React from 'react'; import React from 'react';
import {Tracked, TrackingScope} from 'flipper-plugin';
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
height: '100%', height: '100%',
@@ -127,6 +128,7 @@ export default class WelcomeScreen extends PureComponent<Props, State> {
render() { render() {
return ( return (
<Container> <Container>
<TrackingScope scope="welcomescreen">
<Welcome isMounted={this.state.isMounted}> <Welcome isMounted={this.state.isMounted}>
<Logo src="./icon.png" /> <Logo src="./icon.png" />
<Title>Welcome to Flipper</Title> <Title>Welcome to Flipper</Title>
@@ -135,11 +137,14 @@ export default class WelcomeScreen extends PureComponent<Props, State> {
? `Version ${remote.app.getVersion()}` ? `Version ${remote.app.getVersion()}`
: 'Development Mode'} : 'Development Mode'}
</Version> </Version>
<Tracked>
<Item <Item
onClick={() => onClick={() => {
shell && shell &&
shell.openExternal('https://fbflipper.com/docs/features/index') shell.openExternal(
}> 'https://fbflipper.com/docs/features/index',
);
}}>
<Icon size={20} name="rocket" color={brandColors.Flipper} /> <Icon size={20} name="rocket" color={brandColors.Flipper} />
<FlexColumn> <FlexColumn>
<ItemTitle>Using Flipper</ItemTitle> <ItemTitle>Using Flipper</ItemTitle>
@@ -151,7 +156,9 @@ export default class WelcomeScreen extends PureComponent<Props, State> {
<Item <Item
onClick={() => onClick={() =>
shell && shell &&
shell.openExternal('https://fbflipper.com/docs/tutorial/intro') shell.openExternal(
'https://fbflipper.com/docs/tutorial/intro',
)
}> }>
<Icon size={20} name="magic-wand" color={brandColors.Flipper} /> <Icon size={20} name="magic-wand" color={brandColors.Flipper} />
<FlexColumn> <FlexColumn>
@@ -184,7 +191,9 @@ export default class WelcomeScreen extends PureComponent<Props, State> {
</ItemSubTitle> </ItemSubTitle>
</FlexColumn> </FlexColumn>
</Item> </Item>
</Tracked>
</Welcome> </Welcome>
</TrackingScope>
</Container> </Container>
); );
} }

View File

@@ -343,7 +343,7 @@ const demos: PreviewProps[] = [
], ],
demos: { demos: {
'Basic example': ( 'Basic example': (
<TrackingScope scope="Tracking scope demo"> <TrackingScope scope="tracking scope demo">
<Tracked> <Tracked>
<Button onClick={() => {}}>Test</Button> <Button onClick={() => {}}>Test</Button>
</Tracked> </Tracked>
@@ -356,6 +356,7 @@ const demos: PreviewProps[] = [
function ComponentPreview({title, demos, description, props}: PreviewProps) { function ComponentPreview({title, demos, description, props}: PreviewProps) {
return ( return (
<Card title={title} size="small" type="inner"> <Card title={title} size="small" type="inner">
<TrackingScope scope={title}>
<Layout.Container gap="small"> <Layout.Container gap="small">
<Text type="secondary">{description}</Text> <Text type="secondary">{description}</Text>
<Collapse ghost> <Collapse ghost>
@@ -415,6 +416,7 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
</Collapse.Panel> </Collapse.Panel>
</Collapse> </Collapse>
</Layout.Container> </Layout.Container>
</TrackingScope>
</Card> </Card>
); );
} }

View File

@@ -35,7 +35,7 @@ import {
toggleLeftSidebarVisible, toggleLeftSidebarVisible,
toggleRightSidebarVisible, toggleRightSidebarVisible,
} from '../reducers/application'; } from '../reducers/application';
import {theme, Layout} from 'flipper-plugin'; import {theme, Layout, withTrackingScope} from 'flipper-plugin';
import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen'; import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen';
import SettingsSheet from '../chrome/SettingsSheet'; import SettingsSheet from '../chrome/SettingsSheet';
import WelcomeScreen from './WelcomeScreen'; import WelcomeScreen from './WelcomeScreen';
@@ -98,6 +98,7 @@ export function LeftRailButton({
return ( return (
<Tooltip title={title} placement="right"> <Tooltip title={title} placement="right">
<LeftRailButtonElem <LeftRailButtonElem
title={title}
kind={small ? 'small' : undefined} kind={small ? 'small' : undefined}
type={selected ? 'primary' : 'ghost'} type={selected ? 'primary' : 'ghost'}
icon={iconElement} icon={iconElement}
@@ -119,7 +120,7 @@ const LeftRailDivider = styled(Divider)({
}); });
LeftRailDivider.displayName = 'LeftRailDividier'; LeftRailDivider.displayName = 'LeftRailDividier';
export function LeftRail({ export const LeftRail = withTrackingScope(function LeftRail({
toplevelSelection, toplevelSelection,
setToplevelSelection, setToplevelSelection,
}: ToplevelProps) { }: ToplevelProps) {
@@ -167,7 +168,7 @@ export function LeftRail({
</Layout.Bottom> </Layout.Bottom>
</Layout.Container> </Layout.Container>
); );
} });
function LeftSidebarToggleButton() { function LeftSidebarToggleButton() {
const dispatch = useDispatch(); const dispatch = useDispatch();

View File

@@ -127,9 +127,9 @@ export function SandyApp({logger}: {logger: Logger}) {
{staticView ? ( {staticView ? (
<TrackingScope <TrackingScope
scope={ scope={
staticView.constructor?.name ??
staticView.displayName ?? staticView.displayName ??
staticView.name ?? staticView.name ??
staticView.constructor?.name ??
'unknown static view' 'unknown static view'
}> }>
<ContentContainer> <ContentContainer>

View File

@@ -16,7 +16,7 @@ import {
CodeOutlined, CodeOutlined,
BugOutlined, BugOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import {theme} from 'flipper-plugin'; import {theme, Tracked, TrackingScope} from 'flipper-plugin';
const {Text, Title} = Typography; const {Text, Title} = Typography;
@@ -45,6 +45,7 @@ function Row(props: {
onClick?: () => void; onClick?: () => void;
}) { }) {
return ( return (
<Tracked action={props.title}>
<RowContainer onClick={props.onClick}> <RowContainer onClick={props.onClick}>
<Space size="middle"> <Space size="middle">
{cloneElement(props.icon, { {cloneElement(props.icon, {
@@ -58,6 +59,7 @@ function Row(props: {
</FlexColumn> </FlexColumn>
</Space> </Space>
</RowContainer> </RowContainer>
</Tracked>
); );
} }
@@ -114,6 +116,7 @@ export default function WelcomeScreen({
/> />
} }
onCancel={onClose}> onCancel={onClose}>
<TrackingScope scope="welcomescreen">
<Space <Space
direction="vertical" direction="vertical"
size="middle" size="middle"
@@ -154,6 +157,7 @@ export default function WelcomeScreen({
onClick={openExternal(constants.FEEDBACK_GROUP_LINK)} onClick={openExternal(constants.FEEDBACK_GROUP_LINK)}
/> />
</Space> </Space>
</TrackingScope>
</Modal> </Modal>
); );
} }

View File

@@ -16,9 +16,9 @@ import {
CaretDownOutlined, CaretDownOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import {Glyph, Layout, styled} from '../../ui'; import {Glyph, Layout, styled} from '../../ui';
import {theme} from 'flipper-plugin'; import {theme, useTrackedCallback} from 'flipper-plugin';
import {batch} from 'react-redux'; import {batch} from 'react-redux';
import {Dispatch, useDispatch, useStore} from '../../utils/useStore'; import {useDispatch, useStore} from '../../utils/useStore';
import { import {
canBeDefaultDevice, canBeDefaultDevice,
getAvailableClients, getAvailableClients,
@@ -55,11 +55,34 @@ export function AppSelector() {
uninitializedClients, uninitializedClients,
selectedApp, selectedApp,
} = useStore((state) => state.connections); } = useStore((state) => state.connections);
const onSelectDevice = useTrackedCallback(
'select-device',
(device: BaseDevice) => {
batch(() => {
dispatch(selectDevice(device));
dispatch(selectClient(null));
});
},
[],
);
const onSelectApp = useTrackedCallback(
'select-app',
(device: BaseDevice, client: Client) => {
batch(() => {
dispatch(selectDevice(device));
dispatch(selectClient(client.id));
});
},
[],
);
const entries = computeEntries( const entries = computeEntries(
devices, devices,
dispatch,
clients, clients,
uninitializedClients, uninitializedClients,
onSelectDevice,
onSelectApp,
); );
const client = clients.find((client) => client.id === selectedApp); const client = clients.find((client) => client.id === selectedApp);
@@ -144,9 +167,10 @@ const AppIconContainer = styled.div({
function computeEntries( function computeEntries(
devices: BaseDevice[], devices: BaseDevice[],
dispatch: Dispatch,
clients: Client[], clients: Client[],
uninitializedClients: State['connections']['uninitializedClients'], uninitializedClients: State['connections']['uninitializedClients'],
onSelectDevice: (device: BaseDevice) => void,
onSelectApp: (device: BaseDevice, client: Client) => void,
) { ) {
const entries = devices.filter(canBeDefaultDevice).map((device) => { const entries = devices.filter(canBeDefaultDevice).map((device) => {
const deviceEntry = ( const deviceEntry = (
@@ -155,10 +179,7 @@ function computeEntries(
key={device.serial} key={device.serial}
style={{fontWeight: 'bold'}} style={{fontWeight: 'bold'}}
onClick={() => { onClick={() => {
batch(() => { onSelectDevice(device);
dispatch(selectDevice(device));
dispatch(selectClient(null));
});
}}> }}>
{device.displayTitle()} {device.displayTitle()}
</Menu.Item> </Menu.Item>
@@ -167,10 +188,7 @@ function computeEntries(
<Menu.Item <Menu.Item
key={client.id} key={client.id}
onClick={() => { onClick={() => {
batch(() => { onSelectApp(device, client);
dispatch(selectDevice(device));
dispatch(selectClient(client.id));
});
}}> }}>
<Radio value={client.id}>{client.query.app}</Radio> <Radio value={client.id}>{client.query.app}</Radio>
</Menu.Item> </Menu.Item>

View File

@@ -7,11 +7,17 @@
* @format * @format
*/ */
import React, {useCallback, useMemo} from 'react'; import React, {useMemo} from 'react';
import {AutoComplete, Input, Typography} from 'antd'; import {AutoComplete, Input, Typography} from 'antd';
import {StarFilled, StarOutlined} from '@ant-design/icons'; import {StarFilled, StarOutlined} from '@ant-design/icons';
import {useStore} from '../../utils/useStore'; import {useStore} from '../../utils/useStore';
import {Layout, NUX, useValue} from 'flipper-plugin'; import {
Layout,
NUX,
TrackingScope,
useTrackedCallback,
useValue,
} from 'flipper-plugin';
import {navPluginStateSelector} from '../../chrome/LocationsButton'; import {navPluginStateSelector} from '../../chrome/LocationsButton';
// eslint-disable-next-line flipper/no-relative-imports-across-packages // eslint-disable-next-line flipper/no-relative-imports-across-packages
@@ -25,11 +31,13 @@ export function BookmarkSection() {
const navPlugin = useStore(navPluginStateSelector); const navPlugin = useStore(navPluginStateSelector);
return navPlugin ? ( return navPlugin ? (
<TrackingScope scope="bookmarks">
<NUX <NUX
title="Use bookmarks to directly navigate to a location in the app." title="Use bookmarks to directly navigate to a location in the app."
placement="right"> placement="right">
<BookmarkSectionInput navPlugin={navPlugin} /> <BookmarkSectionInput navPlugin={navPlugin} />
</NUX> </NUX>
</TrackingScope>
) : null; ) : null;
} }
@@ -48,7 +56,9 @@ function BookmarkSectionInput({navPlugin}: {navPlugin: NavigationPlugin}) {
[currentURI, bookmarks, patterns, 20], [currentURI, bookmarks, patterns, 20],
); );
const handleBookmarkClick = useCallback(() => { const handleBookmarkClick = useTrackedCallback(
'bookmark',
() => {
if (isBookmarked) { if (isBookmarked) {
navPlugin.removeBookmark(currentURI); navPlugin.removeBookmark(currentURI);
} else if (currentURI) { } else if (currentURI) {
@@ -57,7 +67,11 @@ function BookmarkSectionInput({navPlugin}: {navPlugin: NavigationPlugin}) {
commonName: null, commonName: null,
}); });
} }
}, [navPlugin, currentURI, isBookmarked]); },
[navPlugin, currentURI, isBookmarked],
);
const navigate = useTrackedCallback('navigate', navPlugin.navigateTo, []);
const bookmarkButton = isBookmarked ? ( const bookmarkButton = isBookmarked ? (
<StarFilled onClick={handleBookmarkClick} /> <StarFilled onClick={handleBookmarkClick} />
@@ -69,7 +83,7 @@ function BookmarkSectionInput({navPlugin}: {navPlugin: NavigationPlugin}) {
<StyledAutoComplete <StyledAutoComplete
dropdownMatchSelectWidth={500} dropdownMatchSelectWidth={500}
value={currentURI} value={currentURI}
onSelect={navPlugin.navigateTo} onSelect={navigate}
style={{flex: 1}} style={{flex: 1}}
options={[ options={[
{ {
@@ -99,7 +113,7 @@ function BookmarkSectionInput({navPlugin}: {navPlugin: NavigationPlugin}) {
navPlugin.currentURI.set(e.target.value); navPlugin.currentURI.set(e.target.value);
}} }}
onPressEnter={() => { onPressEnter={() => {
navPlugin.navigateTo(currentURI); navigate(currentURI);
}} }}
/> />
</StyledAutoComplete> </StyledAutoComplete>

View File

@@ -13,7 +13,7 @@ import {AndroidOutlined, AppleOutlined} from '@ant-design/icons';
import {Store} from '../../reducers'; import {Store} from '../../reducers';
import {useStore} from '../../utils/useStore'; import {useStore} from '../../utils/useStore';
import {launchEmulator} from '../../devices/AndroidDevice'; import {launchEmulator} from '../../devices/AndroidDevice';
import {Layout, renderReactRoot} from 'flipper-plugin'; import {Layout, renderReactRoot, withTrackingScope} from 'flipper-plugin';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import { import {
launchSimulator, launchSimulator,
@@ -31,7 +31,8 @@ export function showEmulatorLauncher(store: Store) {
type GetSimulators = typeof getSimulators; type GetSimulators = typeof getSimulators;
export function LaunchEmulatorDialog({ export const LaunchEmulatorDialog = withTrackingScope(
function LaunchEmulatorDialog({
onClose, onClose,
getSimulators, getSimulators,
}: { }: {
@@ -40,7 +41,9 @@ export function LaunchEmulatorDialog({
}) { }) {
const iosEnabled = useStore((state) => state.settingsState.enableIOS); const iosEnabled = useStore((state) => state.settingsState.enableIOS);
const androidEmulators = useStore((state) => const androidEmulators = useStore((state) =>
state.settingsState.enableAndroid ? state.connections.androidEmulators : [], state.settingsState.enableAndroid
? state.connections.androidEmulators
: [],
); );
const [iosEmulators, setIosEmulators] = useState<IOSDeviceParams[]>([]); const [iosEmulators, setIosEmulators] = useState<IOSDeviceParams[]>([]);
@@ -64,14 +67,14 @@ export function LaunchEmulatorDialog({
<Button <Button
key={name} key={name}
icon={<AndroidOutlined />} icon={<AndroidOutlined />}
onClick={() => { onClick={() =>
launchEmulator(name) launchEmulator(name)
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
message.error('Failed to start emulator: ' + e); message.error('Failed to start emulator: ' + e);
}) })
.finally(onClose); .then(onClose)
}}> }>
{name} {name}
</Button> </Button>
)), )),
@@ -79,14 +82,14 @@ export function LaunchEmulatorDialog({
<Button <Button
key={device.udid} key={device.udid}
icon={<AppleOutlined />} icon={<AppleOutlined />}
onClick={() => { onClick={() =>
launchSimulator(device.udid) launchSimulator(device.udid)
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
message.error('Failed to start simulator: ' + e); message.error('Failed to start simulator: ' + e);
}) })
.finally(onClose); .then(onClose)
}}> }>
{device.name} {device.name}
</Button> </Button>
)), )),
@@ -104,4 +107,5 @@ export function LaunchEmulatorDialog({
</Layout.Container> </Layout.Container>
</Modal> </Modal>
); );
} },
);

View File

@@ -12,7 +12,7 @@ import {Badge, Button, Menu, Tooltip, Typography} from 'antd';
import {InfoIcon, SidebarTitle} from '../LeftSidebar'; import {InfoIcon, SidebarTitle} from '../LeftSidebar';
import {PlusOutlined, MinusOutlined} from '@ant-design/icons'; import {PlusOutlined, MinusOutlined} from '@ant-design/icons';
import {Glyph, Layout, styled} from '../../ui'; import {Glyph, Layout, styled} from '../../ui';
import {theme, NUX} from 'flipper-plugin'; import {theme, NUX, Tracked} from 'flipper-plugin';
import {useDispatch, useStore} from '../../utils/useStore'; import {useDispatch, useStore} from '../../utils/useStore';
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils'; import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
import {ClientPluginDefinition, DevicePluginDefinition} from '../../plugin'; import {ClientPluginDefinition, DevicePluginDefinition} from '../../plugin';
@@ -225,8 +225,9 @@ function ActionButton({
icon={icon} icon={icon}
title={title} title={title}
style={{border: 'none', color: theme.textColorPrimary}} style={{border: 'none', color: theme.textColorPrimary}}
onClick={() => { onClick={(e) => {
onClick(id); onClick(id);
e.stopPropagation();
}} }}
/> />
); );
@@ -273,6 +274,7 @@ const PluginEntry = memo(function PluginEntry({
}, [active]); }, [active]);
return ( return (
<Tracked action={`open:${plugin.id}`}>
<Menu.Item <Menu.Item
key={plugin.id} key={plugin.id}
active={active} active={active}
@@ -293,6 +295,7 @@ const PluginEntry = memo(function PluginEntry({
{hovering && actions} {hovering && actions}
</Layout.Horizontal> </Layout.Horizontal>
</Menu.Item> </Menu.Item>
</Tracked>
); );
}); });

View File

@@ -21,7 +21,7 @@ import {useStore} from '../../utils/useStore';
import {useIsSandy} from '../../sandy-chrome/SandyContext'; import {useIsSandy} from '../../sandy-chrome/SandyContext';
import type {ButtonProps} from 'antd/lib/button'; import type {ButtonProps} from 'antd/lib/button';
import {DownOutlined, CheckOutlined} from '@ant-design/icons'; import {DownOutlined, CheckOutlined} from '@ant-design/icons';
import {theme} from 'flipper-plugin'; import {theme, Tracked} from 'flipper-plugin';
type ButtonType = 'primary' | 'success' | 'warning' | 'danger'; type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
@@ -366,6 +366,7 @@ function ClassicButton(props: Props) {
} }
return ( return (
<Tracked>
<StyledButton <StyledButton
{...restProps} {...restProps}
ref={_ref as any} ref={_ref as any}
@@ -377,6 +378,7 @@ function ClassicButton(props: Props) {
{iconComponent} {iconComponent}
{children} {children}
</StyledButton> </StyledButton>
</Tracked>
); );
} }

View File

@@ -12,6 +12,7 @@ import Tooltip from './Tooltip';
import {colors} from './colors'; import {colors} from './colors';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import React from 'react'; import React from 'react';
import {Tracked} from 'flipper-plugin';
type Props = React.ComponentProps<typeof ToolbarIconContainer> & { type Props = React.ComponentProps<typeof ToolbarIconContainer> & {
active?: boolean; active?: boolean;
@@ -30,6 +31,7 @@ const ToolbarIconContainer = styled.div({
export default function ToolbarIcon({active, icon, title, ...props}: Props) { export default function ToolbarIcon({active, icon, title, ...props}: Props) {
return ( return (
<Tooltip title={title}> <Tooltip title={title}>
<Tracked action={title}>
<ToolbarIconContainer {...props}> <ToolbarIconContainer {...props}>
<Glyph <Glyph
name={icon} name={icon}
@@ -41,6 +43,7 @@ export default function ToolbarIcon({active, icon, title, ...props}: Props) {
} }
/> />
</ToolbarIconContainer> </ToolbarIconContainer>
</Tracked>
</Tooltip> </Tooltip>
); );
} }

View File

@@ -58,6 +58,7 @@ export {
setGlobalInteractionReporter as _setGlobalInteractionReporter, setGlobalInteractionReporter as _setGlobalInteractionReporter,
withTrackingScope, withTrackingScope,
useTrackedCallback, useTrackedCallback,
wrapInteractionHandler as _wrapInteractionHandler,
} from './ui/Tracked'; } from './ui/Tracked';
export {sleep} from './utils/sleep'; export {sleep} from './utils/sleep';

View File

@@ -21,6 +21,7 @@ import {createHash} from 'crypto';
import type {TooltipPlacement} from 'antd/lib/tooltip'; import type {TooltipPlacement} from 'antd/lib/tooltip';
import {SandyPluginInstance} from '../plugin/Plugin'; import {SandyPluginInstance} from '../plugin/Plugin';
import {theme} from './theme'; import {theme} from './theme';
import {Tracked} from './Tracked';
const {Text} = Typography; const {Text} = Typography;
@@ -121,9 +122,11 @@ export function NUX({
style={{color: theme.textColorPrimary}}> style={{color: theme.textColorPrimary}}>
<BulbTwoTone style={{fontSize: 24}} /> <BulbTwoTone style={{fontSize: 24}} />
<Text>{title}</Text> <Text>{title}</Text>
<Tracked action={'nux:dismiss:' + title.substr(0, 50)}>
<Button size="small" type="default" onClick={dismiss}> <Button size="small" type="default" onClick={dismiss}>
Dismiss Dismiss
</Button> </Button>
</Tracked>
</Layout.Container> </Layout.Container>
}> }>
<Pulse /> <Pulse />

View File

@@ -109,7 +109,6 @@ export function useTrackedCallback<T extends Function>(
}, deps) as any; }, deps) as any;
} }
// Exported for test
export function wrapInteractionHandler<T extends Function>( export function wrapInteractionHandler<T extends Function>(
fn: T, fn: T,
element: React.ReactElement | null | string, element: React.ReactElement | null | string,