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,
* 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<CredNode> {
const match = (a) => NodeAddress.hasPrefix(a, prefix);
const addresses = Array.from(this._addressToCred.keys()).filter(match);
credSortedNodes(
prefixes?: $ReadOnlyArray<NodeAddressT>
): $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));
return sortBy(credNodes, (x: CredNode) => -x.total);
}
@ -173,17 +181,18 @@ export class TimelineCred {
const {typePrefixes, nodesPerType, fullInclusionPrefixes} = opts;
const selectedNodes: Set<NodeAddressT> = 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();

View File

@ -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);
});
});

View File

@ -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));

View File

@ -33,7 +33,7 @@ const DEFAULT_ENTRIES_PER_CHART = 6;
export class TimelineCredView extends React.Component<Props> {
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)