timelineCred.credSortedNodes takes prefixes (#1368)

This lets us filter by a group of prefixes simultaneously, which enables
e.g. seeing all user node types at once.

I also tweaked the API to make it a bit more convenient, you can now
pass no arguments and get all nodes in sorted order.

Test plan: Unit tests updated.
This commit is contained in:
Dandelion Mané 2019-09-10 19:44:03 +02:00 committed by GitHub
parent 65f22a0a74
commit 1079f5ec86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 31 deletions

View File

@ -138,12 +138,20 @@ export class TimelineCred {
} }
/** /**
* Return all the nodes matching the prefix, along with their cred, * Returns nodes sorted by their total cred (descending).
* sorted by total cred (descending). *
* If prefixes is provided, then only nodes matching at least one of the provided
* address prefixes will be included.
*/ */
credSortedNodes(prefix: NodeAddressT): $ReadOnlyArray<CredNode> { credSortedNodes(
const match = (a) => NodeAddress.hasPrefix(a, prefix); prefixes?: $ReadOnlyArray<NodeAddressT>
const addresses = Array.from(this._addressToCred.keys()).filter(match); ): $ReadOnlyArray<CredNode> {
let addresses = Array.from(this._addressToCred.keys());
if (prefixes != null) {
const match = (a) => prefixes.some((p) => NodeAddress.hasPrefix(a, p));
addresses = addresses.filter(match);
}
const credNodes = addresses.map((a) => this.credNode(a)); const credNodes = addresses.map((a) => this.credNode(a));
return sortBy(credNodes, (x: CredNode) => -x.total); return sortBy(credNodes, (x: CredNode) => -x.total);
} }
@ -173,18 +181,19 @@ export class TimelineCred {
const {typePrefixes, nodesPerType, fullInclusionPrefixes} = opts; const {typePrefixes, nodesPerType, fullInclusionPrefixes} = opts;
const selectedNodes: Set<NodeAddressT> = new Set(); const selectedNodes: Set<NodeAddressT> = new Set();
for (const prefix of typePrefixes) { for (const prefix of typePrefixes) {
const matchingNodes = this.credSortedNodes(prefix).slice(0, nodesPerType); const matchingNodes = this.credSortedNodes([prefix]).slice(
0,
nodesPerType
);
for (const {node} of matchingNodes) { for (const {node} of matchingNodes) {
selectedNodes.add(node.address); selectedNodes.add(node.address);
} }
} }
// For the fullInclusionPrefixes, we won't slice -- we just take every match. // For the fullInclusionPrefixes, we won't slice -- we just take every match.
for (const prefix of fullInclusionPrefixes) { const matchingNodes = this.credSortedNodes(fullInclusionPrefixes);
const matchingNodes = this.credSortedNodes(prefix);
for (const {node} of matchingNodes) { for (const {node} of matchingNodes) {
selectedNodes.add(node.address); selectedNodes.add(node.address);
} }
}
const filteredAddressToCred = new Map(); const filteredAddressToCred = new Map();
for (const address of selectedNodes) { for (const address of selectedNodes) {

View File

@ -93,23 +93,18 @@ describe("src/analysis/timeline/timelineCred", () => {
const json = exampleTimelineCred().toJSON(); const json = exampleTimelineCred().toJSON();
const tc_ = TimelineCred.fromJSON(json); const tc_ = TimelineCred.fromJSON(json);
expect(tc.graph()).toEqual(tc_.graph()); expect(tc.graph()).toEqual(tc_.graph());
expect(tc.params()).toEqual(tc_.params());
expect(tc.plugins()).toEqual(tc_.plugins());
expect(tc.credSortedNodes(NodeAddress.empty)).toEqual(
tc.credSortedNodes(NodeAddress.empty)
);
}); });
it("cred sorting works", () => { it("cred sorting works", () => {
const tc = exampleTimelineCred(); const tc = exampleTimelineCred();
const sorted = tc.credSortedNodes(NodeAddress.empty); const sorted = tc.credSortedNodes();
const expected = sortBy(sorted, (x) => -x.total); const expected = sortBy(sorted, (x) => -x.total);
expect(sorted).toEqual(expected); expect(sorted).toEqual(expected);
}); });
it("type filtering works", () => { it("prefix filtering works", () => {
const tc = exampleTimelineCred(); const tc = exampleTimelineCred();
const filtered = tc.credSortedNodes(userPrefix); const filtered = tc.credSortedNodes([userPrefix]);
for (const {node} of filtered) { for (const {node} of filtered) {
const isUser = NodeAddress.hasPrefix(node.address, userPrefix); const isUser = NodeAddress.hasPrefix(node.address, userPrefix);
expect(isUser).toBe(true); expect(isUser).toBe(true);
@ -117,9 +112,29 @@ describe("src/analysis/timeline/timelineCred", () => {
expect(filtered).toHaveLength(users.length); expect(filtered).toHaveLength(users.length);
}); });
it("prefix filtering can combine disjoint prefixes", () => {
const tc = exampleTimelineCred();
const filtered = tc.credSortedNodes([userPrefix, fooPrefix]);
const all = tc.credSortedNodes();
expect(filtered).toEqual(all);
});
it("prefix filtering will not result in node double-inclusion", () => {
const tc = exampleTimelineCred();
const filtered = tc.credSortedNodes([userPrefix, NodeAddress.empty]);
const all = tc.credSortedNodes();
expect(filtered).toEqual(all);
});
it("an empty list of prefixes results in an empty array", () => {
const tc = exampleTimelineCred();
const filtered = tc.credSortedNodes([]);
expect(filtered).toHaveLength(0);
});
it("cred aggregation works", () => { it("cred aggregation works", () => {
const tc = exampleTimelineCred(); const tc = exampleTimelineCred();
const nodes = tc.credSortedNodes(NodeAddress.empty); const nodes = tc.credSortedNodes();
for (const node of nodes) { for (const node of nodes) {
expect(node.total).toEqual(sum(node.cred)); expect(node.total).toEqual(sum(node.cred));
} }
@ -136,8 +151,8 @@ describe("src/analysis/timeline/timelineCred", () => {
}); });
const checkPrefix = (p: NodeAddressT) => { const checkPrefix = (p: NodeAddressT) => {
const fullNodes = tc.credSortedNodes(p); const fullNodes = tc.credSortedNodes([p]);
const truncatedNodes = filtered.credSortedNodes(p); const truncatedNodes = filtered.credSortedNodes([p]);
expect(truncatedNodes).toHaveLength(nodesPerType); expect(truncatedNodes).toHaveLength(nodesPerType);
expect(fullNodes.slice(0, nodesPerType)).toEqual(truncatedNodes); expect(fullNodes.slice(0, nodesPerType)).toEqual(truncatedNodes);
}; };
@ -153,10 +168,10 @@ describe("src/analysis/timeline/timelineCred", () => {
nodesPerType, nodesPerType,
fullInclusionPrefixes: [userPrefix], fullInclusionPrefixes: [userPrefix],
}); });
const fullUserNodes = tc.credSortedNodes(userPrefix); const fullUserNodes = tc.credSortedNodes([userPrefix]);
const truncatedUserNodes = filtered.credSortedNodes(userPrefix); const truncatedUserNodes = filtered.credSortedNodes([userPrefix]);
expect(fullUserNodes).toEqual(truncatedUserNodes); expect(fullUserNodes).toEqual(truncatedUserNodes);
const truncatedFoo = filtered.credSortedNodes(fooPrefix); const truncatedFoo = filtered.credSortedNodes([fooPrefix]);
expect(truncatedFoo).toHaveLength(0); expect(truncatedFoo).toHaveLength(0);
}); });
@ -168,11 +183,11 @@ describe("src/analysis/timeline/timelineCred", () => {
nodesPerType, nodesPerType,
fullInclusionPrefixes: [userPrefix, fooPrefix], fullInclusionPrefixes: [userPrefix, fooPrefix],
}); });
const fullUserNodes = tc.credSortedNodes(userPrefix); const fullUserNodes = tc.credSortedNodes([userPrefix]);
const truncatedUserNodes = filtered.credSortedNodes(userPrefix); const truncatedUserNodes = filtered.credSortedNodes([userPrefix]);
expect(fullUserNodes).toEqual(truncatedUserNodes); expect(fullUserNodes).toEqual(truncatedUserNodes);
const fullFoo = tc.credSortedNodes(fooPrefix); const fullFoo = tc.credSortedNodes([fooPrefix]);
const truncatedFoo = filtered.credSortedNodes(fooPrefix); const truncatedFoo = filtered.credSortedNodes([fooPrefix]);
expect(fullFoo).toEqual(truncatedFoo); expect(fullFoo).toEqual(truncatedFoo);
}); });
}); });

