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.
This commit is contained in:
Dandelion Mané 2018-05-10 14:49:00 -07:00 committed by GitHub
parent bb2ec756a4
commit 7fc31f6a26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 161 additions and 32 deletions

View File

@ -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<mixed, mixed>,
pagerankResult: ?PagerankResult,
};
const REPO_OWNER_KEY = "repoOwner";
@ -26,6 +27,7 @@ export default class App extends React.Component<Props, State> {
repoOwner: "",
repoName: "",
graph: null,
pagerankResult: null,
};
}
@ -44,6 +46,10 @@ export default class App extends React.Component<Props, State> {
<h1>Cred Explorer</h1>
</header>
<p>Welcome to the SourceCred Explorer!</p>
<PagerankTable
graph={this.state.graph}
pagerankResult={this.state.pagerankResult}
/>
<div>
<label>
Repository owner:
@ -86,12 +92,12 @@ export default class App extends React.Component<Props, State> {
setTimeout(() => {
if (graph != null) {
const pagerankResult = basicPagerank(graph);
this.analyzePagerankResult(pagerankResult);
this.setState({pagerankResult});
}
}, 0);
}}
>
Run basic PageRank (results in console)
Run basic PageRank
</button>
</div>
</div>
@ -119,34 +125,6 @@ export default class App extends React.Component<Props, State> {
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({

View File

@ -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<any, any>,
};
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<Props, State> {
constructor() {
super();
this.state = {typeFilter: null};
}
render() {
if (this.props.graph == null) {
return <p>You must load a graph before seeing PageRank analysis.</p>;
}
if (this.props.pagerankResult == null) {
return <p>Please run PageRank to see analysis.</p>;
}
return (
<div>
<h2>Contributions</h2>
{this.renderFilterSelect()}
{this.renderTable()}
</div>
);
}
renderFilterSelect() {
if (this.props.graph == null || this.props.pagerankResult == null) {
throw new Error("Impossible.");
}
const graph: Graph<any, any> = this.props.graph;
const typesByPlugin: {[pluginName: string]: Set<string>} = {};
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 = (
<option key={pluginName} disabled style={{fontWeight: "bold"}}>
{pluginName}
</option>
);
const entries = Array.from(typesByPlugin[pluginName])
.sort()
.map((type) => (
<option key={type} value={JSON.stringify({pluginName, type})}>
{"\u2003" + type}
</option>
));
return [header, ...entries];
}
return (
<label>
Filter by contribution type:{" "}
<select
value={JSON.stringify(this.state.typeFilter)}
onChange={(e) => {
this.setState({typeFilter: JSON.parse(e.target.value)});
}}
>
<option value={JSON.stringify(null)}>Show all</option>
{Object.keys(typesByPlugin)
.sort()
.map(optionGroup)}
</select>
</label>
);
}
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 (
<table>
<thead>
<tr>
<th>Score</th>
<th>LogScore</th>
<th>Node</th>
</tr>
</thead>
<tbody>
{nodesByScore.map((node) => {
const score = pagerankResult.get(node.address).probability;
return (
<tr key={JSON.stringify(node.address)}>
<td>{(score * 100).toPrecision(3)}</td>
<td>{Math.log(score).toPrecision(3)}</td>
<td>{nodeDescription(graph, node.address)}</td>
</tr>
);
})}
</tbody>
</table>
);
}
}