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,
|
* 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) {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue