persist network plugin state

Summary:
This saved the state of the network plugin even when switching between plugins using persistedState.
A bug in the Android implementation didn't clear the events that were already sent to the desktop.

Reviewed By: jknoxville

Differential Revision: D8752098

fbshipit-source-id: 152ec5da83958ad8124686f780d39983cbce563f
This commit is contained in:
Daniel Büchele
2018-07-10 02:22:18 -07:00
committed by Facebook Github Bot
parent f5dcaf02a4
commit c239fcac01
6 changed files with 144 additions and 132 deletions

View File

@@ -49,9 +49,10 @@ public abstract class BufferingSonarPlugin implements SonarPlugin {
if (mEventQueue == null) { if (mEventQueue == null) {
mEventQueue = new RingBuffer<>(BUFFER_SIZE); mEventQueue = new RingBuffer<>(BUFFER_SIZE);
} }
mEventQueue.enqueue(new CachedSonarEvent(method, sonarObject));
if (mConnection != null) { if (mConnection != null) {
mConnection.send(method, sonarObject); mConnection.send(method, sonarObject);
} else {
mEventQueue.enqueue(new CachedSonarEvent(method, sonarObject));
} }
} }
@@ -60,6 +61,7 @@ public abstract class BufferingSonarPlugin implements SonarPlugin {
for (CachedSonarEvent cachedSonarEvent : mEventQueue.asList()) { for (CachedSonarEvent cachedSonarEvent : mEventQueue.asList()) {
mConnection.send(cachedSonarEvent.method, cachedSonarEvent.sonarObject); mConnection.send(cachedSonarEvent.method, cachedSonarEvent.sonarObject);
} }
mEventQueue.clear();
} }
} }

View File

@@ -25,6 +25,10 @@ final class RingBuffer<T> {
mBuffer.add(item); mBuffer.add(item);
} }
void clear() {
mBuffer.clear();
}
List<T> asList() { List<T> asList() {
return mBuffer; return mBuffer;
} }

View File

