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 {Graph} from "../core/graph";
|
||||||
import {type PluginDeclarations} from "./pluginDeclaration";
|
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.
|
* The CredView is an interface for Graph-aware queries over a CredResult.
|
||||||
*
|
*
|
||||||
|
@ -14,10 +66,21 @@ import {type PluginDeclarations} from "./pluginDeclaration";
|
||||||
* such queries convenient.
|
* such queries convenient.
|
||||||
*/
|
*/
|
||||||
export class CredView {
|
export class CredView {
|
||||||
_credResult: CredResult;
|
+_credResult: CredResult;
|
||||||
|
+_nodeAddressToIndex: Map<NodeAddressT, number>;
|
||||||
|
+_edgeAddressToIndex: Map<EdgeAddressT, number>;
|
||||||
|
+_nodeEvaluator: NodeWeightEvaluator;
|
||||||
|
+_edgeEvaluator: EdgeWeightEvaluator;
|
||||||
|
|
||||||
constructor(result: CredResult) {
|
constructor(result: CredResult) {
|
||||||
this._credResult = result;
|
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 {
|
graph(): Graph {
|
||||||
|
@ -39,4 +102,61 @@ export class CredView {
|
||||||
credResult(): CredResult {
|
credResult(): CredResult {
|
||||||
return this._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
|
// @flow
|
||||||
|
|
||||||
import {Graph, NodeAddress, EdgeAddress} from "../core/graph";
|
import {Graph, NodeAddress, EdgeAddress} from "../core/graph";
|
||||||
|
import {get as nullGet} from "../util/null";
|
||||||
import type {NodeType, EdgeType} from "./types";
|
import type {NodeType, EdgeType} from "./types";
|
||||||
import {
|
import {
|
||||||
type PluginDeclaration,
|
type PluginDeclaration,
|
||||||
|
@ -141,4 +142,102 @@ describe("analysis/credView", () => {
|
||||||
expect(credView.plugins()).toEqual([declaration]);
|
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