mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-21 00:38:19 +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 {NodeType, EdgeType} from "../../adapters/pluginAdapter";
|
||||||
import type {ScoredConnection} from "../../../core/attribution/pagerankNodeDecomposition";
|
import type {ScoredConnection} from "../../../core/attribution/pagerankNodeDecomposition";
|
||||||
|
|
||||||
|
// Sorted by descending `summary.score`
|
||||||
|
export type FlatAggregations = $ReadOnlyArray<FlatAggregation>;
|
||||||
// Sorted by descending `summary.score`
|
// Sorted by descending `summary.score`
|
||||||
export type ConnectionAggregations = $ReadOnlyArray<ConnectionAggregation>;
|
export type ConnectionAggregations = $ReadOnlyArray<ConnectionAggregation>;
|
||||||
|
|
||||||
@ -32,6 +34,14 @@ export type ConnectionAggregation = {|
|
|||||||
+nodeAggregations: $ReadOnlyArray<NodeAggregation>,
|
+nodeAggregations: $ReadOnlyArray<NodeAggregation>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
export type FlatAggregation = {|
|
||||||
|
+connectionType: ConnectionType,
|
||||||
|
+nodeType: NodeType,
|
||||||
|
+summary: AggregationSummary,
|
||||||
|
// sorted by `scoredConnection.connectionScore`
|
||||||
|
+connections: $ReadOnlyArray<ScoredConnection>,
|
||||||
|
|};
|
||||||
|
|
||||||
export function aggregateByNodeType(
|
export function aggregateByNodeType(
|
||||||
xs: $ReadOnlyArray<ScoredConnection>,
|
xs: $ReadOnlyArray<ScoredConnection>,
|
||||||
nodeTypes: $ReadOnlyArray<NodeType>
|
nodeTypes: $ReadOnlyArray<NodeType>
|
||||||
@ -146,3 +156,31 @@ export function aggregateByConnectionType(
|
|||||||
|
|
||||||
return sortBy(result, (x) => -x.summary.score);
|
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
|
// @flow
|
||||||
|
|
||||||
import {EdgeAddress, NodeAddress} from "../../../core/graph";
|
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", () => {
|
describe("app/credExplorer/aggregate", () => {
|
||||||
function example() {
|
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