From 1079f5ec86dffb97ce29d93a21b7d08adf79bbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Tue, 10 Sep 2019 19:44:03 +0200 Subject: [PATCH] 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. --- src/analysis/timeline/timelineCred.js | 31 ++++++++----- src/analysis/timeline/timelineCred.test.js | 51 ++++++++++++++-------- src/cli/scores.js | 2 +- src/explorer/TimelineCredView.js | 2 +- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/analysis/timeline/timelineCred.js b/src/analysis/timeline/timelineCred.js index 7d7aaea..b0917f2 100644 --- a/src/analysis/timeline/timelineCred.js +++ b/src/analysis/timeline/timelineCred.js @@ -138,12 +138,20 @@ export class TimelineCred { } /** - * Return all the nodes matching the prefix, along with their cred, - * sorted by total cred (descending). + * Returns nodes sorted by their 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 { - const match = (a) => NodeAddress.hasPrefix(a, prefix); - const addresses = Array.from(this._addressToCred.keys()).filter(match); + credSortedNodes( + prefixes?: $ReadOnlyArray + ): $ReadOnlyArray { + 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)); return sortBy(credNodes, (x: CredNode) => -x.total); } @@ -173,17 +181,18 @@ export class TimelineCred { const {typePrefixes, nodesPerType, fullInclusionPrefixes} = opts; const selectedNodes: Set = new Set(); 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) { selectedNodes.add(node.address); } } // For the fullInclusionPrefixes, we won't slice -- we just take every match. - for (const prefix of fullInclusionPrefixes) { - const matchingNodes = this.credSortedNodes(prefix); - for (const {node} of matchingNodes) { - selectedNodes.add(node.address); - } + const matchingNodes = this.credSortedNodes(fullInclusionPrefixes); + for (const {node} of matchingNodes) { + selectedNodes.add(node.address); } const filteredAddressToCred = new Map(); diff --git a/src/analysis/timeline/timelineCred.test.js b/src/analysis/timeline/timelineCred.test.js index c1deab3..e0024dd 100644 --- a/src/analysis/timeline/timelineCred.test.js +++ b/src/analysis/timeline/timelineCred.test.js @@ -93,23 +93,18 @@ describe("src/analysis/timeline/timelineCred", () => { const json = exampleTimelineCred().toJSON(); const tc_ = TimelineCred.fromJSON(json); 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", () => { const tc = exampleTimelineCred(); - const sorted = tc.credSortedNodes(NodeAddress.empty); + const sorted = tc.credSortedNodes(); const expected = sortBy(sorted, (x) => -x.total); expect(sorted).toEqual(expected); }); - it("type filtering works", () => { + it("prefix filtering works", () => { const tc = exampleTimelineCred(); - const filtered = tc.credSortedNodes(userPrefix); + const filtered = tc.credSortedNodes([userPrefix]); for (const {node} of filtered) { const isUser = NodeAddress.hasPrefix(node.address, userPrefix); expect(isUser).toBe(true); @@ -117,9 +112,29 @@ describe("src/analysis/timeline/timelineCred", () => { 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", () => { const tc = exampleTimelineCred(); - const nodes = tc.credSortedNodes(NodeAddress.empty); + const nodes = tc.credSortedNodes(); for (const node of nodes) { expect(node.total).toEqual(sum(node.cred)); } @@ -136,8 +151,8 @@ describe("src/analysis/timeline/timelineCred", () => { }); const checkPrefix = (p: NodeAddressT) => { - const fullNodes = tc.credSortedNodes(p); - const truncatedNodes = filtered.credSortedNodes(p); + const fullNodes = tc.credSortedNodes([p]); + const truncatedNodes = filtered.credSortedNodes([p]); expect(truncatedNodes).toHaveLength(nodesPerType); expect(fullNodes.slice(0, nodesPerType)).toEqual(truncatedNodes); }; @@ -153,10 +168,10 @@ describe("src/analysis/timeline/timelineCred", () => { nodesPerType, fullInclusionPrefixes: [userPrefix], }); - const fullUserNodes = tc.credSortedNodes(userPrefix); - const truncatedUserNodes = filtered.credSortedNodes(userPrefix); + const fullUserNodes = tc.credSortedNodes([userPrefix]); + const truncatedUserNodes = filtered.credSortedNodes([userPrefix]); expect(fullUserNodes).toEqual(truncatedUserNodes); - const truncatedFoo = filtered.credSortedNodes(fooPrefix); + const truncatedFoo = filtered.credSortedNodes([fooPrefix]); expect(truncatedFoo).toHaveLength(0); }); @@ -168,11 +183,11 @@ describe("src/analysis/timeline/timelineCred", () => { nodesPerType, fullInclusionPrefixes: [userPrefix, fooPrefix], }); - const fullUserNodes = tc.credSortedNodes(userPrefix); - const truncatedUserNodes = filtered.credSortedNodes(userPrefix); + const fullUserNodes = tc.credSortedNodes([userPrefix]); + const truncatedUserNodes = filtered.credSortedNodes([userPrefix]); expect(fullUserNodes).toEqual(truncatedUserNodes); - const fullFoo = tc.credSortedNodes(fooPrefix); - const truncatedFoo = filtered.credSortedNodes(fooPrefix); + const fullFoo = tc.credSortedNodes([fooPrefix]); + const truncatedFoo = filtered.credSortedNodes([fooPrefix]); expect(fullFoo).toEqual(truncatedFoo); }); }); diff --git a/src/cli/scores.js b/src/cli/scores.js index 951e043..92740be 100644 --- a/src/cli/scores.js +++ b/src/cli/scores.js @@ -101,7 +101,7 @@ export const scores: Command = async (args, std) => { const credJSON = JSON.parse(credBlob.toString()); const timelineCred = TimelineCred.fromJSON(credJSON); const userOutput: NodeOutput[] = timelineCred - .credSortedNodes(userNodeType.prefix) + .credSortedNodes([userNodeType.prefix]) .map((n: CredNode) => { const address = n.node.address; const structuredAddress = GN.fromRaw((address: any)); diff --git a/src/explorer/TimelineCredView.js b/src/explorer/TimelineCredView.js index 08b7638..5dc420d 100644 --- a/src/explorer/TimelineCredView.js +++ b/src/explorer/TimelineCredView.js @@ -33,7 +33,7 @@ const DEFAULT_ENTRIES_PER_CHART = 6; export class TimelineCredView extends React.Component { render() { 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 chartNodes = nodes .slice(0, DEFAULT_ENTRIES_PER_CHART)