Add nodes and edges to CredView (#1839)
This adds support for retrieving cred-augmented nodes and edges to the CredView class. These methods wrap the underlying Graph methods, and return nodes and edges that also include relevant cred information Test plan: Unit tests included; yarn test passes.
This commit is contained in:
parent
753aec3acf
commit
8436dcee81
|
@ -6,6 +6,58 @@ import {type TimelineCredParameters} from "./timeline/params";
|
|||
import {Graph} from "../core/graph";
|
||||
import {type PluginDeclarations} from "./pluginDeclaration";
|
||||
|
||||
import {get as nullGet} from "../util/null";
|
||||
import {type EdgeWeight} from "../core/weights";
|
||||
import type {TimestampMs} from "../util/timestamp";
|
||||
import {
|
||||
type NodeWeightEvaluator,
|
||||
nodeWeightEvaluator,
|
||||
type EdgeWeightEvaluator,
|
||||
edgeWeightEvaluator,
|
||||
} from "../core/algorithm/weightEvaluator";
|
||||
import {
|
||||
type NodeAddressT,
|
||||
type EdgeAddressT,
|
||||
type Node as GraphNode,
|
||||
type Edge as GraphEdge,
|
||||
} from "../core/graph";
|
||||
import type {
|
||||
NodeCredSummary,
|
||||
NodeCredOverTime,
|
||||
EdgeCredSummary,
|
||||
EdgeCredOverTime,
|
||||
} from "./credData";
|
||||
|
||||
export type CredNode = {|
|
||||
+address: NodeAddressT,
|
||||
+description: string,
|
||||
+minted: number,
|
||||
+timestamp: TimestampMs | null,
|
||||
+credSummary: NodeCredSummary,
|
||||
+credOverTime: NodeCredOverTime | null,
|
||||
|};
|
||||
|
||||
export type CredEdge = {|
|
||||
+address: EdgeAddressT,
|
||||
+src: CredNode,
|
||||
+dst: CredNode,
|
||||
+rawWeight: EdgeWeight,
|
||||
+credSummary: EdgeCredSummary,
|
||||
+credOverTime: EdgeCredOverTime | null,
|
||||
+timestamp: TimestampMs,
|
||||
|};
|
||||
|
||||
export type EdgesOptions = {|
|
||||
// An edge address prefix. Only show edges whose addresses match this prefix.
|
||||
+addressPrefix?: EdgeAddressT,
|
||||
// A node address prefix. Only show edges whose src matches
|
||||
// this prefix.
|
||||
+srcPrefix?: NodeAddressT,
|
||||
// A node address prefix. Only show edges whose dst matches
|
||||
// this prefix.
|
||||
+dstPrefix?: NodeAddressT,
|
||||
|};
|
||||
|
||||
/**
|
||||
* The CredView is an interface for Graph-aware queries over a CredResult.
|
||||
*
|
||||
|
@ -14,10 +66,21 @@ import {type PluginDeclarations} from "./pluginDeclaration";
|
|||
* such queries convenient.
|
||||
*/
|
||||
export class CredView {
|
||||
_credResult: CredResult;
|
||||
+_credResult: CredResult;
|
||||
+_nodeAddressToIndex: Map<NodeAddressT, number>;
|
||||
+_edgeAddressToIndex: Map<EdgeAddressT, number>;
|
||||
+_nodeEvaluator: NodeWeightEvaluator;
|
||||
+_edgeEvaluator: EdgeWeightEvaluator;
|
||||
|
||||
constructor(result: CredResult) {
|
||||
this._credResult = result;
|
||||
const {weights, graph} = result.weightedGraph;
|
||||
const nodes = Array.from(graph.nodes());
|
||||
const edges = Array.from(graph.edges({showDangling: false}));
|
||||
this._nodeAddressToIndex = new Map(nodes.map((n, i) => [n.address, i]));
|
||||
this._edgeAddressToIndex = new Map(edges.map((n, i) => [n.address, i]));
|
||||
this._nodeEvaluator = nodeWeightEvaluator(weights);
|
||||
this._edgeEvaluator = edgeWeightEvaluator(weights);
|
||||
}
|
||||
|
||||
graph(): Graph {
|
||||
|
@ -39,4 +102,61 @@ export class CredView {
|
|||
credResult(): CredResult {
|
||||
return this._credResult;
|
||||
}
|
||||
|
||||
_promoteNode(n: GraphNode): CredNode {
|
||||
const idx = nullGet(this._nodeAddressToIndex.get(n.address));
|
||||
const credSummary = this._credResult.credData.nodeSummaries[idx];
|
||||
const credOverTime = this._credResult.credData.nodeOverTime[idx];
|
||||
const minted = this._nodeEvaluator(n.address);
|
||||
return {
|
||||
timestamp: n.timestampMs,
|
||||
description: n.description,
|
||||
address: n.address,
|
||||
credSummary,
|
||||
credOverTime,
|
||||
minted,
|
||||
};
|
||||
}
|
||||
node(a: NodeAddressT): ?CredNode {
|
||||
const graphNode = this.graph().node(a);
|
||||
if (graphNode == null) {
|
||||
return undefined;
|
||||
}
|
||||
return this._promoteNode(graphNode);
|
||||
}
|
||||
nodes(options?: {|+prefix: NodeAddressT|}): $ReadOnlyArray<CredNode> {
|
||||
const graphNodes = Array.from(this.graph().nodes(options));
|
||||
return graphNodes.map((x) => this._promoteNode(x));
|
||||
}
|
||||
|
||||
_promoteEdge(e: GraphEdge): CredEdge {
|
||||
const idx = nullGet(this._edgeAddressToIndex.get(e.address));
|
||||
const srcNode = nullGet(this.graph().node(e.src));
|
||||
const dstNode = nullGet(this.graph().node(e.dst));
|
||||
const credSummary = this._credResult.credData.edgeSummaries[idx];
|
||||
const credOverTime = this._credResult.credData.edgeOverTime[idx];
|
||||
const rawWeight = this._edgeEvaluator(e.address);
|
||||
return {
|
||||
timestamp: e.timestampMs,
|
||||
address: e.address,
|
||||
src: this._promoteNode(srcNode),
|
||||
dst: this._promoteNode(dstNode),
|
||||
credSummary,
|
||||
credOverTime,
|
||||
rawWeight,
|
||||
};
|
||||
}
|
||||
edge(a: EdgeAddressT): ?CredEdge {
|
||||
const graphEdge = this.graph().edge(a);
|
||||
if (graphEdge == null || this.graph().isDanglingEdge(a)) {
|
||||
return undefined;
|
||||
}
|
||||
return this._promoteEdge(graphEdge);
|
||||
}
|
||||
edges(options?: EdgesOptions): $ReadOnlyArray<CredEdge> {
|
||||
const graphEdges = Array.from(
|
||||
this.graph().edges({...options, showDangling: false})
|
||||
);
|
||||
return graphEdges.map((x) => this._promoteEdge(x));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import {Graph, NodeAddress, EdgeAddress} from "../core/graph";
|
||||
import {get as nullGet} from "../util/null";
|
||||
import type {NodeType, EdgeType} from "./types";
|
||||
import {
|
||||
type PluginDeclaration,
|
||||
|
@ -141,4 +142,102 @@ describe("analysis/credView", () => {
|
|||
expect(credView.plugins()).toEqual([declaration]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("nodes", () => {
|
||||
it("can retrieve a CredNode", async () => {
|
||||
const {credView, foo1, graph, credResult} = await example();
|
||||
const {
|
||||
address,
|
||||
credOverTime,
|
||||
credSummary,
|
||||
description,
|
||||
minted,
|
||||
timestamp,
|
||||
} = nullGet(credView.node(foo1.address));
|
||||
expect({address, description, timestampMs: timestamp}).toEqual(foo1);
|
||||
expect(minted).toEqual(2);
|
||||
const nodeOrder = Array.from(graph.nodes()).map((x) => x.address);
|
||||
const index = nodeOrder.findIndex((x) => x === foo1.address);
|
||||
expect(credOverTime).toEqual(credResult.credData.nodeOverTime[index]);
|
||||
expect(credSummary).toEqual(credResult.credData.nodeSummaries[index]);
|
||||
});
|
||||
it("returns undefined for non-existent node", async () => {
|
||||
const {credView} = await example();
|
||||
expect(credView.node(NodeAddress.fromParts(["nope"]))).toBe(undefined);
|
||||
});
|
||||
it("returns array of all nodes when no arguments provided", async () => {
|
||||
const {credView, foo1, foo2, user} = await example();
|
||||
expect(credView.nodes()).toEqual(
|
||||
[foo1, foo2, user].map((x) => credView.node(x.address))
|
||||
);
|
||||
});
|
||||
it("nodes can filter by prefix", async () => {
|
||||
const {credView, foo1, foo2, fooType} = await example();
|
||||
expect(credView.nodes({prefix: fooType.prefix})).toEqual(
|
||||
[foo1, foo2].map((x) => credView.node(x.address))
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("edges", () => {
|
||||
it("can retrieve a CredEdge", async () => {
|
||||
const {credView, flow1, credResult, graph} = await example();
|
||||
const {
|
||||
address,
|
||||
src,
|
||||
dst,
|
||||
credOverTime,
|
||||
credSummary,
|
||||
rawWeight,
|
||||
timestamp,
|
||||
} = nullGet(credView.edge(flow1.address));
|
||||
expect({
|
||||
address,
|
||||
src: src.address,
|
||||
dst: dst.address,
|
||||
timestampMs: timestamp,
|
||||
}).toEqual(flow1);
|
||||
const edgeOrder = Array.from(graph.edges({showDangling: false})).map(
|
||||
(x) => x.address
|
||||
);
|
||||
const index = edgeOrder.findIndex((x) => x === flow1.address);
|
||||
expect(src).toEqual(credView.node(src.address));
|
||||
expect(dst).toEqual(credView.node(dst.address));
|
||||
expect(rawWeight).toEqual({forwards: 2, backwards: 3});
|
||||
expect(credOverTime).toEqual(credResult.credData.edgeOverTime[index]);
|
||||
expect(credSummary).toEqual(credResult.credData.edgeSummaries[index]);
|
||||
});
|
||||
it("returns undefined for non-existent edge", async () => {
|
||||
const {credView} = await example();
|
||||
expect(credView.edge(EdgeAddress.fromParts(["nope"]))).toBe(undefined);
|
||||
});
|
||||
it("returns undefined for a dangling edge", async () => {
|
||||
const {credView, dangling} = await example();
|
||||
expect(credView.edge(dangling.address)).toBe(undefined);
|
||||
});
|
||||
it("returns array of all non-dangling edges when no arguments provided", async () => {
|
||||
const {credView, flow1, flow2, stream1} = await example();
|
||||
expect(credView.edges()).toEqual(
|
||||
[flow1, flow2, stream1].map((x) => credView.edge(x.address))
|
||||
);
|
||||
});
|
||||
it("edges can filter by address prefix", async () => {
|
||||
const {credView, flow1, flow2, flowType} = await example();
|
||||
expect(credView.edges({addressPrefix: flowType.prefix})).toEqual(
|
||||
[flow1, flow2].map((x) => credView.edge(x.address))
|
||||
);
|
||||
});
|
||||
it("edges can filter by src prefix", async () => {
|
||||
const {credView, stream1, userType} = await example();
|
||||
expect(credView.edges({srcPrefix: userType.prefix})).toEqual([
|
||||
credView.edge(stream1.address),
|
||||
]);
|
||||
});
|
||||
it("edges can filter by dst prefix", async () => {
|
||||
const {credView, flow1, flow2, userType} = await example();
|
||||
expect(credView.edges({dstPrefix: userType.prefix})).toEqual([
|
||||
credView.edge(flow1.address),
|
||||
credView.edge(flow2.address),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue