From 7fc31f6a262b4435bc5512fafe7bdaf1d4d0e76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Thu, 10 May 2018 14:49:00 -0700 Subject: [PATCH] Add `PagerankTable` for exploring PageRank results (#264) `PagerankTable` is forked from `ContributionList`. Test plan: I took it for a spin and it seemed OK. I'm not inclined to write formal tests because we are in rapid prototyping mode stil. --- src/app/credExplorer/App.js | 42 ++----- src/app/credExplorer/pagerankTable.js | 151 ++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 src/app/credExplorer/pagerankTable.js diff --git a/src/app/credExplorer/App.js b/src/app/credExplorer/App.js index 4ecbf89..e88b7be 100644 --- a/src/app/credExplorer/App.js +++ b/src/app/credExplorer/App.js @@ -1,19 +1,20 @@ // @flow -import stringify from "json-stable-stringify"; import React from "react"; import {StyleSheet, css} from "aphrodite/no-important"; import {Graph} from "../../core/graph"; -import type {PagerankResult} from "./basicPagerank"; import basicPagerank from "./basicPagerank"; import LocalStore from "./LocalStore"; +import type {PagerankResult} from "./basicPagerank"; +import {PagerankTable} from "./pagerankTable"; type Props = {}; type State = { repoOwner: string, repoName: string, graph: ?Graph, + pagerankResult: ?PagerankResult, }; const REPO_OWNER_KEY = "repoOwner"; @@ -26,6 +27,7 @@ export default class App extends React.Component { repoOwner: "", repoName: "", graph: null, + pagerankResult: null, }; } @@ -44,6 +46,10 @@ export default class App extends React.Component {

Cred Explorer

Welcome to the SourceCred Explorer!

+
@@ -119,34 +125,6 @@ export default class App extends React.Component { console.error("Error while fetching:", e); }); } - - analyzePagerankResult(pagerankResult: PagerankResult) { - const addressKey = ({pluginName, type}) => stringify({pluginName, type}); - const addressesByKey = {}; - pagerankResult.getAll().forEach(({address}) => { - if (addressesByKey[addressKey(address)] === undefined) { - addressesByKey[addressKey(address)] = []; - } - addressesByKey[addressKey(address)].push(address); - }); - Object.keys(addressesByKey).forEach((key) => { - addressesByKey[key] = addressesByKey[key] - .slice() - .sort((x, y) => { - const px = pagerankResult.get(x).probability; - const py = pagerankResult.get(y).probability; - return px - py; - }) - .reverse(); - const {pluginName, type} = JSON.parse(key); - console.log(`%c${type} (${pluginName})`, "font-weight: bold"); - addressesByKey[key].slice(0, 5).forEach((address) => { - const score = pagerankResult.get(address).probability; - const name = address.id; - console.log(` - [${score.toString()}] ${name}`); - }); - }); - } } const styles = StyleSheet.create({ diff --git a/src/app/credExplorer/pagerankTable.js b/src/app/credExplorer/pagerankTable.js new file mode 100644 index 0000000..372b7c7 --- /dev/null +++ b/src/app/credExplorer/pagerankTable.js @@ -0,0 +1,151 @@ +// @flow + +import React from "react"; +import stringify from "json-stable-stringify"; + +import {Graph} from "../../core/graph"; +import {PLUGIN_NAME as GITHUB_PLUGIN_NAME} from "../../plugins/github/pluginName"; +import {GIT_PLUGIN_NAME} from "../../plugins/git/types"; +import {nodeDescription as githubNodeDescription} from "../../plugins/github/render"; +import {nodeDescription as gitNodeDescription} from "../../plugins/git/render"; +import type {PagerankResult} from "./basicPagerank"; + +type Props = { + pagerankResult: ?PagerankResult, + graph: ?Graph, +}; + +type State = { + typeFilter: ?{| + +pluginName: string, + +type: string, + |}, +}; + +function nodeDescription(graph, address) { + switch (address.pluginName) { + case GITHUB_PLUGIN_NAME: { + return githubNodeDescription(graph, address); + } + case GIT_PLUGIN_NAME: { + return gitNodeDescription(graph, address); + } + default: { + return stringify(address); + } + } +} + +export class PagerankTable extends React.Component { + constructor() { + super(); + this.state = {typeFilter: null}; + } + + render() { + if (this.props.graph == null) { + return

You must load a graph before seeing PageRank analysis.

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

Please run PageRank to see analysis.

; + } + return ( +
+

Contributions

+ {this.renderFilterSelect()} + {this.renderTable()} +
+ ); + } + + renderFilterSelect() { + if (this.props.graph == null || this.props.pagerankResult == null) { + throw new Error("Impossible."); + } + const graph: Graph = this.props.graph; + const typesByPlugin: {[pluginName: string]: Set} = {}; + graph.nodes().forEach((node) => { + if (!typesByPlugin[node.address.pluginName]) { + typesByPlugin[node.address.pluginName] = new Set(); + } + typesByPlugin[node.address.pluginName].add(node.address.type); + }); + function optionGroup(pluginName: string) { + const header = ( + + ); + const entries = Array.from(typesByPlugin[pluginName]) + .sort() + .map((type) => ( + + )); + return [header, ...entries]; + } + return ( + + ); + } + + renderTable() { + if (this.props.graph == null || this.props.pagerankResult == null) { + throw new Error("Impossible."); + } + const {graph, pagerankResult} = this.props; + const nodesByScore = graph + .nodes() + .slice() + .filter( + (n) => + this.state.typeFilter + ? n.address.pluginName === this.state.typeFilter.pluginName && + n.address.type === this.state.typeFilter.type + : true + ) + .sort((a, b) => { + const x = pagerankResult.get(a.address).probability; + const y = pagerankResult.get(b.address).probability; + return x - y; + }) + .reverse(); + return ( + + + + + + + + + + {nodesByScore.map((node) => { + const score = pagerankResult.get(node.address).probability; + return ( + + + + + + ); + })} + +
ScoreLogScoreNode
{(score * 100).toPrecision(3)}{Math.log(score).toPrecision(3)}{nodeDescription(graph, node.address)}
+ ); + } +}