Dependency-inject `LocalStore` (#522)

Summary:
This commit modifies components that directly depend on the
browser-specific local store implementation to instead have their
dependencies injected.

Test Plan:
Tests pass, but are likely not sufficient. Manual testing indicates that
the local storage still works, for both reads and writes, on a fresh
profile or with existing data, for both the repository owner/name and
the weight configuration.

wchargin-branch: di-localstore
This commit is contained in:
William Chargin 2018-07-24 18:56:36 -07:00 committed by GitHub
parent 1fa039ba6c
commit 801b4ec700
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 29 deletions

View File

@ -3,7 +3,8 @@
import React from "react";
import {StyleSheet, css} from "aphrodite/no-important";
import LocalStore from "./LocalStore";
import type {LocalStore} from "../localStore";
import BrowserLocalStore from "../browserLocalStore";
import {StaticPluginAdapter as GithubAdapter} from "../../plugins/github/pluginAdapter";
import {StaticPluginAdapter as GitAdapter} from "../../plugins/git/pluginAdapter";
import {Graph} from "../../core/graph";
@ -16,7 +17,22 @@ import type {PagerankNodeDecomposition} from "../../core/attribution/pagerankNod
import * as NullUtil from "../../util/null";
type Props = {||};
const REPO_OWNER_KEY = "repoOwner";
const REPO_NAME_KEY = "repoName";
const MAX_ENTRIES_PER_LIST = 100;
export default class AppPage extends React.Component<{||}> {
static _LOCAL_STORE = new BrowserLocalStore({
version: "1",
keyPrefix: "cred-explorer",
});
render() {
return <App localStore={AppPage._LOCAL_STORE} />;
}
}
type Props = {|+localStore: LocalStore|};
type State = {
repoOwner: string,
repoName: string,
@ -32,11 +48,7 @@ type State = {
edgeEvaluator: ?EdgeEvaluator,
};
const REPO_OWNER_KEY = "repoOwner";
const REPO_NAME_KEY = "repoName";
const MAX_ENTRIES_PER_LIST = 100;
export default class App extends React.Component<Props, State> {
export class App extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
@ -48,13 +60,15 @@ export default class App extends React.Component<Props, State> {
}
componentDidMount() {
const {localStore} = this.props;
this.setState((state) => ({
repoOwner: LocalStore.get(REPO_OWNER_KEY, state.repoOwner),
repoName: LocalStore.get(REPO_NAME_KEY, state.repoName),
repoOwner: localStore.get(REPO_OWNER_KEY, state.repoOwner),
repoName: localStore.get(REPO_NAME_KEY, state.repoName),
}));
}
render() {
const {localStore} = this.props;
const {edgeEvaluator} = this.state;
const {graphWithMetadata, pnd} = this.state.data;
return (
@ -70,7 +84,7 @@ export default class App extends React.Component<Props, State> {
onChange={(e) => {
const value = e.target.value;
this.setState({repoOwner: value}, () => {
LocalStore.set(REPO_OWNER_KEY, this.state.repoOwner);
localStore.set(REPO_OWNER_KEY, this.state.repoOwner);
});
}}
/>
@ -83,7 +97,7 @@ export default class App extends React.Component<Props, State> {
onChange={(e) => {
const value = e.target.value;
this.setState({repoName: value}, () => {
LocalStore.set(REPO_NAME_KEY, this.state.repoName);
localStore.set(REPO_NAME_KEY, this.state.repoName);
});
}}
/>
@ -122,7 +136,10 @@ export default class App extends React.Component<Props, State> {
) : (
<p>Graph not loaded.</p>
)}
<WeightConfig onChange={(ee) => this.setState({edgeEvaluator: ee})} />
<WeightConfig
localStore={localStore}
onChange={(ee) => this.setState({edgeEvaluator: ee})}
/>
<PagerankTable
adapters={NullUtil.map(graphWithMetadata, (x) => x.adapters)}
pnd={pnd}

View File

@ -2,8 +2,9 @@
import React from "react";
import {shallow} from "enzyme";
import BrowserLocalStore from "../browserLocalStore";
import {pagerank} from "../../core/attribution/pagerank";
import App from "./App";
import {App} from "./App";
import {Graph, NodeAddress, EdgeAddress} from "../../core/graph";
@ -95,17 +96,27 @@ function example() {
}
describe("app/credExplorer/App", () => {
function makeLocalStore() {
// TODO(@wchargin): This should be an in-memory implementation of
// LocalStore, not the browser version. This only works because the
// store is not actually needed for the shallow render to complete
// successfully.
return new BrowserLocalStore({
version: "1",
keyPrefix: "cred-explorer",
});
}
it("renders with clean state", () => {
shallow(<App />);
shallow(<App localStore={makeLocalStore()} />);
});
it("renders with graph and adapters set", () => {
const app = shallow(<App />);
const app = shallow(<App localStore={makeLocalStore()} />);
const {graph, adapters} = example();
const data = {graph, adapters, pagerankResult: null};
app.setState({data});
});
it("renders with graph and adapters and pagerankResult", () => {
const app = shallow(<App />);
const app = shallow(<App localStore={makeLocalStore()} />);
const {graph, adapters, pagerankResult} = example();
const data = {graph, adapters, pagerankResult};
app.setState({data});

View File

@ -1,5 +0,0 @@
// @flow
import LocalStore from "../browserLocalStore";
export default new LocalStore({version: "1", keyPrefix: "cred-explorer"});

View File

@ -9,9 +9,9 @@ import {
NodeAddress,
} from "../../core/graph";
import type {LocalStore} from "../localStore";
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
import {byEdgeType, byNodeType} from "./edgeWeights";
import LocalStore from "./LocalStore";
import * as MapUtil from "../../util/map";
import * as NullUtil from "../../util/null";
@ -19,9 +19,10 @@ import type {StaticPluginAdapter} from "../pluginAdapter";
import {StaticPluginAdapter as GithubAdapter} from "../../plugins/github/pluginAdapter";
import {StaticPluginAdapter as GitAdapter} from "../../plugins/git/pluginAdapter";
type Props = {
onChange: (EdgeEvaluator) => void,
};
type Props = {|
+localStore: LocalStore,
+onChange: (EdgeEvaluator) => void,
|};
type EdgeWeights = Map<EdgeAddressT, UserEdgeWeight>;
type UserEdgeWeight = {|+logWeight: number, +directionality: number|};
@ -71,15 +72,16 @@ export class WeightConfig extends React.Component<Props, State> {
}
componentDidMount() {
const {localStore} = this.props;
this.setState(
(state) => {
return {
edgeWeights: NullUtil.orElse(
NullUtil.map(LocalStore.get(EDGE_WEIGHTS_KEY), MapUtil.fromObject),
NullUtil.map(localStore.get(EDGE_WEIGHTS_KEY), MapUtil.fromObject),
state.edgeWeights
),
nodeWeights: NullUtil.orElse(
NullUtil.map(LocalStore.get(NODE_WEIGHTS_KEY), MapUtil.fromObject),
NullUtil.map(localStore.get(NODE_WEIGHTS_KEY), MapUtil.fromObject),
state.nodeWeights
),
};
@ -126,9 +128,10 @@ export class WeightConfig extends React.Component<Props, State> {
}
fire() {
const {localStore} = this.props;
const {edgeWeights, nodeWeights} = this.state;
LocalStore.set(EDGE_WEIGHTS_KEY, MapUtil.toObject(edgeWeights));
LocalStore.set(NODE_WEIGHTS_KEY, MapUtil.toObject(nodeWeights));
localStore.set(EDGE_WEIGHTS_KEY, MapUtil.toObject(edgeWeights));
localStore.set(NODE_WEIGHTS_KEY, MapUtil.toObject(nodeWeights));
const edgePrefixes = Array.from(edgeWeights.entries()).map(
([prefix, {logWeight, directionality}]) => ({
prefix,