Migrate Fresco plugin to Sandy

Summary:
Removed the "notifications for leaks" test since we're no longer storing notifications but showing them immediately.

Will do UI updates separately.

Reviewed By: mweststrate

Differential Revision: D28025507

fbshipit-source-id: 58db2504c29bba441b2c2d86cd3e2b8b5c010757
This commit is contained in:
Mathias Fleig Mortensen
2021-05-05 06:03:40 -07:00
committed by Facebook GitHub Bot
parent dd9af302cc
commit 8a5b2c5981
4 changed files with 340 additions and 481 deletions

View File

@@ -1,32 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`notifications for leaks 1`] = `
<React.Fragment>
<Styled(div)>
CloseableReference leaked for
<Text
code={true}
>
com.facebook.imagepipeline.memory.NativeMemoryChunk
</Text>
(identity hashcode:
deadbeef
).
</Styled(div)>
<Styled(div)>
<Text
bold={true}
>
Stacktrace:
</Text>
</Styled(div)>
<Styled(div)>
<Text
code={true}
>
&lt;unavailable&gt;
</Text>
</Styled(div)>
</React.Fragment>
`;

View File

@@ -1,99 +0,0 @@
/**
* 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
*/
import FrescoPlugin from '../index';
import {PersistedState, ImageEventWithId} from '../index';
import {AndroidCloseableReferenceLeakEvent} from '../api';
import {Notification} from 'flipper';
import {ImagesMap} from '../ImagePool';
type ScanDisplayTime = {[scan_number: number]: number};
function mockPersistedState(
imageSizes: Array<{
width: number;
height: number;
}> = [],
viewport: {
width: number;
height: number;
} = {width: 150, height: 150},
): PersistedState {
const scanDisplayTime: ScanDisplayTime = {};
scanDisplayTime[1] = 3;
const events: Array<ImageEventWithId> = [
{
imageIds: [...Array(imageSizes.length).keys()].map(String),
eventId: 0,
attribution: [],
startTime: 1,
endTime: 2,
source: 'source',
coldStart: true,
viewport: {...viewport, scanDisplayTime},
},
];
const imagesMap = imageSizes.reduce((acc, val, index) => {
acc[index] = {
imageId: String(index),
width: val.width,
height: val.height,
sizeBytes: 10,
data: 'undefined',
};
return acc;
}, {} as ImagesMap);
return {
surfaceList: new Set(),
images: [],
events,
imagesMap,
closeableReferenceLeaks: [],
isLeakTrackingEnabled: false,
nextEventId: 0,
showDiskImages: false,
};
}
test('notifications for leaks', () => {
const notificationReducer: (
persistedState: PersistedState,
) => Array<Notification> = FrescoPlugin.getActiveNotifications;
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
{
identityHashCode: 'deadbeef',
className: 'com.facebook.imagepipeline.memory.NativeMemoryChunk',
stacktrace: null,
},
{
identityHashCode: 'f4c3b00c',
className: 'com.facebook.flipper.SomeMemoryAbstraction',
stacktrace: null,
},
];
const persistedStateWithoutTracking = {
...mockPersistedState(),
closeableReferenceLeaks,
isLeakTrackingEnabled: false,
};
const emptyNotifs = notificationReducer(persistedStateWithoutTracking);
expect(emptyNotifs).toHaveLength(0);
const persistedStateWithTracking = {
...mockPersistedState(),
closeableReferenceLeaks,
isLeakTrackingEnabled: true,
};
const notifs = notificationReducer(persistedStateWithTracking);
expect(notifs).toHaveLength(2);
expect(notifs[0].message).toMatchSnapshot();
expect(notifs[1].title).toContain('SomeMemoryAbstraction');
});

View File

