From ab0fa81a4034a2750c76a692177a1e4749415eaa Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 23 Jul 2018 10:42:40 -0700 Subject: [PATCH] Show PageRank node decomposition in the explorer (#507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This commit hooks up the PageRank table to the PageRank node decomposition developed previously. The new cred explorer displays one entry per contribution to a node’s cred (i.e., one entry per in-edge, per out-edge, and per synthetic loop), listing the proportion of the node’s cred that is provided by this contribution. This makes it easy to observe facts like, “90% of this issue’s cred is due to being written by a particular author”. Paired with @decentralion. Test Plan: Unit tests added; run `yarn travis`. wchargin-branch: pagerank-table-node-decomposition --- src/app/credExplorer/App.js | 20 +- src/app/credExplorer/PagerankTable.js | 461 +++++++----- src/app/credExplorer/PagerankTable.test.js | 703 ++++++++++++------ .../__snapshots__/PagerankTable.test.js.snap | 61 +- .../__snapshots__/pagerank.test.js.snap | 59 -- src/core/attribution/pagerank.js | 11 +- src/core/attribution/pagerank.test.js | 34 - 7 files changed, 750 insertions(+), 599 deletions(-) delete mode 100644 src/core/attribution/__snapshots__/pagerank.test.js.snap delete mode 100644 src/core/attribution/pagerank.test.js diff --git a/src/app/credExplorer/App.js b/src/app/credExplorer/App.js index 7a1fd1e..28120a4 100644 --- a/src/app/credExplorer/App.js +++ b/src/app/credExplorer/App.js @@ -7,11 +7,12 @@ 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 NodeDistribution} from "../../core/attribution/pagerank"; +import {pagerank} from "../../core/attribution/pagerank"; import {PagerankTable} from "./PagerankTable"; import type {PluginAdapter} from "../pluginAdapter"; import {type EdgeEvaluator} from "../../core/attribution/pagerank"; import {WeightConfig} from "./WeightConfig"; +import type {PagerankNodeDecomposition} from "../../core/attribution/pagerankNodeDecomposition"; import * as NullUtil from "../../util/null"; @@ -26,13 +27,14 @@ type State = { +nodeCount: number, +edgeCount: number, |}, - +pagerankResult: ?NodeDistribution, + +pnd: ?PagerankNodeDecomposition, |}, 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 { constructor(props: Props) { @@ -40,7 +42,7 @@ export default class App extends React.Component { this.state = { repoOwner: "", repoName: "", - data: {graphWithMetadata: null, pagerankResult: null}, + data: {graphWithMetadata: null, pnd: null}, edgeEvaluator: null, }; } @@ -54,7 +56,7 @@ export default class App extends React.Component { render() { const {edgeEvaluator} = this.state; - const {graphWithMetadata, pagerankResult} = this.state.data; + const {graphWithMetadata, pnd} = this.state.data; return (
@@ -96,10 +98,10 @@ export default class App extends React.Component { throw new Error("Unexpected null value"); } const {graph} = graphWithMetadata; - const pagerankResult = pagerank(graph, edgeEvaluator, { + const pnd = pagerank(graph, edgeEvaluator, { verbose: true, }); - const data = {graphWithMetadata, pagerankResult}; + const data = {graphWithMetadata, pnd}; // In case a new graph was loaded while waiting for // PageRank. const stomped = @@ -123,9 +125,9 @@ export default class App extends React.Component { )} this.setState({edgeEvaluator: ee})} /> x.graph)} adapters={NullUtil.map(graphWithMetadata, (x) => x.adapters)} - pagerankResult={pagerankResult} + pnd={pnd} + maxEntriesPerList={MAX_ENTRIES_PER_LIST} />
@@ -166,7 +168,7 @@ export default class App extends React.Component { nodeCount: Array.from(graph.nodes()).length, edgeCount: Array.from(graph.edges()).length, }, - pagerankResult: null, + pnd: null, }; this.setState({data}); }); diff --git a/src/app/credExplorer/PagerankTable.js b/src/app/credExplorer/PagerankTable.js index 8968f67..70b51a7 100644 --- a/src/app/credExplorer/PagerankTable.js +++ b/src/app/credExplorer/PagerankTable.js @@ -4,29 +4,18 @@ import sortBy from "lodash.sortby"; import React from "react"; import { - Graph, - NodeAddress, - type NodeAddressT, - type Neighbor, - Direction, - type Edge, - EdgeAddress, type EdgeAddressT, + type NodeAddressT, + EdgeAddress, + NodeAddress, } from "../../core/graph"; -import type {NodeDistribution} from "../../core/attribution/pagerank"; +import type { + PagerankNodeDecomposition, + ScoredContribution, +} from "../../core/attribution/pagerankNodeDecomposition"; +import type {Contribution} from "../../core/attribution/graphToMarkovChain"; import type {PluginAdapter} from "../pluginAdapter"; - -const MAX_TABLE_ENTRIES = 100; - -type Props = { - pagerankResult: ?NodeDistribution, - graph: ?Graph, - adapters: ?$ReadOnlyArray, -}; - -type State = { - topLevelFilter: NodeAddressT, -}; +import * as NullUtil from "../../util/null"; // TODO: Factor this out and test it (#465) export function nodeDescription( @@ -74,37 +63,45 @@ function edgeVerb( } } -export function neighborVerb( - {node, edge}: Neighbor, - adapters: $ReadOnlyArray -): string { - const forwardVerb = edgeVerb(edge.address, "FORWARD", adapters); - const backwardVerb = edgeVerb(edge.address, "BACKWARD", adapters); - if (edge.src === edge.dst) { - return `${forwardVerb} and ${backwardVerb}`; - } else if (edge.dst === node) { - return forwardVerb; - } else { - return backwardVerb; - } +function scoreDisplay(probability: number) { + const modifiedLogScore = Math.log(probability) + 10; + return modifiedLogScore.toFixed(2); } -export class PagerankTable extends React.PureComponent { +type SharedProps = {| + +pnd: PagerankNodeDecomposition, + +adapters: $ReadOnlyArray, + +maxEntriesPerList: number, +|}; + +type PagerankTableProps = {| + +pnd: ?PagerankNodeDecomposition, + +adapters: ?$ReadOnlyArray, + +maxEntriesPerList: number, +|}; +type PagerankTableState = {|topLevelFilter: NodeAddressT|}; +export class PagerankTable extends React.PureComponent< + PagerankTableProps, + PagerankTableState +> { constructor() { super(); this.state = {topLevelFilter: NodeAddress.empty}; } render() { - if (this.props.graph == null || this.props.adapters == null) { + if (this.props.adapters == null) { return

You must load a graph before seeing PageRank analysis.

; } - if (this.props.pagerankResult == null) { + if (this.props.pnd == null) { return

Please run PageRank to see analysis.

; } + if (this.props.maxEntriesPerList == null) { + throw new Error("maxEntriesPerList not set"); + } return (
-

Contributions

+

PageRank results

{this.renderFilterSelect()} {this.renderTable()}
@@ -112,8 +109,8 @@ export class PagerankTable extends React.PureComponent { } renderFilterSelect() { - const {graph, pagerankResult, adapters} = this.props; - if (graph == null || pagerankResult == null || adapters == null) { + const {pnd, adapters} = this.props; + if (pnd == null || adapters == null) { throw new Error("Impossible."); } @@ -136,7 +133,7 @@ export class PagerankTable extends React.PureComponent { } return (