From d627475119ed8fea005dc4cb47ab9f2465dc2af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Sat, 30 Jun 2018 15:04:40 -0700 Subject: [PATCH] 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 --- src/v3/app/credExplorer/App.js | 89 +++++++++++++++------- src/v3/app/credExplorer/App.test.js | 113 ++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 28 deletions(-) create mode 100644 src/v3/app/credExplorer/App.test.js diff --git a/src/v3/app/credExplorer/App.js b/src/v3/app/credExplorer/App.js index 4a4c2c4..8ed2a01 100644 --- a/src/v3/app/credExplorer/App.js +++ b/src/v3/app/credExplorer/App.js @@ -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, +|}; 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 { this.state = { repoOwner: "", repoName: "", + data: {graph: null, pagerankResult: null, adapters: null}, }; } @@ -34,12 +45,12 @@ export default class App extends React.Component { } render() { + const {graph, adapters, pagerankResult} = this.state.data; return (

Cred Explorer

-

Welcome to the SourceCred Explorer!


+ + {graph ? ( +

+ Graph loaded: {Array.from(graph.nodes()).length} nodes,{" "} + {Array.from(graph.edges()).length} edges. +

+ ) : ( +

Graph not loaded.

+ )} +
); @@ -85,37 +132,23 @@ export default class App extends React.Component { 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}); }); } } diff --git a/src/v3/app/credExplorer/App.test.js b/src/v3/app/credExplorer/App.test.js new file mode 100644 index 0000000..3f578aa --- /dev/null +++ b/src/v3/app/credExplorer/App.test.js @@ -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(); + }); + it("renders with graph and adapters set", () => { + const app = shallow(); + const {graph, adapters} = example(); + const data = {graph, adapters, pagerankResult: null}; + app.setState({data}); + }); + it("renders with graph and adapters and pagerankResult", () => { + const app = shallow(); + const {graph, adapters, pagerankResult} = example(); + const data = {graph, adapters, pagerankResult}; + app.setState({data}); + }); +});