@@ -19,43 +19,23 @@ import {
} from './api'; } from './api';
import {Fragment} from 'react'; import {Fragment} from 'react';
import {ImagesMap} from './ImagePool'; import {ImagesMap} from './ImagePool';
import {ReduxState} from 'flipper'; import {PluginClient, createState, usePlugin, useValue} from 'flipper-plugin';
import React from 'react'; import React from 'react';
import ImagesCacheOverview from './ImagesCacheOverview'; import ImagesCacheOverview from './ImagesCacheOverview';
import { import {
FlipperPlugin,
FlexRow, FlexRow,
Text, Text,
DetailSidebar, DetailSidebar,
colors, colors,
styled, styled,
isProduction, isProduction,
Notification,
BaseAction,
} from 'flipper'; } from 'flipper';
import ImagesSidebar from './ImagesSidebar'; import ImagesSidebar from './ImagesSidebar';
import ImagePool from './ImagePool'; import ImagePool from './ImagePool';
export type ImageEventWithId = ImageEvent & {eventId: number}; export type ImageEventWithId = ImageEvent & {eventId: number};
export type AllImageEventsInfo = {
export type PersistedState = {
surfaceList: Set<string>;
images: ImagesList;
events: Array<ImageEventWithId>; events: Array<ImageEventWithId>;
imagesMap: ImagesMap;
closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>;
isLeakTrackingEnabled: boolean;
showDiskImages: boolean;
nextEventId: number;
};
type PluginState = {
selectedSurfaces: Set<string>;
selectedImage: ImageId | null;
isDebugOverlayEnabled: boolean;
isAutoRefreshEnabled: boolean;
images: ImagesList;
coldStartFilter: boolean;
}; };
const EmptySidebar = styled(FlexRow)({ const EmptySidebar = styled(FlexRow)({
@@ -79,141 +59,57 @@ const debugLog = (...args: any[]) => {
} }
}; };
export default class FlipperImagesPlugin extends FlipperPlugin< type Methods = {
PluginState, getAllImageEventsInfo(params: {}): Promise<AllImageEventsInfo>;
BaseAction, listImages(params: {showDiskImages: boolean}): Promise<ImagesListResponse>;
PersistedState getImage(params: {imageId: string}): Promise<ImageData>;
> { clear(params: {type: string}): Promise<void>;
static defaultPersistedState: PersistedState = { trimMemory(params: {}): Promise<void>;
images: [], enableDebugOverlay(params: {enabled: boolean}): Promise<void>;
events: [], };
imagesMap: {},
surfaceList: new Set(),
closeableReferenceLeaks: [],
isLeakTrackingEnabled: false,
showDiskImages: false,
nextEventId: 0,
};
static exportPersistedState = ( type Events = {
callClient: undefined | ((method: string, params?: any) => Promise<any>), closeable_reference_leak_event: AndroidCloseableReferenceLeakEvent;
persistedState: PersistedState, events: ImageEvent;
store?: ReduxState, debug_overlay_event: FrescoDebugOverlayEvent;
): Promise<PersistedState> => { };
const defaultPromise = Promise.resolve(persistedState);
if (!persistedState) {
persistedState = FlipperImagesPlugin.defaultPersistedState;
}
if (!store || !callClient) {
return defaultPromise;
}
return Promise.all([
callClient('listImages', {showDiskImages: persistedState.showDiskImages}),
callClient('getAllImageEventsInfo'),
]).then(async ([responseImages, responseEvents]) => {
const levels: ImagesList = responseImages.levels;
const events: Array<ImageEventWithId> = responseEvents.events;
let pluginData: PersistedState = {
...persistedState,
images: persistedState ? [...persistedState.images, ...levels] : levels,
closeableReferenceLeaks:
(persistedState && persistedState.closeableReferenceLeaks) || [],
};
events.forEach((event: ImageEventWithId, index) => { export function plugin(client: PluginClient<Events, Methods>) {
if (!event) { const selectedSurfaces = createState<Set<string>>(
return; new Set([surfaceDefaultText]),
} );
const {attribution} = event; const currentSelectedImage = createState<ImageId | null>(null);
if ( const isDebugOverlayEnabled = createState<boolean>(false);
attribution && const isAutoRefreshEnabled = createState<boolean>(false);
attribution instanceof Array && const currentImages = createState<ImagesList>([]);
attribution.length > 0 const coldStartFilter = createState<boolean>(false);
) { const imagePool = createState<ImagePool | null>(null);
const surface = attribution[0] ? attribution[0].trim() : undefined;
if (surface && surface.length > 0) {
pluginData.surfaceList = new Set([
...pluginData.surfaceList,
surface,
]);
}
}
pluginData = {
...pluginData,
events: [{...event, eventId: index}, ...pluginData.events],
};
});
const idSet: Set<string> = levels.reduce((acc, level: CacheInfo) => {
level.imageIds.forEach((id) => {
acc.add(id);
});
return acc;
}, new Set<string>());
const imageDataList: Array<ImageData> = [];
for (const id of idSet) {
try {
const imageData: ImageData = await callClient('getImage', {
imageId: id,
});
imageDataList.push(imageData);
} catch (e) {
console.error(e);
}
}
imageDataList.forEach((data: ImageData) => {
const imagesMap = {...pluginData.imagesMap};
imagesMap[data.imageId] = data;
pluginData.imagesMap = imagesMap;
});
return pluginData;
});
};
static persistedStateReducer = ( const surfaceList = createState<Set<string>>(new Set(), {
persistedState: PersistedState, persist: 'surfaceList',
method: string, });
data: AndroidCloseableReferenceLeakEvent | ImageEvent, const images = createState<ImagesList>([], {persist: 'images'});
): PersistedState => { const events = createState<Array<ImageEventWithId>>([], {persist: 'events'});
if (method == 'closeable_reference_leak_event') { const imagesMap = createState<ImagesMap>({}, {persist: 'imagesMap'});
const event: AndroidCloseableReferenceLeakEvent = data as AndroidCloseableReferenceLeakEvent; const isLeakTrackingEnabled = createState<boolean>(false, {
return { persist: 'isLeakTrackingEnabled',
...persistedState, });
closeableReferenceLeaks: persistedState.closeableReferenceLeaks.concat( const showDiskImages = createState<boolean>(false, {
event, persist: 'showDiskImages',
), });
}; const nextEventId = createState<number>(0, {persist: 'nextEventId'});
} else if (method == 'events') {
const event: ImageEvent = data as ImageEvent;
debugLog('Received events', event);
let {surfaceList} = persistedState;
const {attribution} = event;
if (attribution instanceof Array && attribution.length > 0) {
const surface = attribution[0] ? attribution[0].trim() : undefined;
if (surface && surface.length > 0) {
surfaceList = new Set([...surfaceList, surface]);
}
}
return {
...persistedState,
surfaceList,
events: [
{eventId: persistedState.nextEventId, ...event},
...persistedState.events,
],
nextEventId: persistedState.nextEventId + 1,
};
}
return persistedState; client.onConnect(() => {
}; init();
});
static getActiveNotifications = ({ client.onDestroy(() => {
closeableReferenceLeaks = [], imagePool?.get()?.clear();
isLeakTrackingEnabled = false, });
}: PersistedState): Array<Notification> =>
closeableReferenceLeaks client.onMessage('closeable_reference_leak_event', (event) => {
.filter((_) => isLeakTrackingEnabled) if (isLeakTrackingEnabled) {
.map((event: AndroidCloseableReferenceLeakEvent) => ({ client.showNotification({
id: event.identityHashCode, id: event.identityHashCode,
title: `Leaked CloseableReference: ${event.className}`, title: `Leaked CloseableReference: ${event.className}`,
message: ( message: (
@@ -233,25 +129,183 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
), ),
severity: 'error', severity: 'error',
category: 'closeablereference_leak', category: 'closeablereference_leak',
})); });
}
});
state: PluginState = { client.onExport(async () => {
selectedSurfaces: new Set([surfaceDefaultText]), const [responseImages, responseEvents] = await Promise.all([
selectedImage: null, client.send('listImages', {showDiskImages: showDiskImages.get()}),
isDebugOverlayEnabled: false, client.send('getAllImageEventsInfo', {}),
isAutoRefreshEnabled: false, ]);
images: [], const levels: ImagesList = responseImages.levels;
coldStartFilter: false, const newEvents: Array<ImageEventWithId> = responseEvents.events;
};
imagePool: ImagePool | undefined;
nextEventId: number = 1;
filterImages = ( images.set([...images.get(), ...levels]);
newEvents.forEach((event: ImageEventWithId, index) => {
if (!event) {
return;
}
const {attribution} = event;
if (
attribution &&
attribution instanceof Array &&
attribution.length > 0
) {
const surface = attribution[0] ? attribution[0].trim() : undefined;
if (surface && surface.length > 0) {
surfaceList.set(new Set([...surfaceList.get(), surface]));
}
}
events.set([{...event, eventId: index}, ...events.get()]);
});
const idSet: Set<string> = levels.reduce((acc, level: CacheInfo) => {
level.imageIds.forEach((id) => {
acc.add(id);
});
return acc;
}, new Set<string>());
const imageDataList: Array<ImageData> = [];
for (const id of idSet) {
try {
const imageData: ImageData = await client.send('getImage', {
imageId: id,
});
imageDataList.push(imageData);
} catch (e) {
console.error(e);
}
}
const imagesMapCopy = {...imagesMap.get()};
imageDataList.forEach((data: ImageData) => {
imagesMapCopy[data.imageId] = data;
});
imagesMap.set(imagesMapCopy);
});
client.onMessage('debug_overlay_event', (event) => {
isDebugOverlayEnabled.set(event.enabled);
});
client.onMessage('events', (event) => {
debugLog('Received events', event);
const {attribution} = event;
if (attribution instanceof Array && attribution.length > 0) {
const surface = attribution[0] ? attribution[0].trim() : undefined;
if (surface && surface.length > 0) {
surfaceList.update((draft) => (draft = new Set([...draft, surface])));
}
}
events.update((draft) => {
draft.unshift({
eventId: nextEventId.get(),
...event,
});
});
nextEventId.set(nextEventId.get() + 1);
});
function onClear(type: string) {
client.send('clear', {type});
setTimeout(() => updateCaches('onClear'), 1000);
}
function onTrimMemory() {
client.send('trimMemory', {});
setTimeout(() => updateCaches('onTrimMemory'), 1000);
}
function onEnableDebugOverlay(enabled: boolean) {
client.send('enableDebugOverlay', {enabled});
}
function onEnableAutoRefresh(enabled: boolean) {
isAutoRefreshEnabled.set(enabled);
if (enabled) {
// Delay the call just enough to allow the state change to complete.
setTimeout(() => onAutoRefresh());
}
}
function onAutoRefresh() {
updateCaches('auto-refresh');
if (isAutoRefreshEnabled.get()) {
setTimeout(() => onAutoRefresh(), 1000);
}
}
function getImage(imageId: string) {
if (!client.isConnected) {
debugLog(`Cannot fetch image ${imageId}: disconnected`);
return;
}
debugLog('<- getImage requested for ' + imageId);
client.send('getImage', {imageId}).then((image: ImageData) => {
debugLog('-> getImage ' + imageId + ' returned');
imagePool.get()?._fetchCompleted(image);
});
}
function onImageSelected(selectedImage: ImageId) {
currentSelectedImage.set(selectedImage);
}
function onSurfaceChange(surfaces: Set<string>) {
updateImagesOnUI(images.get(), surfaces, coldStartFilter.get());
}
function onColdStartChange(checked: boolean) {
updateImagesOnUI(images.get(), selectedSurfaces.get(), checked);
}
function onTrackLeaks(checked: boolean) {
client.logger.track('usage', 'fresco:onTrackLeaks', {
enabled: checked,
});
isLeakTrackingEnabled.set(checked);
}
function onShowDiskImages(checked: boolean) {
client.logger.track('usage', 'fresco:onShowDiskImages', {
enabled: checked,
});
showDiskImages.set(checked);
updateCaches('refresh');
}
function init() {
debugLog('init()');
if (client.isConnected) {
updateCaches('init');
} else {
debugLog(`not connected)`);
}
imagePool.set(
new ImagePool(getImage, (images: ImagesMap) => imagesMap.set(images)),
);
const filteredImages = filterImages(
images.get(),
events.get(),
selectedSurfaces.get(),
coldStartFilter.get(),
);
images.set(filteredImages);
}
function filterImages(
images: ImagesList, images: ImagesList,
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>,
surfaces: Set<string>, surfaces: Set<string>,
coldStart: boolean, coldStart: boolean,
): ImagesList => { ): ImagesList {
if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) { if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) {
return images; return images;
} }
@@ -280,216 +334,149 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
return {...image, imageIds: imageIdList}; return {...image, imageIds: imageIdList};
}); });
return imageList; return imageList;
};
init() {
debugLog('init()');
if (this.client.isConnected) {
this.updateCaches('init');
this.client.subscribe(
'debug_overlay_event',
(event: FrescoDebugOverlayEvent) => {
this.setState({isDebugOverlayEnabled: event.enabled});
},
);
} else {
debugLog(`not connected)`);
}
this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) =>
this.props.setPersistedState({imagesMap: images}),
);
const images = this.filterImages(
this.props.persistedState.images,
this.props.persistedState.events,
this.state.selectedSurfaces,
this.state.coldStartFilter,
);
this.setState({images});
} }
teardown() { function updateImagesOnUI(
this.imagePool ? this.imagePool.clear() : undefined; newImages: ImagesList,
}
updateImagesOnUI = (
images: ImagesList,
surfaces: Set<string>, surfaces: Set<string>,
coldStart: boolean, coldStart: boolean,
) => { ) {
const filteredImages = this.filterImages( const filteredImages = filterImages(
images, newImages,
this.props.persistedState.events, events.get(),
surfaces, surfaces,
coldStart, coldStart,
); );
this.setState({ selectedSurfaces.set(surfaces);
selectedSurfaces: surfaces, images.set(filteredImages);
images: filteredImages, coldStartFilter.set(coldStart);
coldStartFilter: coldStart, }
});
}; function updateCaches(reason: string) {
updateCaches = (reason: string) => {
debugLog('Requesting images list (reason=' + reason + ')'); debugLog('Requesting images list (reason=' + reason + ')');
this.client client
.call('listImages', { .send('listImages', {
showDiskImages: this.props.persistedState.showDiskImages, showDiskImages: showDiskImages.get(),
}) })
.then((response: ImagesListResponse) => { .then((response: ImagesListResponse) => {
response.levels.forEach((data) => response.levels.forEach((data) =>
this.imagePool imagePool?.get()?.fetchImages(data.imageIds),
? this.imagePool.fetchImages(data.imageIds)
: undefined,
); );
this.props.setPersistedState({images: response.levels}); images.set(response.levels);
this.updateImagesOnUI( updateImagesOnUI(
this.props.persistedState.images, images.get(),
this.state.selectedSurfaces, selectedSurfaces.get(),
this.state.coldStartFilter, coldStartFilter.get(),
); );
}); });
}
return {
selectedSurfaces,
currentSelectedImage,
isDebugOverlayEnabled,
isAutoRefreshEnabled,
currentImages,
coldStartFilter,
surfaceList,
images,
events,
imagesMap,
isLeakTrackingEnabled,
showDiskImages,
nextEventId,
imagePool,
onSurfaceChange,
onColdStartChange,
onClear,
onTrimMemory,
updateCaches,
onEnableDebugOverlay,
onEnableAutoRefresh,
onImageSelected,
onTrackLeaks,
onShowDiskImages,
}; };
}
onClear = (type: string) => { export function Component() {
this.client.call('clear', {type}); const instance = usePlugin(plugin);
setTimeout(() => this.updateCaches('onClear'), 1000);
};
onTrimMemory = () => { let selectedSurfaces = useValue(instance.selectedSurfaces);
this.client.call('trimMemory', {}); const isDebugOverlayEnabled = useValue(instance.isDebugOverlayEnabled);
setTimeout(() => this.updateCaches('onTrimMemory'), 1000); const isAutoRefreshEnabled = useValue(instance.isAutoRefreshEnabled);
}; const coldStartFilter = useValue(instance.coldStartFilter);
onEnableDebugOverlay = (enabled: boolean) => { const surfaceList = useValue(instance.surfaceList);
this.client.call('enableDebugOverlay', {enabled}); const images = useValue(instance.images);
}; const events = useValue(instance.events);
const imagesMap = useValue(instance.imagesMap);
const isLeakTrackingEnabled = useValue(instance.isLeakTrackingEnabled);
const showDiskImages = useValue(instance.showDiskImages);
onEnableAutoRefresh = (enabled: boolean) => { const options = [...surfaceList].reduce(
this.setState({isAutoRefreshEnabled: enabled}); (acc, item) => {
if (enabled) { return [...acc, item];
// Delay the call just enough to allow the state change to complete. },
setTimeout(() => this.onAutoRefresh()); [surfaceDefaultText],
} );
};
onAutoRefresh = () => { if (selectedSurfaces.has(surfaceDefaultText)) {
this.updateCaches('auto-refresh'); selectedSurfaces = new Set(options);
if (this.state.isAutoRefreshEnabled) { }
setTimeout(() => this.onAutoRefresh(), 1000);
}
};
getImage = (imageId: string) => { return (
if (!this.client.isConnected) { <React.Fragment>
debugLog(`Cannot fetch image ${imageId}: disconnected`); <ImagesCacheOverview
return; allSurfacesOption={surfaceDefaultText}
} surfaceOptions={new Set(options)}
debugLog('<- getImage requested for ' + imageId); selectedSurfaces={selectedSurfaces}
this.client.call('getImage', {imageId}).then((image: ImageData) => { onChangeSurface={instance.onSurfaceChange}
debugLog('-> getImage ' + imageId + ' returned'); coldStartFilter={coldStartFilter}
this.imagePool ? this.imagePool._fetchCompleted(image) : undefined; onColdStartChange={instance.onColdStartChange}
}); images={images}
}; onClear={instance.onClear}
onTrimMemory={instance.onTrimMemory}
onRefresh={() => instance.updateCaches('refresh')}
onEnableDebugOverlay={instance.onEnableDebugOverlay}
onEnableAutoRefresh={instance.onEnableAutoRefresh}
isDebugOverlayEnabled={isDebugOverlayEnabled}
isAutoRefreshEnabled={isAutoRefreshEnabled}
onImageSelected={instance.onImageSelected}
imagesMap={imagesMap}
events={events}
isLeakTrackingEnabled={isLeakTrackingEnabled}
onTrackLeaks={instance.onTrackLeaks}
showDiskImages={showDiskImages}
onShowDiskImages={instance.onShowDiskImages}
/>
<DetailSidebar>
<Sidebar />
</DetailSidebar>
</React.Fragment>
);
}
onImageSelected = (selectedImage: ImageId) => this.setState({selectedImage}); function Sidebar() {
const instance = usePlugin(plugin);
renderSidebar = () => { const events = useValue(instance.events);
const {selectedImage} = this.state; const imagesMap = useValue(instance.imagesMap);
const currentSelectedImage = useValue(instance.currentSelectedImage);
if (selectedImage == null) {
return (
<EmptySidebar grow={true}>
<Text align="center">
Select an image to see the events associated with it.
</Text>
</EmptySidebar>
);
}
const maybeImage = this.props.persistedState.imagesMap[selectedImage];
const events = this.props.persistedState.events.filter((e) =>
e.imageIds.includes(selectedImage),
);
return <ImagesSidebar image={maybeImage} events={events} />;
};
onSurfaceChange = (surfaces: Set<string>) => {
this.updateImagesOnUI(
this.props.persistedState.images,
surfaces,
this.state.coldStartFilter,
);
};
onColdStartChange = (checked: boolean) => {
this.updateImagesOnUI(
this.props.persistedState.images,
this.state.selectedSurfaces,
checked,
);
};
onTrackLeaks = (checked: boolean) => {
this.props.logger.track('usage', 'fresco:onTrackLeaks', {enabled: checked});
this.props.setPersistedState({
isLeakTrackingEnabled: checked,
});
};
onShowDiskImages = (checked: boolean) => {
this.props.logger.track('usage', 'fresco:onShowDiskImages', {
enabled: checked,
});
this.props.setPersistedState({
showDiskImages: checked,
});
this.updateCaches('refresh');
};
render() {
const options = [...this.props.persistedState.surfaceList].reduce(
(acc, item) => {
return [...acc, item];
},
[surfaceDefaultText],
);
let {selectedSurfaces} = this.state;
if (selectedSurfaces.has(surfaceDefaultText)) {
selectedSurfaces = new Set(options);
}
if (currentSelectedImage == null) {
return ( return (
<React.Fragment> <EmptySidebar grow={true}>
<ImagesCacheOverview <Text align="center">
allSurfacesOption={surfaceDefaultText} Select an image to see the events associated with it.
surfaceOptions={new Set(options)} </Text>
selectedSurfaces={selectedSurfaces} </EmptySidebar>
onChangeSurface={this.onSurfaceChange}
coldStartFilter={this.state.coldStartFilter}
onColdStartChange={this.onColdStartChange}
images={this.state.images}
onClear={this.onClear}
onTrimMemory={this.onTrimMemory}
onRefresh={() => this.updateCaches('refresh')}
onEnableDebugOverlay={this.onEnableDebugOverlay}
onEnableAutoRefresh={this.onEnableAutoRefresh}
isDebugOverlayEnabled={this.state.isDebugOverlayEnabled}
isAutoRefreshEnabled={this.state.isAutoRefreshEnabled}
onImageSelected={this.onImageSelected}
imagesMap={this.props.persistedState.imagesMap}
events={this.props.persistedState.events}
isLeakTrackingEnabled={
this.props.persistedState.isLeakTrackingEnabled
}
onTrackLeaks={this.onTrackLeaks}
showDiskImages={this.props.persistedState.showDiskImages}
onShowDiskImages={this.onShowDiskImages}
/>
<DetailSidebar>{this.renderSidebar()}</DetailSidebar>
</React.Fragment>
); );
} }
const maybeImage = imagesMap[currentSelectedImage];
const filteredEvents = events.filter((e) =>
e.imageIds.includes(currentSelectedImage),
);
return <ImagesSidebar image={maybeImage} events={filteredEvents} />;
} }

View File

@@ -13,5 +13,8 @@
"icon": "profile", "icon": "profile",
"bugs": { "bugs": {
"email": "oncall+fresco@xmail.facebook.com" "email": "oncall+fresco@xmail.facebook.com"
},
"peerDependencies": {
"flipper-plugin": "*"
} }
} }