diff --git a/modules/server/src/network/aggregations/index.ts b/modules/server/src/network/aggregations/index.ts index fdf269fdf..81e160fa8 100644 --- a/modules/server/src/network/aggregations/index.ts +++ b/modules/server/src/network/aggregations/index.ts @@ -1,2 +1,51 @@ +import { SupportedAggregation, SUPPORTED_AGGREGATIONS } from '../common'; +import { Aggregations, NetworkAggregation, NumericAggregations } from '../types'; + // TODO: implement export const resolveAggregations = (networkResults: void) => null; + +/** + * Resolve aggregation based on aggregation type + * @param type + * @param aggregations + */ +export const resolveToNetworkAggregation = ( + type: SupportedAggregation, + aggregations: Aggregations[], +) => { + if (type === SUPPORTED_AGGREGATIONS.Aggregations) { + resolveAggregation(aggregations); + } else if (type === SUPPORTED_AGGREGATIONS.NumericAggregations) { + resolveNumericAggregation(aggregations); + } else { + // no types match + throw Error('No matching aggregation type'); + } +}; + +/** + * Takes an array of the same aggregation type and computes the singular type + * eg. NumericAggregation => NetworkNumericAggregation + * + * @param aggregations + * @returns + */ +export const resolveAggregation = (aggregations: Aggregations[]): NetworkAggregation => { + const emptyAggregation: NetworkAggregation = { bucket_count: 0, buckets: [] }; + + const resolvedAggregation = aggregations.reduce((resolvedAggregation, agg) => { + const computedBucketCount = resolvedAggregation.bucket_count + agg.bucket_count; + const computedBuckets = agg.buckets.map(({ key, doc_count }) => { + // potentially expensive "find" if array of buckets is very large + const bucket = resolvedAggregation.buckets.find((bucket) => bucket.key === key); + return { key, doc_count: (bucket?.doc_count || 0) + doc_count }; + }); + return { bucket_count: computedBucketCount, buckets: computedBuckets }; + }, emptyAggregation); + return resolvedAggregation; +}; + +const resolveNumericAggregation = (aggregations: NumericAggregations) => { + // TODO: implement + throw Error('Not implemented'); +}; diff --git a/modules/server/src/network/aggregations/tests/aggregation.test.ts b/modules/server/src/network/aggregations/tests/aggregation.test.ts new file mode 100644 index 000000000..22eb005c2 --- /dev/null +++ b/modules/server/src/network/aggregations/tests/aggregation.test.ts @@ -0,0 +1,27 @@ +import { resolveAggregation } from '..'; +import { aggregation } from './fixture'; + +describe('Aggregation', () => { + it('should resolve multiple objects of type Aggregation into a single object of type NetworkAggregation', () => { + const result = resolveAggregation([aggregation.inputA, aggregation.inputB]); + + const resultForMaleCount = + result.buckets.find((bucket) => bucket.key === 'Male')?.doc_count || 0; + const resultForFemaleCount = + result.buckets.find((bucket) => bucket.key === 'Female')?.doc_count || 0; + + const expectedMaleCount = + (aggregation.inputA.buckets.find((bucket) => bucket.key === 'Male')?.doc_count || 0) + + (aggregation.inputB.buckets.find((bucket) => bucket.key === 'Male')?.doc_count || 0); + + const expectedFemaleCount = + (aggregation.inputA.buckets.find((bucket) => bucket.key === 'Female')?.doc_count || 0) + + (aggregation.inputB.buckets.find((bucket) => bucket.key === 'Female')?.doc_count || 0); + + expect(result.bucket_count).toEqual( + aggregation.inputA.bucket_count + aggregation.inputB.bucket_count, + ); + expect(resultForMaleCount).toEqual(expectedMaleCount); + expect(resultForFemaleCount).toEqual(expectedFemaleCount); + }); +}); diff --git a/modules/server/src/network/aggregations/tests/fixture.ts b/modules/server/src/network/aggregations/tests/fixture.ts new file mode 100644 index 000000000..26743e70b --- /dev/null +++ b/modules/server/src/network/aggregations/tests/fixture.ts @@ -0,0 +1,54 @@ +import { Aggregations } from '@/network/types'; + +// Aggregation +const inputA: Aggregations = { + bucket_count: 82, + buckets: [ + { + key: 'Male', + doc_count: 70, + }, + { + key: 'Female', + doc_count: 12, + }, + ], +}; + +const inputB: Aggregations = { + bucket_count: 715, + buckets: [ + { + key: 'Male', + doc_count: 15, + }, + { + key: 'Female', + doc_count: 700, + }, + ], +}; + +const inputC: Aggregations = { + bucket_count: 1565, + buckets: [ + { + key: 'Male', + doc_count: 765, + }, + { + key: 'Female', + doc_count: 800, + }, + { + key: 'Unknown', + doc_count: 0, + }, + ], +}; + +export const aggregation = { + inputA, + inputB, + inputC, +}; diff --git a/modules/server/src/network/types.ts b/modules/server/src/network/types.ts index 71eda8454..03c97f591 100644 --- a/modules/server/src/network/types.ts +++ b/modules/server/src/network/types.ts @@ -38,3 +38,20 @@ export type RemoteConnectionData = { errors: string[]; status: ConnectionStatus; }; + +export type Bucket = { + doc_count: number; + key: string; +}; + +export type Aggregations = { + bucket_count: number; + buckets: Bucket[]; +}; + +export type NumericAggregations = {}; + +export type NetworkAggregation = { + bucket_count: number; + buckets: Bucket[]; +};