@@ -84,7 +84,11 @@ export class SonarBasePlugin<
} }
} }
export class SonarDevicePlugin<S = *, A = *> extends SonarBasePlugin<S, A> { export class SonarDevicePlugin<S = *, A = *, P = *> extends SonarBasePlugin<
S,
A,
P,
> {
device: BaseDevice; device: BaseDevice;
_setup(target: PluginTarget) { _setup(target: PluginTarget) {
@@ -100,7 +104,7 @@ export class SonarDevicePlugin<S = *, A = *> extends SonarBasePlugin<S, A> {
} }
} }
export class SonarPlugin<S = *, A = *> extends SonarBasePlugin<S, A> { export class SonarPlugin<S = *, A = *, P = *> extends SonarBasePlugin<S, A, P> {
constructor() { constructor() {
super(); super();
this.subscriptions = []; this.subscriptions = [];

View File

@@ -5,7 +5,7 @@
* @format * @format
*/ */
import type {TableHighlightedRows, TableRows} from 'sonar'; import type {TableHighlightedRows, TableRows, TableBodyRow} from 'sonar';
import { import {
ContextMenu, ContextMenu,
@@ -17,19 +17,18 @@ import {
PureComponent, PureComponent,
SonarSidebar, SonarSidebar,
} from 'sonar'; } from 'sonar';
import {SonarPlugin, SearchableTable} from 'sonar'; import {SonarPlugin, SearchableTable} from 'sonar';
import RequestDetails from './RequestDetails.js'; import RequestDetails from './RequestDetails.js';
import {URL} from 'url'; import {URL} from 'url';
// $FlowFixMe
import sortBy from 'lodash.sortby';
type RequestId = string; type RequestId = string;
type State = {| type PersistedState = {|
requests: {[id: RequestId]: Request}, requests: {[id: RequestId]: Request},
responses: {[id: RequestId]: Response}, responses: {[id: RequestId]: Response},
|};
type State = {|
selectedIds: Array<RequestId>, selectedIds: Array<RequestId>,
|}; |};
@@ -109,7 +108,7 @@ const TextEllipsis = Text.extends({
paddingTop: 4, paddingTop: 4,
}); });
export default class extends SonarPlugin<State> { export default class extends SonarPlugin<State, *, PersistedState> {
static title = 'Network'; static title = 'Network';
static id = 'Network'; static id = 'Network';
static icon = 'internet'; static icon = 'internet';
@@ -122,53 +121,39 @@ export default class extends SonarPlugin<State> {
}; };
state = { state = {
requests: {},
responses: {},
selectedIds: [], selectedIds: [],
}; };
init() { init() {
this.client.subscribe('newRequest', (request: Request) => { this.client.subscribe('newRequest', (request: Request) => {
this.dispatchAction({request, type: 'NewRequest'}); this.props.setPersistedState({
requests: {
...this.props.persistedState.requests,
[request.id]: request,
},
});
}); });
this.client.subscribe('newResponse', (response: Response) => { this.client.subscribe('newResponse', (response: Response) => {
this.dispatchAction({response, type: 'NewResponse'}); this.props.setPersistedState({
responses: {
...this.props.persistedState.responses,
[response.id]: response,
},
});
}); });
} }
reducers = {
NewRequest(state: State, {request}: {request: Request}) {
return {
requests: {...state.requests, [request.id]: request},
responses: state.responses,
};
},
NewResponse(state: State, {response}: {response: Response}) {
return {
requests: state.requests,
responses: {...state.responses, [response.id]: response},
};
},
Clear(state: State) {
return {
requests: {},
responses: {},
};
},
};
onRowHighlighted = (selectedIds: Array<RequestId>) => onRowHighlighted = (selectedIds: Array<RequestId>) =>
this.setState({selectedIds}); this.setState({selectedIds});
clearLogs = () => { clearLogs = () => {
this.setState({selectedIds: []}); this.setState({selectedIds: []});
this.dispatchAction({type: 'Clear'}); this.props.setPersistedState({responses: {}, requests: {}});
}; };
renderSidebar = () => { renderSidebar = () => {
const {selectedIds, requests, responses} = this.state; const {requests, responses} = this.props.persistedState;
const {selectedIds} = this.state;
const selectedId = selectedIds.length === 1 ? selectedIds[0] : null; const selectedId = selectedIds.length === 1 ? selectedIds[0] : null;
return selectedId != null ? ( return selectedId != null ? (
@@ -181,11 +166,13 @@ export default class extends SonarPlugin<State> {
}; };
render() { render() {
const {requests, responses} = this.props.persistedState;
return ( return (
<FlexColumn fill={true}> <FlexColumn fill={true}>
<NetworkTable <NetworkTable
requests={this.state.requests} requests={requests || {}}
responses={this.state.responses} responses={responses || {}}
clear={this.clearLogs} clear={this.clearLogs}
onRowHighlighted={this.onRowHighlighted} onRowHighlighted={this.onRowHighlighted}
/> />
@@ -195,53 +182,18 @@ export default class extends SonarPlugin<State> {
} }
} }
type NetworkTableProps = {| type NetworkTableProps = {
requests: {[id: RequestId]: Request}, requests: {[id: RequestId]: Request},
responses: {[id: RequestId]: Response}, responses: {[id: RequestId]: Response},
clear: () => void, clear: () => void,
onRowHighlighted: (keys: TableHighlightedRows) => void, onRowHighlighted: (keys: TableHighlightedRows) => void,
|}; };
type NetworkTableState = {| type NetworkTableState = {|
sortedRows: TableRows, sortedRows: TableRows,
|}; |};
class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> { function buildRow(request: Request, response: ?Response): ?TableBodyRow {
static ContextMenu = ContextMenu.extends({
flex: 1,
});
state = {
sortedRows: [],
};
componentWillReceiveProps(nextProps: NetworkTableProps) {
if (Object.keys(nextProps.requests).length === 0) {
// cleared
this.setState({sortedRows: []});
} else if (this.props.requests !== nextProps.requests) {
// new request
for (const requestId in nextProps.requests) {
if (this.props.requests[requestId] == null) {
this.buildRow(nextProps.requests[requestId], null);
break;
}
}
} else if (this.props.responses !== nextProps.responses) {
// new response
for (const responseId in nextProps.responses) {
if (this.props.responses[responseId] == null) {
this.buildRow(
nextProps.requests[responseId],
nextProps.responses[responseId],
);
break;
}
}
}
}
buildRow(request: Request, response: ?Response) {
if (request == null) { if (request == null) {
return; return;
} }
@@ -249,7 +201,7 @@ class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
const domain = url.host + url.pathname; const domain = url.host + url.pathname;
const friendlyName = getHeaderValue(request.headers, 'X-FB-Friendly-Name'); const friendlyName = getHeaderValue(request.headers, 'X-FB-Friendly-Name');
const newRow = { return {
columns: { columns: {
domain: { domain: {
value: ( value: (
@@ -263,9 +215,7 @@ class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
}, },
status: { status: {
value: ( value: (
<StatusColumn> <StatusColumn>{response ? response.status : undefined}</StatusColumn>
{response ? response.status : undefined}
</StatusColumn>
), ),
isFilterable: true, isFilterable: true,
}, },
@@ -282,21 +232,78 @@ class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
copyText: request.url, copyText: request.url,
highlightOnHover: true, highlightOnHover: true,
}; };
}
let rows; function calculateState(
if (response == null) { props: {
rows = [...this.state.sortedRows, newRow]; requests: {[id: RequestId]: Request},
} else { responses: {[id: RequestId]: Response},
const index = this.state.sortedRows.findIndex(r => r.key === request.id); },
if (index > -1) { nextProps: NetworkTableProps,
rows = [...this.state.sortedRows]; rows: TableRows = [],
): NetworkTableState {
rows = [...rows];
if (Object.keys(nextProps.requests).length === 0) {
// cleared
rows = [];
} else if (props.requests !== nextProps.requests) {
// new request
for (const requestId in nextProps.requests) {
if (props.requests[requestId] == null) {
const newRow = buildRow(
nextProps.requests[requestId],
nextProps.responses[requestId],
);
if (newRow) {
rows.push(newRow);
}
}
}
} else if (props.responses !== nextProps.responses) {
// new response
for (const responseId in nextProps.responses) {
if (props.responses[responseId] == null) {
const newRow = buildRow(
nextProps.requests[responseId],
nextProps.responses[responseId],
);
const index = rows.findIndex(
r => r.key === nextProps.requests[responseId].id,
);
if (index > -1 && newRow) {
rows[index] = newRow; rows[index] = newRow;
} }
break;
}
}
} }
this.setState({ rows.sort((a, b) => (String(a.sortKey) > String(b.sortKey) ? 1 : -1));
sortedRows: sortBy(rows, x => x.sortKey),
return {
sortedRows: rows,
};
}
class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
static ContextMenu = ContextMenu.extends({
flex: 1,
}); });
constructor(props: NetworkTableProps) {
super(props);
this.state = calculateState(
{
requests: {},
responses: {},
},
props,
);
}
componentWillReceiveProps(nextProps: NetworkTableProps) {
this.setState(calculateState(this.props, nextProps, this.state.sortedRows));
} }
contextMenuItems = [ contextMenuItems = [

View File

@@ -4,7 +4,6 @@
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lodash.sortby": "^4.7.0",
"pako": "^1.0.6" "pako": "^1.0.6"
} }
} }

View File

@@ -2,10 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
pako@^1.0.6: pako@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"