Integrate PageRank to the v3 cred explorer! (#468)
This integrates the PageRank table from #466 into the v3 cred explorer app, bringing the v3 frontend to better-than-parity with v1! Test plan: Some unit tests were included, and running `yarn start` and inspecting the App reveals that it is working correctly. Loading a PageRank result and then changing the repository no longer triggers a crash :). Paired with @wchargin
This commit is contained in:
parent
c10ce0060b
commit
d627475119
|
@ -7,11 +7,21 @@ import LocalStore from "./LocalStore";
|
|||
import {createPluginAdapter as createGithubAdapter} from "../../plugins/github/pluginAdapter";
|
||||
import {createPluginAdapter as createGitAdapter} from "../../plugins/git/pluginAdapter";
|
||||
import {Graph} from "../../core/graph";
|
||||
import {pagerank, type PagerankResult} from "../../core/attribution/pagerank";
|
||||
import {PagerankTable} from "./PagerankTable";
|
||||
import type {PluginAdapter} from "../pluginAdapter";
|
||||
|
||||
type GraphWithMetadata = {|
|
||||
+graph: ?Graph,
|
||||
+pagerankResult: ?PagerankResult,
|
||||
adapters: ?$ReadOnlyArray<PluginAdapter>,
|
||||
|};
|
||||
|
||||
type Props = {};
|
||||
type State = {
|
||||
repoOwner: string,
|
||||
repoName: string,
|
||||
data: GraphWithMetadata,
|
||||
};
|
||||
|
||||
const REPO_OWNER_KEY = "repoOwner";
|
||||
|
@ -23,6 +33,7 @@ export default class App extends React.Component<Props, State> {
|
|||
this.state = {
|
||||
repoOwner: "",
|
||||
repoName: "",
|
||||
data: {graph: null, pagerankResult: null, adapters: null},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,12 +45,12 @@ export default class App extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {graph, adapters, pagerankResult} = this.state.data;
|
||||
return (
|
||||
<div>
|
||||
<header className={css(styles.header)}>
|
||||
<h1>Cred Explorer</h1>
|
||||
</header>
|
||||
<p>Welcome to the SourceCred Explorer!</p>
|
||||
<div>
|
||||
<label>
|
||||
Repository owner:
|
||||
|
@ -68,6 +79,42 @@ export default class App extends React.Component<Props, State> {
|
|||
</label>
|
||||
<br />
|
||||
<button onClick={() => this.loadData()}>Load data</button>
|
||||
<button
|
||||
disabled={graph == null}
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
if (graph != null) {
|
||||
const edgeWeight = (_unused_Edge) => ({
|
||||
toWeight: 1,
|
||||
froWeight: 1,
|
||||
});
|
||||
const pagerankResult = pagerank(graph, edgeWeight, {
|
||||
verbose: true,
|
||||
});
|
||||
const data = {graph, pagerankResult, adapters};
|
||||
// In case a new graph was loaded while waiting for PageRank
|
||||
if (this.state.data.graph === graph) {
|
||||
this.setState({data});
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
Run basic PageRank
|
||||
</button>
|
||||
{graph ? (
|
||||
<p>
|
||||
Graph loaded: {Array.from(graph.nodes()).length} nodes,{" "}
|
||||
{Array.from(graph.edges()).length} edges.
|
||||
</p>
|
||||
) : (
|
||||
<p>Graph not loaded.</p>
|
||||
)}
|
||||
<PagerankTable
|
||||
graph={graph}
|
||||
pagerankResult={pagerankResult}
|
||||
adapters={adapters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -85,37 +132,23 @@ export default class App extends React.Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
const githubGraphPromise = createGithubAdapter(repoOwner, repoName).then(
|
||||
(githubAdapter) => {
|
||||
const graph = githubAdapter.graph();
|
||||
const nodeCount = Array.from(graph.nodes()).length;
|
||||
const edgeCount = Array.from(graph.edges()).length;
|
||||
console.log(
|
||||
`GitHub: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
|
||||
);
|
||||
return graph;
|
||||
const githubPromise = createGithubAdapter(repoOwner, repoName).then(
|
||||
(adapter) => {
|
||||
const graph = adapter.graph();
|
||||
return {graph, adapter};
|
||||
}
|
||||
);
|
||||
|
||||
const gitGraphPromise = createGitAdapter(repoOwner, repoName).then(
|
||||
(gitAdapter) => {
|
||||
const graph = gitAdapter.graph();
|
||||
const nodeCount = Array.from(graph.nodes()).length;
|
||||
const edgeCount = Array.from(graph.edges()).length;
|
||||
console.log(
|
||||
`Git: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
|
||||
);
|
||||
return graph;
|
||||
}
|
||||
);
|
||||
const gitPromise = createGitAdapter(repoOwner, repoName).then((adapter) => {
|
||||
const graph = adapter.graph();
|
||||
return {graph, adapter};
|
||||
});
|
||||
|
||||
Promise.all([gitGraphPromise, githubGraphPromise]).then((graphs) => {
|
||||
const graph = Graph.merge(graphs);
|
||||
const nodeCount = Array.from(graph.nodes()).length;
|
||||
const edgeCount = Array.from(graph.edges()).length;
|
||||
console.log(
|
||||
`Combined: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
|
||||
);
|
||||
Promise.all([gitPromise, githubPromise]).then((graphsAndAdapters) => {
|
||||
const graph = Graph.merge(graphsAndAdapters.map((x) => x.graph));
|
||||
const adapters = graphsAndAdapters.map((x) => x.adapter);
|
||||
const data = {graph, adapters, pagerankResult: null};
|
||||
this.setState({data});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import {shallow} from "enzyme";
|
||||
|
||||
import {pagerank} from "../../core/attribution/pagerank";
|
||||
import App from "./App";
|
||||
|
||||
import {Graph, NodeAddress, EdgeAddress} from "../../core/graph";
|
||||
|
||||
require("../testUtil").configureEnzyme();
|
||||
require("../testUtil").configureAphrodite();
|
||||
|
||||
function example() {
|
||||
const graph = new Graph();
|
||||
const nodes = {
|
||||
fooAlpha: NodeAddress.fromParts(["foo", "a", "1"]),
|
||||
fooBeta: NodeAddress.fromParts(["foo", "b", "2"]),
|
||||
bar1: NodeAddress.fromParts(["bar", "a", "1"]),
|
||||
bar2: NodeAddress.fromParts(["bar", "2"]),
|
||||
xox: NodeAddress.fromParts(["xox"]),
|
||||
empty: NodeAddress.empty,
|
||||
};
|
||||
Object.values(nodes).forEach((n) => graph.addNode((n: any)));
|
||||
|
||||
function addEdge(parts, src, dst) {
|
||||
const edge = {address: EdgeAddress.fromParts(parts), src, dst};
|
||||
graph.addEdge(edge);
|
||||
}
|
||||
|
||||
addEdge(["a"], nodes.fooAlpha, nodes.fooBeta);
|
||||
addEdge(["b"], nodes.fooAlpha, nodes.bar1);
|
||||
addEdge(["c"], nodes.fooAlpha, nodes.xox);
|
||||
addEdge(["d"], nodes.bar1, nodes.bar1);
|
||||
addEdge(["e"], nodes.bar1, nodes.xox);
|
||||
addEdge(["e'"], nodes.bar1, nodes.xox);
|
||||
|
||||
const adapters = [
|
||||
{
|
||||
name: () => "foo",
|
||||
graph: () => {
|
||||
throw new Error("unused");
|
||||
},
|
||||
renderer: () => ({
|
||||
nodeDescription: (x) => `foo: ${NodeAddress.toString(x)}`,
|
||||
}),
|
||||
nodePrefix: () => NodeAddress.fromParts(["foo"]),
|
||||
nodeTypes: () => [
|
||||
{name: "alpha", prefix: NodeAddress.fromParts(["foo", "a"])},
|
||||
{name: "beta", prefix: NodeAddress.fromParts(["foo", "b"])},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: () => "bar",
|
||||
graph: () => {
|
||||
throw new Error("unused");
|
||||
},
|
||||
renderer: () => ({
|
||||
nodeDescription: (x) => `bar: ${NodeAddress.toString(x)}`,
|
||||
}),
|
||||
nodePrefix: () => NodeAddress.fromParts(["bar"]),
|
||||
nodeTypes: () => [
|
||||
{name: "alpha", prefix: NodeAddress.fromParts(["bar", "a"])},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: () => "xox",
|
||||
graph: () => {
|
||||
throw new Error("unused");
|
||||
},
|
||||
renderer: () => ({
|
||||
nodeDescription: (_unused_arg) => `xox node!`,
|
||||
}),
|
||||
nodePrefix: () => NodeAddress.fromParts(["xox"]),
|
||||
nodeTypes: () => [],
|
||||
},
|
||||
{
|
||||
name: () => "unused",
|
||||
graph: () => {
|
||||
throw new Error("unused");
|
||||
},
|
||||
renderer: () => {
|
||||
throw new Error("Impossible!");
|
||||
},
|
||||
nodePrefix: () => NodeAddress.fromParts(["unused"]),
|
||||
nodeTypes: () => [],
|
||||
},
|
||||
];
|
||||
|
||||
const pagerankResult = pagerank(graph, (_unused_Edge) => ({
|
||||
toWeight: 1,
|
||||
froWeight: 1,
|
||||
}));
|
||||
|
||||
return {adapters, nodes, graph, pagerankResult};
|
||||
}
|
||||
|
||||
describe("app/credExplorer/App", () => {
|
||||
it("renders with clean state", () => {
|
||||
shallow(<App />);
|
||||
});
|
||||
it("renders with graph and adapters set", () => {
|
||||
const app = shallow(<App />);
|
||||
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 {graph, adapters, pagerankResult} = example();
|
||||
const data = {graph, adapters, pagerankResult};
|
||||
app.setState({data});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue