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:
parent
65f22a0a74
commit
1079f5ec86
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue