Remove the ExplorerAdapter from the legacy app

Prior to #1136, we needed an `ExplorerAdapter` abstraction to get node
description data to the frontend. Now that it's included in the graph,
we can throw away this abstraction, which is a big step towards plugin
simplification (work towards #1120).

Since it only affects a deprecated/legacy part of the code base, I
didn't put much effort into making the result super clean. I also
removed a few tests that became inconvenient.

Test plan: Verified that the legacy frontend still works. There's one
tiny regression, which is that the link color in the legacy frontend no
longer matches the rest of the UI, but that's actually consistent with
the timeline frontend, so no biggie.

`yarn test` passes.
This commit is contained in:
Dandelion Mané 2019-07-14 20:08:11 +01:00
parent 493d7332c6
commit 7a88d32cb2
21 changed files with 145 additions and 743 deletions

View File

@ -9,6 +9,7 @@ import BrowserLocalStore from "../../webutil/browserLocalStore";
import Link from "../../webutil/Link";
import type {RepoId} from "../../core/repoId";
import {type NodeAddressT} from "../../core/graph";
import {declaration as githubDeclaration} from "../../plugins/github/declaration";
import {PagerankTable} from "./pagerankTable/Table";
import {WeightConfig} from "../weights/WeightConfig";
@ -21,7 +22,6 @@ import {
type StateTransitionMachineInterface,
initialState,
} from "./state";
import {StaticExplorerAdapterSet} from "./adapters/explorerAdapterSet";
import {userNodeType} from "../../plugins/github/declaration";
const credOverviewUrl =
@ -31,7 +31,6 @@ const feedbackUrl =
export class AppPage extends React.Component<{|
+assets: Assets,
+adapters: StaticExplorerAdapterSet,
+repoId: RepoId,
|}> {
static _LOCAL_STORE = new CheckedLocalStore(
@ -47,7 +46,6 @@ export class AppPage extends React.Component<{|
<App
repoId={this.props.repoId}
assets={this.props.assets}
adapters={this.props.adapters}
localStore={AppPage._LOCAL_STORE}
/>
);
@ -57,7 +55,6 @@ export class AppPage extends React.Component<{|
type Props = {|
+assets: Assets,
+localStore: LocalStore,
+adapters: StaticExplorerAdapterSet,
+repoId: RepoId,
|};
type State = {|
@ -90,9 +87,7 @@ export function createApp(
const {appState} = this.state;
const weightConfig = (
<WeightConfig
declarations={this.props.adapters
.adapters()
.map((a) => a.declaration())}
declarations={[githubDeclaration]}
nodeTypeWeights={this.state.weights.nodeTypeWeights}
edgeTypeWeights={this.state.weights.edgeTypeWeights}
onNodeWeightChange={(prefix, weight) => {
@ -119,15 +114,15 @@ export function createApp(
);
let pagerankTable;
if (appState.type === "PAGERANK_EVALUATED") {
const adapters = appState.graphWithAdapters.adapters;
const pnd = appState.pagerankNodeDecomposition;
pagerankTable = (
<PagerankTable
defaultNodeType={userNodeType}
adapters={adapters}
weightConfig={weightConfig}
weightFileManager={weightFileManager}
manualWeights={this.state.weights.nodeManualWeights}
declarations={[githubDeclaration]}
graph={appState.graph}
onManualWeightsChange={(addr: NodeAddressT, weight: number) =>
this.setState(({weights}) => {
weights.nodeManualWeights.set(addr, weight);
@ -164,9 +159,11 @@ export function createApp(
onClick={() =>
this.stateTransitionMachine.loadGraphAndRunPagerank(
this.props.assets,
this.props.adapters,
this.state.weights,
this.props.adapters.combinedTypes(),
{
nodeTypes: githubDeclaration.nodeTypes.slice(),
edgeTypes: githubDeclaration.edgeTypes.slice(),
},
GithubPrefix.user
)
}

View File

@ -7,10 +7,6 @@ import {Graph} from "../../core/graph";
import {makeRepoId} from "../../core/repoId";
import {Assets} from "../../webutil/assets";
import testLocalStore from "../../webutil/testLocalStore";
import {
DynamicExplorerAdapterSet,
StaticExplorerAdapterSet,
} from "./adapters/explorerAdapterSet";
import {PagerankTable} from "./pagerankTable/Table";
import {createApp, LoadingIndicator, ProjectDetail} from "./App";
@ -37,7 +33,6 @@ describe("explorer/legacy/App", () => {
const el = shallow(
<App
assets={new Assets("/foo/")}
adapters={new StaticExplorerAdapterSet([])}
localStore={localStore}
repoId={makeRepoId("foo", "bar")}
/>
@ -56,10 +51,6 @@ describe("explorer/legacy/App", () => {
};
}
const emptyAdapters = new DynamicExplorerAdapterSet(
new StaticExplorerAdapterSet([]),
[]
);
const exampleStates = {
readyToLoadGraph: (loadingState) => {
return () => ({
@ -73,7 +64,7 @@ describe("explorer/legacy/App", () => {
type: "READY_TO_RUN_PAGERANK",
repoId: makeRepoId("foo", "bar"),
loading: loadingState,
graphWithAdapters: {graph: new Graph(), adapters: emptyAdapters},
graph: new Graph(),
});
},
pagerankEvaluated: (loadingState) => {
@ -81,7 +72,7 @@ describe("explorer/legacy/App", () => {
type: "PAGERANK_EVALUATED",
repoId: makeRepoId("foo", "bar"),
loading: loadingState,
graphWithAdapters: {graph: new Graph(), adapters: emptyAdapters},
graph: new Graph(),
pagerankNodeDecomposition: new Map(),
});
},

View File

@ -1,18 +0,0 @@
// @flow
import {type Node as ReactNode} from "react";
import {Graph, type NodeAddressT} from "../../../core/graph";
import type {Assets} from "../../../webutil/assets";
import type {RepoId} from "../../../core/repoId";
import type {PluginDeclaration} from "../../../analysis/pluginDeclaration";
export interface StaticExplorerAdapter {
declaration(): PluginDeclaration;
load(assets: Assets, repoId: RepoId): Promise<DynamicExplorerAdapter>;
}
export interface DynamicExplorerAdapter {
graph(): Graph;
nodeDescription(NodeAddressT): ReactNode;
static(): StaticExplorerAdapter;
}

View File

@ -1,134 +0,0 @@
// @flow
/**
* This module is deprecated; logic for aggregating over different plugins,
* and matching adapters for a particular or edge, should live at the
* core plugin level, not at the level of particular adapters. Otherwise,
* we will find ourselves re-implementing the same logic repetitiously for
* every new adapter.
*
* If considering adding new dependencies on this file or adding new features,
* please consider simply re-writing it to live in the analysis module and
* be paramterized over the adapter choice.
*/
import {Graph, type NodeAddressT, type EdgeAddressT} from "../../../core/graph";
import {NodeTrie, EdgeTrie} from "../../../core/trie";
import type {NodeAndEdgeTypes} from "../../../analysis/types";
import {combineTypes} from "../../../analysis/pluginDeclaration";
import type {Assets} from "../../../webutil/assets";
import type {RepoId} from "../../../core/repoId";
import type {
StaticExplorerAdapter,
DynamicExplorerAdapter,
} from "./explorerAdapter";
import type {EdgeType, NodeType} from "../../../analysis/types";
export class StaticExplorerAdapterSet {
_adapters: $ReadOnlyArray<StaticExplorerAdapter>;
_adapterNodeTrie: NodeTrie<StaticExplorerAdapter>;
_adapterEdgeTrie: EdgeTrie<StaticExplorerAdapter>;
_typeNodeTrie: NodeTrie<NodeType>;
_typeEdgeTrie: EdgeTrie<EdgeType>;
constructor(adapters: $ReadOnlyArray<StaticExplorerAdapter>): void {
this._adapters = adapters;
this._adapterNodeTrie = new NodeTrie();
this._adapterEdgeTrie = new EdgeTrie();
this._typeNodeTrie = new NodeTrie();
this._typeEdgeTrie = new EdgeTrie();
const usedPluginNames = new Set();
this._adapters.forEach((a) => {
const name = a.declaration().name;
if (usedPluginNames.has(name)) {
throw new Error(`Multiple plugins with name "${name}"`);
}
usedPluginNames.add(name);
this._adapterNodeTrie.add(a.declaration().nodePrefix, a);
this._adapterEdgeTrie.add(a.declaration().edgePrefix, a);
});
this.nodeTypes().forEach((t) => this._typeNodeTrie.add(t.prefix, t));
this.edgeTypes().forEach((t) => this._typeEdgeTrie.add(t.prefix, t));
}
adapters(): $ReadOnlyArray<StaticExplorerAdapter> {
return this._adapters;
}
combinedTypes(): NodeAndEdgeTypes {
return combineTypes(this._adapters.map((x) => x.declaration()));
}
// TODO(@decentralion): Remove the next two methods
// (Although really I want to remove this whole class.)
nodeTypes(): NodeType[] {
return [].concat(...this._adapters.map((x) => x.declaration().nodeTypes));
}
edgeTypes(): EdgeType[] {
return [].concat(...this._adapters.map((x) => x.declaration().edgeTypes));
}
adapterMatchingNode(x: NodeAddressT): ?StaticExplorerAdapter {
return this._adapterNodeTrie.getLast(x);
}
adapterMatchingEdge(x: EdgeAddressT): ?StaticExplorerAdapter {
return this._adapterEdgeTrie.getLast(x);
}
typeMatchingNode(x: NodeAddressT): ?NodeType {
return this._typeNodeTrie.getLast(x);
}
typeMatchingEdge(x: EdgeAddressT): ?EdgeType {
return this._typeEdgeTrie.getLast(x);
}
load(assets: Assets, repoId: RepoId): Promise<DynamicExplorerAdapterSet> {
return Promise.all(this._adapters.map((a) => a.load(assets, repoId))).then(
(adapters) => new DynamicExplorerAdapterSet(this, adapters)
);
}
}
export class DynamicExplorerAdapterSet {
_adapters: $ReadOnlyArray<DynamicExplorerAdapter>;
_staticExplorerAdapterSet: StaticExplorerAdapterSet;
_adapterNodeTrie: NodeTrie<DynamicExplorerAdapter>;
_adapterEdgeTrie: EdgeTrie<DynamicExplorerAdapter>;
constructor(
staticExplorerAdapterSet: StaticExplorerAdapterSet,
adapters: $ReadOnlyArray<DynamicExplorerAdapter>
): void {
this._staticExplorerAdapterSet = staticExplorerAdapterSet;
this._adapters = adapters;
this._adapterNodeTrie = new NodeTrie();
this._adapterEdgeTrie = new EdgeTrie();
this._adapters.forEach((a) => {
this._adapterNodeTrie.add(a.static().declaration().nodePrefix, a);
this._adapterEdgeTrie.add(a.static().declaration().edgePrefix, a);
});
}
adapterMatchingNode(x: NodeAddressT): ?DynamicExplorerAdapter {
return this._adapterNodeTrie.getLast(x);
}
adapterMatchingEdge(x: EdgeAddressT): ?DynamicExplorerAdapter {
return this._adapterEdgeTrie.getLast(x);
}
adapters(): $ReadOnlyArray<DynamicExplorerAdapter> {
return this._adapters;
}
graph(): Graph {
return Graph.merge(this._adapters.map((x) => x.graph()));
}
static() {
return this._staticExplorerAdapterSet;
}
}

View File

@ -1,166 +0,0 @@
// @flow
import {NodeAddress, EdgeAddress, Graph} from "../../../core/graph";
import {FactorioStaticAdapter} from "../../../plugins/demo/explorerAdapter";
import {StaticExplorerAdapterSet} from "./explorerAdapterSet";
import {Assets} from "../../../webutil/assets";
import {makeRepoId} from "../../../core/repoId";
import * as NullUtil from "../../../util/null";
describe("explorer/legacy/adapters/explorerAdapterSet", () => {
describe("StaticExplorerAdapterSet", () => {
function example() {
const x = new FactorioStaticAdapter();
const sas = new StaticExplorerAdapterSet([x]);
return {x, sas};
}
it("errors if two plugins have the same name", () => {
const x = new FactorioStaticAdapter();
const shouldError = () => new StaticExplorerAdapterSet([x, x]);
expect(shouldError).toThrowError("Multiple plugins with name");
});
it("includes the manually provided plugin adapters", () => {
const {x, sas} = example();
expect(sas.adapters()[0].declaration().name).toBe(x.declaration().name);
});
it("aggregates NodeTypes across plugins", () => {
const {sas} = example();
const nodeTypes = sas.nodeTypes();
const expectedNumNodeTypes = new FactorioStaticAdapter().declaration()
.nodeTypes.length;
expect(nodeTypes).toHaveLength(expectedNumNodeTypes);
});
it("aggregates EdgeTypes across plugins", () => {
const {sas} = example();
const edgeTypes = sas.edgeTypes();
const expectedNumEdgeTypes = new FactorioStaticAdapter().declaration()
.edgeTypes.length;
expect(edgeTypes).toHaveLength(expectedNumEdgeTypes);
});
it("finds adapter matching a node", () => {
const {x, sas} = example();
let matching = sas.adapterMatchingNode(
NodeAddress.fromParts(["factorio", "inserter"])
);
matching = NullUtil.get(matching);
expect(matching.declaration().name).toBe(x.declaration().name);
});
it("finds adapter matching an edge", () => {
const {x, sas} = example();
let matching = sas.adapterMatchingEdge(
EdgeAddress.fromParts(["factorio", "assembles"])
);
matching = NullUtil.get(matching);
expect(matching.declaration().name).toBe(x.declaration().name);
});
it("finds no adapter for unregistered node", () => {
const {sas} = example();
const adapter = sas.adapterMatchingNode(NodeAddress.fromParts(["weird"]));
expect(adapter).toBe(undefined);
});
it("finds no adapter for unregistered edge", () => {
const {sas} = example();
const adapter = sas.adapterMatchingEdge(EdgeAddress.fromParts(["weird"]));
expect(adapter).toBe(undefined);
});
it("finds type matching a node", () => {
const {sas} = example();
const type = NullUtil.get(
sas.typeMatchingNode(
NodeAddress.fromParts(["factorio", "inserter", "1", "foo"])
)
);
expect(type.name).toBe("inserter");
});
it("finds type matching an edge", () => {
const {sas} = example();
const type = NullUtil.get(
sas.typeMatchingEdge(
EdgeAddress.fromParts(["factorio", "assembles", "other", "1", "foo"])
)
);
expect(type.forwardName).toBe("assembles");
});
it("finds no type for unregistered node", () => {
const {sas} = example();
const type = sas.typeMatchingNode(
NodeAddress.fromParts(["wombat", "1", "foo"])
);
expect(type).toBe(undefined);
});
it("finds no type for unregistered edge", () => {
const {sas} = example();
const type = sas.typeMatchingEdge(
EdgeAddress.fromParts(["wombat", "1", "foo"])
);
expect(type).toBe(undefined);
});
it("loads a dynamicExplorerAdapterSet", async () => {
const {x, sas} = example();
const loadingMock = jest.fn().mockResolvedValue();
x.loadingMock = loadingMock;
expect(x.loadingMock).toHaveBeenCalledTimes(0);
const assets = new Assets("/my/gateway/");
const repoId = makeRepoId("foo", "bar");
const das = await sas.load(assets, repoId);
expect(loadingMock).toHaveBeenCalledTimes(1);
expect(loadingMock.mock.calls[0]).toHaveLength(2);
expect(loadingMock.mock.calls[0][0]).toBe(assets);
expect(loadingMock.mock.calls[0][1]).toBe(repoId);
expect(das).toEqual(expect.anything());
});
});
describe("DynamicExplorerAdapterSet", () => {
async function example() {
const x = new FactorioStaticAdapter();
const sas = new StaticExplorerAdapterSet([x]);
const das = await sas.load(
new Assets("/my/gateway/"),
makeRepoId("foo", "bar")
);
return {x, sas, das};
}
it("allows retrieval of the original StaticExplorerAdapterSet", async () => {
const {sas, das} = await example();
expect(das.static()).toBe(sas);
});
it("allows accessing the dynamic adapters", async () => {
const {sas, das} = await example();
expect(das.adapters().map((a) => a.static().declaration().name)).toEqual(
sas.adapters().map((a) => a.declaration().name)
);
});
it("allows retrieval of the aggregated graph", async () => {
const {das} = await example();
const expectedGraph = Graph.merge(das.adapters().map((x) => x.graph()));
expect(das.graph().equals(expectedGraph)).toBe(true);
});
it("finds adapter matching a node", async () => {
const {x, das} = await example();
let matching = das.adapterMatchingNode(
NodeAddress.fromParts(["factorio", "inserter"])
);
matching = NullUtil.get(matching);
expect(matching.static().declaration().name).toBe(x.declaration().name);
});
it("finds adapter matching an edge", async () => {
const {x, das} = await example();
let matching = das.adapterMatchingEdge(
EdgeAddress.fromParts(["factorio", "assembles"])
);
matching = NullUtil.get(matching);
expect(matching.static().declaration().name).toBe(x.declaration().name);
});
it("finds no adapter for unregistered node", async () => {
const {das} = await example();
const adapter = das.adapterMatchingNode(NodeAddress.fromParts(["weird"]));
expect(adapter).toBe(undefined);
});
it("finds no adapter for unregistered edge", async () => {
const {das} = await example();
const adapter = das.adapterMatchingEdge(EdgeAddress.fromParts(["weird"]));
expect(adapter).toBe(undefined);
});
});
});

View File

@ -20,13 +20,11 @@ type AggregationRowListProps = {|
export class AggregationRowList extends React.PureComponent<AggregationRowListProps> {
render() {
const {depth, node, sharedProps} = this.props;
const {pnd, adapters} = sharedProps;
const {pnd, declarations} = sharedProps;
const nodeTypes = [].concat(...declarations.map((x) => x.nodeTypes));
const edgeTypes = [].concat(...declarations.map((x) => x.edgeTypes));
const {scoredConnections} = NullUtil.get(pnd.get(node));
const aggregations = aggregateFlat(
scoredConnections,
adapters.static().nodeTypes(),
adapters.static().edgeTypes()
);
const aggregations = aggregateFlat(scoredConnections, nodeTypes, edgeTypes);
return (
<React.Fragment>
{aggregations.map((agg) => (

View File

@ -6,6 +6,7 @@ import {shallow} from "enzyme";
import * as NullUtil from "../../../util/null";
import {NodeAddress, EdgeAddress} from "../../../core/graph";
import type {EdgeType, NodeType} from "../../../analysis/types";
import {combineTypes} from "../../../analysis/pluginDeclaration";
import {
AggregationRowList,
AggregationRow,
@ -23,14 +24,15 @@ require("../../../webutil/testUtil").configureEnzyme();
describe("explorer/legacy/pagerankTable/Aggregation", () => {
describe("AggregationRowList", () => {
it("instantiates AggregationRows for each aggregation", async () => {
const {adapters, pnd, sharedProps} = await example();
const {pnd, sharedProps} = await example();
const node = factorioNodes.inserter1.address;
const depth = 20;
const connections = NullUtil.get(pnd.get(node)).scoredConnections;
const types = combineTypes(sharedProps.declarations);
const aggregations = aggregateFlat(
connections,
adapters.static().nodeTypes(),
adapters.static().edgeTypes()
types.nodeTypes,
types.edgeTypes
);
const el = shallow(
<AggregationRowList
@ -56,13 +58,14 @@ describe("explorer/legacy/pagerankTable/Aggregation", () => {
describe("AggregationRow", () => {
async function setup() {
const {pnd, adapters, sharedProps} = await example();
const {pnd, sharedProps} = await example();
const target = factorioNodes.inserter1.address;
const {scoredConnections} = NullUtil.get(pnd.get(target));
const types = combineTypes(sharedProps.declarations);
const aggregations = aggregateFlat(
scoredConnections,
adapters.static().nodeTypes(),
adapters.static().edgeTypes()
types.nodeTypes,
types.edgeTypes
);
const aggregation = aggregations[0];
const depth = 23;

View File

@ -3,14 +3,14 @@
import React from "react";
import * as NullUtil from "../../../util/null";
import type {NodeAddressT} from "../../../core/graph";
import {type PluginDeclaration} from "../../../analysis/pluginDeclaration";
import {type NodeAddressT, Graph} from "../../../core/graph";
import type {Connection} from "../../../core/attribution/graphToMarkovChain";
import type {ScoredConnection} from "../../../analysis/pagerankNodeDecomposition";
import {DynamicExplorerAdapterSet} from "../adapters/explorerAdapterSet";
import {TableRow} from "./TableRow";
import {NodeRow} from "./Node";
import {edgeVerb, nodeDescription, type SharedProps, Badge} from "./shared";
import {nodeDescription, edgeVerb, type SharedProps, Badge} from "./shared";
type ConnectionRowListProps = {|
+depth: number,
@ -54,13 +54,17 @@ export class ConnectionRow extends React.PureComponent<ConnectionRowProps> {
depth,
scoredConnection: {connection, source, connectionScore},
} = this.props;
const {pnd, adapters} = sharedProps;
const {pnd, declarations, graph} = sharedProps;
const {score: targetScore} = NullUtil.get(pnd.get(target));
const connectionProportion = connectionScore / targetScore;
const connectionPercent = (connectionProportion * 100).toFixed(2) + "%";
const connectionView = (
<ConnectionView connection={connection} adapters={adapters} />
<ConnectionView
connection={connection}
declarations={declarations}
graph={graph}
/>
);
return (
<TableRow
@ -84,10 +88,11 @@ export class ConnectionRow extends React.PureComponent<ConnectionRowProps> {
export class ConnectionView extends React.PureComponent<{|
+connection: Connection,
+adapters: DynamicExplorerAdapterSet,
+declarations: $ReadOnlyArray<PluginDeclaration>,
+graph: Graph,
|}> {
render() {
const {connection, adapters} = this.props;
const {connection, declarations, graph} = this.props;
const {adjacency} = connection;
switch (adjacency.type) {
case "SYNTHETIC_LOOP":
@ -96,18 +101,18 @@ export class ConnectionView extends React.PureComponent<{|
return (
<span>
<Badge>
{edgeVerb(adjacency.edge.address, "BACKWARD", adapters)}
{edgeVerb(adjacency.edge.address, "BACKWARD", declarations)}
</Badge>{" "}
<span>{nodeDescription(adjacency.edge.src, adapters)}</span>
<span>{nodeDescription(adjacency.edge.src, graph)}</span>
</span>
);
case "OUT_EDGE":
return (
<span>
<Badge>
{edgeVerb(adjacency.edge.address, "FORWARD", adapters)}
{edgeVerb(adjacency.edge.address, "FORWARD", declarations)}
</Badge>{" "}
<span>{nodeDescription(adjacency.edge.dst, adapters)}</span>
<span>{nodeDescription(adjacency.edge.dst, graph)}</span>
</span>
);
default:

View File

@ -108,13 +108,11 @@ describe("explorer/legacy/pagerankTable/Connection", () => {
expect(row.props().multiuseColumn).toBe(expectedPercent);
});
it("with a ConnectionView as description", async () => {
const {row, sharedProps, scoredConnection} = await setup();
const {adapters} = sharedProps;
const {row, scoredConnection} = await setup();
const description = row.props().description;
const cv = shallow(description).instance();
expect(cv).toBeInstanceOf(ConnectionView);
expect(cv.props.connection).toEqual(scoredConnection.connection);
expect(cv.props.adapters).toEqual(adapters);
});
describe("with a NodeRow as children", () => {
function getChildren(row) {
@ -146,7 +144,7 @@ describe("explorer/legacy/pagerankTable/Connection", () => {
});
describe("ConnectionView", () => {
async function setup() {
const {pnd, adapters} = await example();
const {pnd, sharedProps} = await example();
const {scoredConnections} = NullUtil.get(
pnd.get(factorioNodes.machine1.address)
);
@ -162,11 +160,14 @@ describe("explorer/legacy/pagerankTable/Connection", () => {
const syntheticConnection = connectionByType("SYNTHETIC_LOOP");
function cvForConnection(connection: Connection) {
return shallow(
<ConnectionView adapters={adapters} connection={connection} />
<ConnectionView
graph={sharedProps.graph}
declarations={sharedProps.declarations}
connection={connection}
/>
);
}
return {
adapters,
connections,
pnd,
cvForConnection,
@ -195,22 +196,14 @@ describe("explorer/legacy/pagerankTable/Connection", () => {
const view = cvForConnection(inConnection);
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("is transported by");
expect(description.text()).toEqual(
'[factorio]: NodeAddress["factorio","inserter","1"]'
);
});
it("for outward connections, renders a `Badge` and description", async () => {
const {cvForConnection, outConnection} = await setup();
const view = cvForConnection(outConnection);
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("assembles");
expect(description.text()).toEqual(
'[factorio]: NodeAddress["factorio","inserter","2"]'
);
});
it("for synthetic connections, renders only a `Badge`", async () => {
const {cvForConnection, syntheticConnection} = await setup();

View File

@ -55,7 +55,7 @@ export type NodeRowProps = {|
export class NodeRow extends React.PureComponent<NodeRowProps> {
render() {
const {depth, node, sharedProps, showPadding} = this.props;
const {pnd, adapters, onManualWeightsChange, manualWeights} = sharedProps;
const {pnd, graph, onManualWeightsChange, manualWeights} = sharedProps;
const {score} = NullUtil.get(pnd.get(node));
const weight = NullUtil.orElse(manualWeights.get(node), 1);
const slider = (
@ -74,7 +74,7 @@ export class NodeRow extends React.PureComponent<NodeRowProps> {
/>
</label>
);
const description = <span>{nodeDescription(node, adapters)}</span>;
const description = <span>{nodeDescription(node, graph)}</span>;
return (
<TableRow
depth={depth}

View File

@ -15,7 +15,6 @@ import {
import type {NodeAddressT} from "../../../core/graph";
import {nodeDescription} from "./shared";
import {example} from "./sharedTestUtils";
import {NodeRowList, NodeRow, type NodeRowProps} from "./Node";
import {nodes as factorioNodes} from "../../../plugins/demo/graph";
@ -28,13 +27,13 @@ describe("explorer/legacy/pagerankTable/Node", () => {
return sortBy(nodes, (node) => -NullUtil.get(pnd.get(node)).score);
}
async function setup(maxEntriesPerList: number = 123) {
const {adapters, pnd, sharedProps: changeEntries} = await example();
const {pnd, sharedProps: changeEntries} = await example();
const sharedProps = {...changeEntries, maxEntriesPerList};
const nodes = Array.from(pnd.keys());
expect(nodes).not.toHaveLength(0);
const component = <NodeRowList sharedProps={sharedProps} nodes={nodes} />;
const element = shallow(component);
return {element, adapters, sharedProps, nodes};
return {element, sharedProps, nodes};
}
it("creates `NodeRow`s with the right props", async () => {
const {element, nodes, sharedProps} = await setup();
@ -164,12 +163,6 @@ describe("explorer/legacy/pagerankTable/Node", () => {
expect(span.text()).toEqual("4×");
});
});
it("with the node description", async () => {
const {row, sharedProps, node} = await setup();
const {adapters} = sharedProps;
const description = shallow(row.props().description);
expect(description.text()).toEqual(nodeDescription(node, adapters));
});
describe("with a AggregationRowList as children", () => {
function getChildren(row) {
const children = row.props().children;

View File

@ -5,16 +5,16 @@ import sortBy from "lodash.sortby";
import {WeightConfig} from "../../weights/WeightConfig";
import {WeightsFileManager} from "../../weights/WeightsFileManager";
import {NodeAddress, type NodeAddressT} from "../../../core/graph";
import {Graph, NodeAddress, type NodeAddressT} from "../../../core/graph";
import type {PagerankNodeDecomposition} from "../../../analysis/pagerankNodeDecomposition";
import {DynamicExplorerAdapterSet} from "../adapters/explorerAdapterSet";
import type {DynamicExplorerAdapter} from "../adapters/explorerAdapter";
import {NodeRowList} from "./Node";
import {type NodeType} from "../../../analysis/types";
import {type PluginDeclaration} from "../../../analysis/pluginDeclaration";
type PagerankTableProps = {|
+pnd: PagerankNodeDecomposition,
+adapters: DynamicExplorerAdapterSet,
+declarations: $ReadOnlyArray<PluginDeclaration>,
+graph: Graph,
+maxEntriesPerList: number,
+defaultNodeType: ?NodeType,
+manualWeights: Map<NodeAddressT, number>,
@ -75,29 +75,26 @@ export class PagerankTable extends React.PureComponent<
}
renderFilterSelect() {
const {pnd, adapters} = this.props;
if (pnd == null || adapters == null) {
const {pnd, declarations} = this.props;
if (pnd == null || declarations == null) {
throw new Error("Impossible.");
}
function optionGroup(adapter: DynamicExplorerAdapter) {
function optionGroup(declaration: PluginDeclaration) {
const header = (
<option
key={adapter.static().declaration().nodePrefix}
value={adapter.static().declaration().nodePrefix}
key={declaration.nodePrefix}
value={declaration.nodePrefix}
style={{fontWeight: "bold"}}
>
{adapter.static().declaration().name}
{declaration.name}
</option>
);
const entries = adapter
.static()
.declaration()
.nodeTypes.map((type) => (
<option key={type.prefix} value={type.prefix}>
{"\u2003" + type.name}
</option>
));
const entries = declaration.nodeTypes.map((type) => (
<option key={type.prefix} value={type.prefix}>
{"\u2003" + type.name}
</option>
));
return [header, ...entries];
}
return (
@ -110,10 +107,9 @@ export class PagerankTable extends React.PureComponent<
}
>
<option value={NodeAddress.empty}>Show all</option>
{sortBy(
adapters.adapters(),
(a: DynamicExplorerAdapter) => a.static().declaration().name
).map(optionGroup)}
{sortBy(declarations, (d: PluginDeclaration) => d.name).map(
optionGroup
)}
</select>
</label>
);
@ -122,20 +118,22 @@ export class PagerankTable extends React.PureComponent<
renderTable() {
const {
pnd,
adapters,
declarations,
maxEntriesPerList,
manualWeights,
onManualWeightsChange,
graph,
} = this.props;
if (pnd == null || adapters == null || maxEntriesPerList == null) {
if (pnd == null || declarations == null || maxEntriesPerList == null) {
throw new Error("Impossible.");
}
const sharedProps = {
pnd,
adapters,
declarations,
maxEntriesPerList,
manualWeights,
onManualWeightsChange,
graph,
};
return (
<table

View File

@ -17,7 +17,6 @@ describe("explorer/legacy/pagerankTable/Table", () => {
async function setup(defaultNodeType?: NodeType) {
const {
pnd,
adapters,
sharedProps,
manualWeights,
onManualWeightsChange,
@ -31,7 +30,8 @@ describe("explorer/legacy/pagerankTable/Table", () => {
weightConfig={weightConfig}
weightFileManager={weightFileManager}
pnd={pnd}
adapters={adapters}
graph={sharedProps.graph}
declarations={sharedProps.declarations}
maxEntriesPerList={maxEntriesPerList}
manualWeights={manualWeights}
onManualWeightsChange={onManualWeightsChange}
@ -39,7 +39,6 @@ describe("explorer/legacy/pagerankTable/Table", () => {
);
return {
pnd,
adapters,
element,
maxEntriesPerList,
weightConfig,

View File

@ -1,45 +1,51 @@
// @flow
import React, {type Node as ReactNode} from "react";
import Markdown from "react-markdown";
import {
Graph,
type EdgeAddressT,
type NodeAddressT,
NodeAddress,
EdgeAddress,
} from "../../../core/graph";
import {DynamicExplorerAdapterSet} from "../adapters/explorerAdapterSet";
import {type PluginDeclaration} from "../../../analysis/pluginDeclaration";
import type {PagerankNodeDecomposition} from "../../../analysis/pagerankNodeDecomposition";
export function nodeDescription(
address: NodeAddressT,
adapters: DynamicExplorerAdapterSet
graph: Graph
): ReactNode {
const adapter = adapters.adapterMatchingNode(address);
if (adapter == null) {
return NodeAddress.toString(address);
}
try {
return adapter.nodeDescription(address);
} catch (e) {
const result = NodeAddress.toString(address);
console.error(`Error getting description for ${result}: ${e.message}`);
return result;
const node = graph.node(address);
if (node == null) {
throw new Error(`No node for ${address}`);
}
return <Markdown renderers={{paragraph: "span"}} source={node.description} />;
}
export function edgeVerb(
address: EdgeAddressT,
direction: "FORWARD" | "BACKWARD",
adapters: DynamicExplorerAdapterSet
declarations: $ReadOnlyArray<PluginDeclaration>
): string {
const edgeType = adapters.static().typeMatchingEdge(address);
function getType() {
for (const {edgeTypes} of declarations) {
for (const edgeType of edgeTypes) {
if (EdgeAddress.hasPrefix(address, edgeType.prefix)) {
return edgeType;
}
}
}
throw Error(`No matching type for ${address}`);
}
const edgeType = getType();
return direction === "FORWARD" ? edgeType.forwardName : edgeType.backwardName;
}
export type SharedProps = {|
+pnd: PagerankNodeDecomposition,
+adapters: DynamicExplorerAdapterSet,
+graph: Graph,
+declarations: $ReadOnlyArray<PluginDeclaration>,
+maxEntriesPerList: number,
+manualWeights: Map<NodeAddressT, number>,
+onManualWeightsChange: (NodeAddressT, number) => void,

View File

@ -4,14 +4,15 @@ import React from "react";
import {type NodeAddressT} from "../../../core/graph";
import {pagerank} from "../../../analysis/pagerank";
import {dynamicExplorerAdapterSet} from "../../../plugins/demo/explorerAdapter";
import {graph as demoGraph} from "../../../plugins/demo/graph";
import {declaration as demoDeclaration} from "../../../plugins/demo/declaration";
import type {SharedProps} from "./shared";
export const COLUMNS = () => ["Description", "", "Cred"];
export async function example() {
const adapters = await dynamicExplorerAdapterSet();
const graph = adapters.graph();
const graph = demoGraph();
const declarations = [demoDeclaration];
const pnd = await pagerank(graph, (_unused_Edge) => ({
forwards: 1,
backwards: 1,
@ -23,7 +24,8 @@ export async function example() {
const weightFileManager: any = <div data-test-weight-file-manager={true} />;
const sharedProps: SharedProps = {
adapters,
graph,
declarations,
pnd,
maxEntriesPerList,
manualWeights,
@ -31,7 +33,6 @@ export async function example() {
};
return {
adapters,
pnd,
maxEntriesPerList,
sharedProps,

View File

@ -7,16 +7,13 @@ import type {Assets} from "../../webutil/assets";
import type {RepoId} from "../../core/repoId";
import {type EdgeEvaluator} from "../../analysis/pagerank";
import type {NodeAndEdgeTypes} from "../../analysis/types";
import {defaultLoader} from "../TimelineApp";
import {
type PagerankNodeDecomposition,
type PagerankOptions,
pagerank,
} from "../../analysis/pagerank";
import {
StaticExplorerAdapterSet,
DynamicExplorerAdapterSet,
} from "./adapters/explorerAdapterSet";
import type {Weights} from "../../analysis/weights";
import {weightsToEdgeEvaluator} from "../../analysis/weightsToEdgeEvaluator";
@ -42,12 +39,12 @@ export type ReadyToLoadGraph = {|
export type ReadyToRunPagerank = {|
+type: "READY_TO_RUN_PAGERANK",
+repoId: RepoId,
+graphWithAdapters: GraphWithAdapters,
+graph: Graph,
+loading: LoadingState,
|};
export type PagerankEvaluated = {|
+type: "PAGERANK_EVALUATED",
+graphWithAdapters: GraphWithAdapters,
+graph: Graph,
+repoId: RepoId,
+pagerankNodeDecomposition: PagerankNodeDecomposition,
+loading: LoadingState,
@ -61,38 +58,28 @@ export function createStateTransitionMachine(
getState: () => AppState,
setState: (AppState) => void
): StateTransitionMachine {
return new StateTransitionMachine(
getState,
setState,
loadGraphWithAdapters,
pagerank
);
return new StateTransitionMachine(getState, setState, doLoadGraph, pagerank);
}
// Exported for testing purposes.
export interface StateTransitionMachineInterface {
+loadGraph: (Assets, StaticExplorerAdapterSet) => Promise<boolean>;
+loadGraph: (Assets) => Promise<boolean>;
+runPagerank: (Weights, NodeAndEdgeTypes, NodeAddressT) => Promise<void>;
+loadGraphAndRunPagerank: (
Assets,
StaticExplorerAdapterSet,
Weights,
NodeAndEdgeTypes,
NodeAddressT
) => Promise<void>;
}
/* In production, instantiate via createStateTransitionMachine; the constructor
* implementation allows specification of the loadGraphWithAdapters and
* implementation allows specification of the loadGraph and
* pagerank functions for DI/testing purposes.
**/
export class StateTransitionMachine implements StateTransitionMachineInterface {
getState: () => AppState;
setState: (AppState) => void;
loadGraphWithAdapters: (
assets: Assets,
adapters: StaticExplorerAdapterSet,
repoId: RepoId
) => Promise<GraphWithAdapters>;
doLoadGraph: (assets: Assets, repoId: RepoId) => Promise<Graph>;
pagerank: (
Graph,
EdgeEvaluator,
@ -102,11 +89,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
constructor(
getState: () => AppState,
setState: (AppState) => void,
loadGraphWithAdapters: (
assets: Assets,
adapters: StaticExplorerAdapterSet,
repoId: RepoId
) => Promise<GraphWithAdapters>,
doLoadGraph: (assets: Assets, repoId: RepoId) => Promise<Graph>,
pagerank: (
Graph,
EdgeEvaluator,
@ -115,15 +98,12 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
) {
this.getState = getState;
this.setState = setState;
this.loadGraphWithAdapters = loadGraphWithAdapters;
this.doLoadGraph = doLoadGraph;
this.pagerank = pagerank;
}
/** Loads the graph, reports whether it was successful */
async loadGraph(
assets: Assets,
adapters: StaticExplorerAdapterSet
): Promise<boolean> {
async loadGraph(assets: Assets): Promise<boolean> {
const state = this.getState();
if (state.type !== "READY_TO_LOAD_GRAPH") {
throw new Error("Tried to loadGraph in incorrect state");
@ -134,14 +114,10 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
let newState: ?AppState;
let success = true;
try {
const graphWithAdapters = await this.loadGraphWithAdapters(
assets,
adapters,
repoId
);
const graph = await this.doLoadGraph(assets, repoId);
newState = {
type: "READY_TO_RUN_PAGERANK",
graphWithAdapters,
graph,
repoId,
loading: "NOT_LOADING",
};
@ -175,7 +151,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
? {...state, loading: "LOADING"}
: {...state, loading: "LOADING"};
this.setState(loadingState);
const graph = state.graphWithAdapters.graph;
const graph = state.graph;
let newState: ?AppState;
try {
const pagerankNodeDecomposition = await this.pagerank(
@ -189,7 +165,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
newState = {
type: "PAGERANK_EVALUATED",
pagerankNodeDecomposition,
graphWithAdapters: state.graphWithAdapters,
graph: state.graph,
repoId: state.repoId,
loading: "NOT_LOADING",
};
@ -208,7 +184,6 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
async loadGraphAndRunPagerank(
assets: Assets,
adapters: StaticExplorerAdapterSet,
weights: Weights,
types: NodeAndEdgeTypes,
totalScoreNodePrefix: NodeAddressT
@ -217,7 +192,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
const type = state.type;
switch (type) {
case "READY_TO_LOAD_GRAPH":
const loadedGraph = await this.loadGraph(assets, adapters);
const loadedGraph = await this.loadGraph(assets);
if (loadedGraph) {
await this.runPagerank(weights, types, totalScoreNodePrefix);
}
@ -232,15 +207,13 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
}
}
export type GraphWithAdapters = {|
+graph: Graph,
+adapters: DynamicExplorerAdapterSet,
|};
export async function loadGraphWithAdapters(
export async function doLoadGraph(
assets: Assets,
adapters: StaticExplorerAdapterSet,
repoId: RepoId
): Promise<GraphWithAdapters> {
const dynamicAdapters = await adapters.load(assets, repoId);
return {graph: dynamicAdapters.graph(), adapters: dynamicAdapters};
): Promise<Graph> {
const loadResult = await defaultLoader(assets, repoId);
if (loadResult.type !== "SUCCESS") {
throw new Error(loadResult);
}
return loadResult.timelineCred.graph();
}

View File

@ -1,20 +1,12 @@
// @flow
import {
StateTransitionMachine,
type AppState,
type GraphWithAdapters,
} from "./state";
import {StateTransitionMachine, type AppState} from "./state";
import {Graph, NodeAddress} from "../../core/graph";
import {Assets} from "../../webutil/assets";
import {makeRepoId, type RepoId} from "../../core/repoId";
import {type EdgeEvaluator} from "../../analysis/pagerank";
import {defaultWeights} from "../../analysis/weights";
import {
StaticExplorerAdapterSet,
DynamicExplorerAdapterSet,
} from "./adapters/explorerAdapterSet";
import type {
PagerankNodeDecomposition,
PagerankOptions,
@ -28,8 +20,8 @@ describe("explorer/legacy/state", () => {
stateContainer.appState = appState;
};
const loadGraphMock: JestMockFn<
[Assets, StaticExplorerAdapterSet, RepoId],
Promise<GraphWithAdapters>
[Assets, RepoId],
Promise<Graph>
> = jest.fn();
const pagerankMock: JestMockFn<
@ -56,27 +48,18 @@ describe("explorer/legacy/state", () => {
type: "READY_TO_RUN_PAGERANK",
repoId: makeRepoId("foo", "bar"),
loading: "NOT_LOADING",
graphWithAdapters: graphWithAdapters(),
graph: new Graph(),
};
}
function pagerankEvaluated(): AppState {
return {
type: "PAGERANK_EVALUATED",
repoId: makeRepoId("foo", "bar"),
graphWithAdapters: graphWithAdapters(),
graph: new Graph(),
pagerankNodeDecomposition: pagerankNodeDecomposition(),
loading: "NOT_LOADING",
};
}
function graphWithAdapters(): GraphWithAdapters {
return {
graph: new Graph(),
adapters: new DynamicExplorerAdapterSet(
new StaticExplorerAdapterSet([]),
[]
),
};
}
function pagerankNodeDecomposition() {
return new Map();
}
@ -92,45 +75,34 @@ describe("explorer/legacy/state", () => {
const badStates = [readyToRunPagerank(), pagerankEvaluated()];
for (const b of badStates) {
const {stm} = example(b);
await expect(
stm.loadGraph(
new Assets("/my/gateway/"),
new StaticExplorerAdapterSet([])
)
).rejects.toThrow("incorrect state");
await expect(stm.loadGraph(new Assets("/my/gateway/"))).rejects.toThrow(
"incorrect state"
);
}
});
it("passes along the adapters and repoId", () => {
it("passes along the repoId", () => {
const {stm, loadGraphMock} = example(readyToLoadGraph());
expect(loadGraphMock).toHaveBeenCalledTimes(0);
const assets = new Assets("/my/gateway/");
const adapters = new StaticExplorerAdapterSet([]);
stm.loadGraph(assets, adapters);
stm.loadGraph(assets);
expect(loadGraphMock).toHaveBeenCalledTimes(1);
expect(loadGraphMock).toHaveBeenCalledWith(
assets,
adapters,
makeRepoId("foo", "bar")
);
});
it("immediately sets loading status", () => {
const {getState, stm} = example(readyToLoadGraph());
expect(loading(getState())).toBe("NOT_LOADING");
stm.loadGraph(
new Assets("/my/gateway/"),
new StaticExplorerAdapterSet([])
);
stm.loadGraph(new Assets("/my/gateway/"));
expect(loading(getState())).toBe("LOADING");
expect(getState().type).toBe("READY_TO_LOAD_GRAPH");
});
it("transitions to READY_TO_RUN_PAGERANK on success", async () => {
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
const gwa = graphWithAdapters();
loadGraphMock.mockReturnValue(Promise.resolve(gwa));
const succeeded = await stm.loadGraph(
new Assets("/my/gateway/"),
new StaticExplorerAdapterSet([])
);
const graph = new Graph();
loadGraphMock.mockReturnValue(Promise.resolve(graph));
const succeeded = await stm.loadGraph(new Assets("/my/gateway/"));
expect(succeeded).toBe(true);
const state = getState();
expect(loading(state)).toBe("NOT_LOADING");
@ -138,7 +110,7 @@ describe("explorer/legacy/state", () => {
if (state.type !== "READY_TO_RUN_PAGERANK") {
throw new Error("Impossible");
}
expect(state.graphWithAdapters).toBe(gwa);
expect(state.graph).toBe(graph);
});
it("sets loading state FAILED on reject", async () => {
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
@ -146,10 +118,7 @@ describe("explorer/legacy/state", () => {
// $ExpectFlowError
console.error = jest.fn();
loadGraphMock.mockReturnValue(Promise.reject(error));
const succeeded = await stm.loadGraph(
new Assets("/my/gateway/"),
new StaticExplorerAdapterSet([])
);
const succeeded = await stm.loadGraph(new Assets("/my/gateway/"));
expect(succeeded).toBe(false);
const state = getState();
expect(loading(state)).toBe("FAILED");
@ -225,13 +194,12 @@ describe("explorer/legacy/state", () => {
(stm: any).runPagerank = jest.fn();
stm.loadGraph.mockResolvedValue(true);
const assets = new Assets("/gateway/");
const adapters = new StaticExplorerAdapterSet([]);
const prefix = NodeAddress.fromParts(["bar"]);
const types = defaultTypes();
const wt = defaultWeights();
await stm.loadGraphAndRunPagerank(assets, adapters, wt, types, prefix);
await stm.loadGraphAndRunPagerank(assets, wt, types, prefix);
expect(stm.loadGraph).toHaveBeenCalledTimes(1);
expect(stm.loadGraph).toHaveBeenCalledWith(assets, adapters);
expect(stm.loadGraph).toHaveBeenCalledWith(assets);
expect(stm.runPagerank).toHaveBeenCalledTimes(1);
expect(stm.runPagerank).toHaveBeenCalledWith(wt, types, prefix);
});
@ -241,11 +209,9 @@ describe("explorer/legacy/state", () => {
(stm: any).runPagerank = jest.fn();
stm.loadGraph.mockResolvedValue(false);
const assets = new Assets("/gateway/");
const adapters = new StaticExplorerAdapterSet([]);
const prefix = NodeAddress.fromParts(["bar"]);
await stm.loadGraphAndRunPagerank(
assets,
adapters,
defaultWeights(),
defaultTypes(),
prefix
@ -262,7 +228,6 @@ describe("explorer/legacy/state", () => {
const types = defaultTypes();
await stm.loadGraphAndRunPagerank(
new Assets("/gateway/"),
new StaticExplorerAdapterSet([]),
wt,
types,
prefix
@ -280,7 +245,6 @@ describe("explorer/legacy/state", () => {
const types = defaultTypes();
await stm.loadGraphAndRunPagerank(
new Assets("/gateway/"),
new StaticExplorerAdapterSet([]),
wt,
types,
prefix

View File

@ -3,26 +3,14 @@
import React from "react";
import type {Assets} from "../webutil/assets";
import {StaticExplorerAdapterSet} from "../explorer/legacy/adapters/explorerAdapterSet";
import {StaticExplorerAdapter as GithubAdapter} from "../plugins/github/explorerAdapter";
import {AppPage} from "../explorer/legacy/App";
import type {RepoId} from "../core/repoId";
function homepageStaticAdapters(): StaticExplorerAdapterSet {
return new StaticExplorerAdapterSet([new GithubAdapter()]);
}
export default class HomepageExplorer extends React.Component<{|
+assets: Assets,
+repoId: RepoId,
|}> {
render() {
return (
<AppPage
assets={this.props.assets}
repoId={this.props.repoId}
adapters={homepageStaticAdapters()}
/>
);
return <AppPage assets={this.props.assets} repoId={this.props.repoId} />;
}
}

View File

@ -1,50 +0,0 @@
// @flow
import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import {declaration} from "./declaration";
import type {
StaticExplorerAdapter,
DynamicExplorerAdapter,
} from "../../explorer/legacy/adapters/explorerAdapter";
import {StaticExplorerAdapterSet} from "../../explorer/legacy/adapters/explorerAdapterSet";
import {Assets} from "../../webutil/assets";
import {type RepoId, makeRepoId} from "../../core/repoId";
import {NodeAddress, type NodeAddressT} from "../../core/graph";
import {graph} from "./graph";
export class FactorioStaticAdapter implements StaticExplorerAdapter {
loadingMock: (assets: Assets, repoId: RepoId) => Promise<mixed>;
declaration(): PluginDeclaration {
return declaration;
}
async load(assets: Assets, repoId: RepoId) {
const result: FactorioDynamicAdapter = new FactorioDynamicAdapter();
if (this.loadingMock) {
return this.loadingMock(assets, repoId).then(() => result);
}
return Promise.resolve(result);
}
}
export class FactorioDynamicAdapter implements DynamicExplorerAdapter {
graph() {
return graph();
}
nodeDescription(x: NodeAddressT) {
return `[factorio]: ${NodeAddress.toString(x)}`;
}
static(): FactorioStaticAdapter {
return new FactorioStaticAdapter();
}
}
export function staticExplorerAdapterSet() {
return new StaticExplorerAdapterSet([new FactorioStaticAdapter()]);
}
export async function dynamicExplorerAdapterSet() {
return await staticExplorerAdapterSet().load(
new Assets("/gateway/"),
makeRepoId("foo", "bar")
);
}

View File

@ -1,77 +0,0 @@
// @flow
import type {
StaticExplorerAdapter as IStaticExplorerAdapter,
DynamicExplorerAdapter as IDynamicExplorerAdapter,
} from "../../explorer/legacy/adapters/explorerAdapter";
import {Graph} from "../../core/graph";
import * as N from "./nodes";
import {description} from "./render";
import type {Assets} from "../../webutil/assets";
import type {RepoId} from "../../core/repoId";
import type {Repository} from "./types";
import type {GitGateway} from "./gitGateway";
import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import {declaration} from "./declaration";
export class StaticExplorerAdapter implements IStaticExplorerAdapter {
_gitGateway: GitGateway;
constructor(gg: GitGateway): void {
this._gitGateway = gg;
}
declaration(): PluginDeclaration {
return declaration;
}
async load(assets: Assets, repoId: RepoId): Promise<IDynamicExplorerAdapter> {
const baseUrl = `/api/v1/data/data/${repoId.owner}/${repoId.name}/git/`;
async function loadGraph() {
const url = assets.resolve(baseUrl + "graph.json");
const response = await fetch(url);
if (!response.ok) {
return Promise.reject(response);
}
const json = await response.json();
return Graph.fromJSON(json);
}
async function loadRepository(): Promise<Repository> {
const url = assets.resolve(baseUrl + "repository.json");
const response = await fetch(url);
if (!response.ok) {
return Promise.reject(response);
}
return await response.json();
}
const [graph, repository] = await Promise.all([
loadGraph(),
loadRepository(),
]);
return new DynamicExplorerAdapter(this._gitGateway, graph, repository);
}
}
class DynamicExplorerAdapter implements IDynamicExplorerAdapter {
+_graph: Graph;
+_repository: Repository;
+_gitGateway: GitGateway;
constructor(
gitGateway: GitGateway,
graph: Graph,
repository: Repository
): void {
this._graph = graph;
this._repository = repository;
this._gitGateway = gitGateway;
}
graph() {
return this._graph;
}
nodeDescription(node) {
// This cast is unsound, and might throw at runtime, but won't have
// silent failures or cause problems down the road.
const address = N.fromRaw((node: any));
return description(address, this._repository, this._gitGateway);
}
static() {
return new StaticExplorerAdapter(this._gitGateway);
}
}

View File

@ -1,62 +0,0 @@
// @flow
import pako from "pako";
import type {
StaticExplorerAdapter as IStaticExplorerAdapter,
DynamicExplorerAdapter as IDynamicExplorerAdapter,
} from "../../explorer/legacy/adapters/explorerAdapter";
import {type Graph, NodeAddress} from "../../core/graph";
import {createGraph} from "./createGraph";
import * as N from "./nodes";
import {RelationalView} from "./relationalView";
import {description} from "./render";
import type {Assets} from "../../webutil/assets";
import type {RepoId} from "../../core/repoId";
import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import {declaration} from "./declaration";
export class StaticExplorerAdapter implements IStaticExplorerAdapter {
declaration(): PluginDeclaration {
return declaration;
}
async load(assets: Assets, repoId: RepoId): Promise<IDynamicExplorerAdapter> {
const url = assets.resolve(
`/api/v1/data/data/${repoId.owner}/${repoId.name}/github/view.json.gz`
);
const response = await fetch(url);
if (!response.ok) {
return Promise.reject(response);
}
const arrayBuffer = await response.arrayBuffer();
const blob = new Uint8Array(arrayBuffer);
const json = JSON.parse(pako.ungzip(blob, {to: "string"}));
const view = RelationalView.fromJSON(json);
const graph = createGraph(view);
return new DynamicExplorerAdapter(view, graph);
}
}
class DynamicExplorerAdapter implements IDynamicExplorerAdapter {
+_view: RelationalView;
+_graph: Graph;
constructor(view: RelationalView, graph: Graph): void {
this._view = view;
this._graph = graph;
}
nodeDescription(node) {
// This cast is unsound, and might throw at runtime, but won't have
// silent failures or cause problems down the road.
const address = N.fromRaw((node: any));
const entity = this._view.entity(address);
if (entity == null) {
throw new Error(`unknown entity: ${NodeAddress.toString(node)}`);
}
return description(entity);
}
graph() {
return this._graph;
}
static() {
return new StaticExplorerAdapter();
}
}