View File

@ -101,7 +101,7 @@ export const scores: Command = async (args, std) => {
const credJSON = JSON.parse(credBlob.toString()); const credJSON = JSON.parse(credBlob.toString());
const timelineCred = TimelineCred.fromJSON(credJSON); const timelineCred = TimelineCred.fromJSON(credJSON);
const userOutput: NodeOutput[] = timelineCred const userOutput: NodeOutput[] = timelineCred
.credSortedNodes(userNodeType.prefix) .credSortedNodes([userNodeType.prefix])
.map((n: CredNode) => { .map((n: CredNode) => {
const address = n.node.address; const address = n.node.address;
const structuredAddress = GN.fromRaw((address: any)); const structuredAddress = GN.fromRaw((address: any));

View File

@ -33,7 +33,7 @@ const DEFAULT_ENTRIES_PER_CHART = 6;
export class TimelineCredView extends React.Component<Props> { export class TimelineCredView extends React.Component<Props> {
render() { render() {
const {selectedNodeFilter, timelineCred} = this.props; const {selectedNodeFilter, timelineCred} = this.props;
const nodes = timelineCred.credSortedNodes(selectedNodeFilter); const nodes = timelineCred.credSortedNodes([selectedNodeFilter]);
const tableNodes = nodes.slice(0, MAX_ENTRIES_PER_LIST); const tableNodes = nodes.slice(0, MAX_ENTRIES_PER_LIST);
const chartNodes = nodes const chartNodes = nodes
.slice(0, DEFAULT_ENTRIES_PER_CHART) .slice(0, DEFAULT_ENTRIES_PER_CHART)