mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-19 07:56:33 +00:00
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:
parent
d8db763257
commit
378f627a6f
@ -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)
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user