Add aggregateFlat and flattenAggregation (#629)

For #502: The UI that I currently have in mind displays aggregations
grouped by connection type and node type together, rather than nested.
I think it will be cumbersome to have multiple hierarchical
levels of expansion.

To make that UI easy to write, this commit adds some logic for
flattening the hiearchical aggregation from #624. I add an extra
translation to flatten, rather than just having the logic produce nested
structures, because it's convenient to keep around the nested structure
in case I decide to implement the hierarchical UI instead. Once we have
solidified how we want the UI to behave, we might choose to simplify
this code.

Test plan: The implementation is rather simple. There are some unit
tests.
This commit is contained in:
Dandelion Mané 2018-08-10 17:05:59 -07:00 committed by GitHub
parent d8db763257
commit 378f627a6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 115 additions and 1 deletions

View File

@ -6,6 +6,8 @@ import {NodeTrie, EdgeTrie} from "../../../core/trie";
import type {NodeType, EdgeType} from "../../adapters/pluginAdapter";
import type {ScoredConnection} from "../../../core/attribution/pagerankNodeDecomposition";
// Sorted by descending `summary.score`
export type FlatAggregations = $ReadOnlyArray<FlatAggregation>;
// Sorted by descending `summary.score`
export type ConnectionAggregations = $ReadOnlyArray<ConnectionAggregation>;
@ -32,6 +34,14 @@ export type ConnectionAggregation = {|
+nodeAggregations: $ReadOnlyArray<NodeAggregation>,
|};
export type FlatAggregation = {|
+connectionType: ConnectionType,
+nodeType: NodeType,
+summary: AggregationSummary,
// sorted by `scoredConnection.connectionScore`
+connections: $ReadOnlyArray<ScoredConnection>,
|};
export function aggregateByNodeType(
xs: $ReadOnlyArray<ScoredConnection>,
nodeTypes: $ReadOnlyArray<NodeType>
@ -146,3 +156,31 @@ export function aggregateByConnectionType(
return sortBy(result, (x) => -x.summary.score);
}
export function flattenAggregation(
xs: ConnectionAggregations
): FlatAggregations {
const result = [];
for (const {connectionType, nodeAggregations} of xs) {
for (const {summary, connections, nodeType} of nodeAggregations) {
const flat: FlatAggregation = {
summary,
connections,
nodeType,
connectionType,
};
result.push(flat);
}
}
return sortBy(result, (x) => -x.summary.score);
}
export function aggregateFlat(
xs: $ReadOnlyArray<ScoredConnection>,
nodeTypes: $ReadOnlyArray<NodeType>,
edgeTypes: $ReadOnlyArray<EdgeType>
): FlatAggregations {
return flattenAggregation(
aggregateByConnectionType(xs, nodeTypes, edgeTypes)
);
}

View File

@ -1,7 +1,13 @@
// @flow
import {EdgeAddress, NodeAddress} from "../../../core/graph";
import {aggregateByNodeType, aggregateByConnectionType} from "./aggregate";
import * as NullUtil from "../../../util/null";
import {
aggregateByNodeType,
aggregateByConnectionType,
flattenAggregation,
aggregateFlat,
} from "./aggregate";
describe("app/credExplorer/aggregate", () => {
function example() {
@ -340,4 +346,74 @@ describe("app/credExplorer/aggregate", () => {
}
});
});
describe("flattenAggregation", () => {
function getFlatAggregations() {
const {
nodeTypesArray,
edgeTypesArray,
scoredConnectionsArray,
} = example();
const byCT = aggregateByConnectionType(
scoredConnectionsArray,
nodeTypesArray,
edgeTypesArray
);
const flat = flattenAggregation(byCT);
return {byCT, flat};
}
it("works on an empty aggregation", () => {
expect(flattenAggregation([])).toEqual([]);
});
it("returns aggregations in score order", () => {
const {flat} = getFlatAggregations();
let lastScore = Infinity;
for (const agg of flat) {
const score = agg.summary.score;
expect(lastScore >= score).toBe(true);
lastScore = score;
}
});
it("each FlatAggregation corresponds to a nested NodeAggregation", () => {
const {flat, byCT} = getFlatAggregations();
for (const agg of flat) {
const matchingConnectionAggregation = NullUtil.get(
byCT.find((x) => x.connectionType === agg.connectionType)
);
const matchingNodeAggregation = NullUtil.get(
matchingConnectionAggregation.nodeAggregations.find(
(x) => x.nodeType === agg.nodeType
)
);
expect(agg.summary).toEqual(matchingNodeAggregation.summary);
expect(agg.connections).toEqual(matchingNodeAggregation.connections);
}
let numNodeAggregations = 0;
for (const agg of byCT) {
numNodeAggregations += agg.nodeAggregations.length;
}
expect(numNodeAggregations).toEqual(flat.length);
});
});
describe("aggregateFlat", () => {
it("is the composition of aggregateByConnectionType and flattenAggregation", () => {
const {
nodeTypesArray,
edgeTypesArray,
scoredConnectionsArray,
} = example();
const byCT = aggregateByConnectionType(
scoredConnectionsArray,
nodeTypesArray,
edgeTypesArray
);
const flat = flattenAggregation(byCT);
const fromScratch = aggregateFlat(
scoredConnectionsArray,
nodeTypesArray,
edgeTypesArray
);
expect(fromScratch).toEqual(flat);
});
});
});