@provoly/dashboard 0.15.9 → 0.15.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/esm2022/filters/date/date-filter.component.mjs +3 -3
  2. package/esm2022/lib/core/components/modal-status/modal-status.component.mjs +2 -2
  3. package/esm2022/lib/core/model/dataset.interface.mjs +1 -1
  4. package/esm2022/lib/core/model/filter.interface.mjs +1 -1
  5. package/esm2022/lib/core/model/widget-map-manifest.interface.mjs +1 -1
  6. package/esm2022/lib/core/public-api.mjs +2 -2
  7. package/esm2022/lib/core/store/aggregation/frontend-aggregation/aggregation-utils.class.mjs +184 -0
  8. package/esm2022/lib/core/store/aggregation/frontend-aggregation/frontend-aggregation.service.mjs +93 -0
  9. package/esm2022/lib/dashboard/store/dashboard.selectors.mjs +2 -2
  10. package/esm2022/widgets/widget-map/component/widget-map.component.mjs +8 -6
  11. package/fesm2022/provoly-dashboard-filters-date.mjs +2 -2
  12. package/fesm2022/provoly-dashboard-filters-date.mjs.map +1 -1
  13. package/fesm2022/provoly-dashboard-widgets-widget-map.mjs +7 -5
  14. package/fesm2022/provoly-dashboard-widgets-widget-map.mjs.map +1 -1
  15. package/fesm2022/provoly-dashboard.mjs +230 -164
  16. package/fesm2022/provoly-dashboard.mjs.map +1 -1
  17. package/lib/core/model/dataset.interface.d.ts +1 -1
  18. package/lib/core/model/filter.interface.d.ts +1 -1
  19. package/lib/core/model/widget-map-manifest.interface.d.ts +1 -0
  20. package/lib/core/public-api.d.ts +1 -1
  21. package/lib/core/store/aggregation/frontend-aggregation/aggregation-utils.class.d.ts +33 -0
  22. package/lib/core/store/aggregation/frontend-aggregation/frontend-aggregation.service.d.ts +22 -0
  23. package/lib/dashboard/store/dashboard.selectors.d.ts +1 -1
  24. package/package.json +31 -31
  25. package/widgets/widget-map/component/widget-map.component.d.ts +2 -1
  26. package/esm2022/lib/core/store/aggregation/frontend-aggregation.service.mjs +0 -210
  27. package/lib/core/store/aggregation/frontend-aggregation.service.d.ts +0 -33
@@ -2573,7 +2573,7 @@ class PryModalStatusComponent {
2573
2573
  .flat()
2574
2574
  .map((preview) => ({
2575
2575
  ...preview,
2576
- name: preview.name.split(',').join(', ')
2576
+ name: (preview.name ?? '').split(',').join(', ')
2577
2577
  }))));
2578
2578
  this.messageCount$ = this.store.select(DataSourceSelectors.datasetPreviews).pipe(map((previews) => {
2579
2579
  return previews.map((preview) => preview.count).reduce((p, c) => p + c, 0);
@@ -3856,7 +3856,7 @@ const namedQueriesUses = createSelector(globalManifest, (manifest) => manifest.w
3856
3856
  const presentation = createSelector(feature$4, (state) => state?.presentation);
3857
3857
  const isCurrentPresentationModified = createSelector(presentation, globalManifest, (state, global) => !equal(global, state.initial));
3858
3858
  const isCurrentPresentationOwner = createSelector(presentation, (state) => !!state.current && state.current.owner);
3859
- const filters = createSelector(feature$4, (state) => state?.manifests.manifest.filters ?? {});
3859
+ const filters = createSelector(feature$4, (state) => state?.manifests.manifest.filters ?? []);
3860
3860
  const datasourceFilters = createSelector(feature$4, (state) => state?.manifests.manifest.filters
3861
3861
  ? state?.manifests.manifest.filters.reduce((obj, filter) => (filter.attributes.forEach((attribute) => (obj[attribute.datasource] || (obj[attribute.datasource] = [])).push({
3862
3862
  attribute: attribute.id,
@@ -5285,195 +5285,261 @@ class ResultsetUtils {
5285
5285
  }
5286
5286
  }
5287
5287
 
5288
- class PryFrontendAggregationService extends PryAggregationService {
5289
- constructor(store) {
5290
- super();
5291
- this.store = store;
5288
+ class AggregationUtils {
5289
+ static getAxisAttribute(classes, options, axis) {
5290
+ return classes
5291
+ .map((clazz) => clazz.attributes.find((attr) => attr.id === options[axis].attribute))
5292
+ .find((attr) => !!attr)?.name;
5292
5293
  }
5293
- // @ts-ignore
5294
- aggregate(datasource, options) {
5295
- return combineLatest([
5296
- this.store.select(DashboardSelectors.resultSets),
5297
- this.store.select(ClassSelectors.classes)
5298
- ]).pipe(map(([resultSets, classes]) => {
5299
- const abscissa = classes
5300
- .map((clazz) => clazz.attributes.find((attr) => attr.id === options.abscissa.attribute))
5301
- .find((attr) => !!attr)?.name;
5302
- const ordinate = classes
5303
- .map((clazz) => clazz.attributes.find((attr) => attr.id === options.ordinate.attribute))
5304
- .find((attr) => !!attr)?.name;
5305
- if (!abscissa || !ordinate) {
5306
- throw new Error('abscissaorodinateinvalid');
5307
- }
5308
- let data = datasource.map((rsName) => resultSets[rsName ?? '']).filter((rsName) => !!rsName);
5309
- const resultSet = data.reduce((rs1, rs2) => ResultsetUtils.mergeResultSets(rs1, rs2), {
5310
- items: {},
5311
- relations: [],
5312
- merged: data.length
5313
- });
5314
- let items = Object.keys(resultSet.items)
5315
- .map((classId) => resultSet.items[classId])
5316
- .reduce((prev, curr) => [...prev, ...curr], []);
5317
- if (!!options.groupBy) {
5318
- const groupBy = classes
5319
- .map((clazz) => clazz.attributes.find((attr) => attr.id === options.groupBy?.attribute))
5320
- .find((attr) => !!attr)?.name;
5321
- if (!groupBy) {
5322
- throw new Error('groupByAttributeisinvalid');
5323
- }
5324
- const itemsGrouped = this.groupBy(items, groupBy);
5325
- const values = [];
5326
- Object.keys(itemsGrouped).forEach((key) => {
5327
- values.push({
5328
- key,
5329
- groupBy: this.getValueFromOperation(itemsGrouped[key], options.ordinate.operation, abscissa, ordinate, options.abscissa.limit)
5330
- });
5331
- });
5332
- return { operation: options.ordinate.operation, values: values };
5333
- }
5334
- const values = this.getValueFromOperation(items, options.ordinate.operation, abscissa, ordinate, options.abscissa.limit);
5335
- return { operation: options.ordinate.operation, values: values };
5336
- }));
5294
+ static getChartDatasourceResultSet(datasources, resultSets) {
5295
+ return datasources
5296
+ .filter((rsName) => rsName in resultSets)
5297
+ .map((rsName) => resultSets[rsName])
5298
+ .reduce((rs1, rs2) => ResultsetUtils.mergeResultSets(rs1, rs2), {
5299
+ items: {},
5300
+ relations: [],
5301
+ merged: datasources.filter((rsName) => rsName in resultSets).length
5302
+ });
5303
+ }
5304
+ static getAggregatedValue(operation, groupedItems, abscissaAttribute, ordinateAttribute) {
5305
+ return this.getAggregatedValueForOperation(operation, groupedItems[abscissaAttribute].map((item) => this.getValueFromAttribute(item, ordinateAttribute).value));
5337
5306
  }
5338
- getValueFromOperation(items, operation, abscissa, ordinate, limit) {
5339
- let result = [];
5340
- const groupByAbscissa = this.groupBy(items, abscissa);
5341
- groupByAbscissa.map((test) => console.log(groupByAbscissa[test]));
5307
+ static getValueFromAttribute(item, attribute) {
5308
+ let value = ItemUtils.getAttributeValue(item, attribute);
5309
+ return Array.isArray(value) ? value[0] : value;
5310
+ }
5311
+ static getAggregatedValueForOperation(operation, values) {
5342
5312
  switch (operation) {
5343
5313
  case Operation.COUNT:
5344
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5345
- key: key,
5346
- value: groupByAbscissa[key].length
5347
- })));
5348
- break;
5314
+ return values.length;
5349
5315
  case Operation.SUM:
5350
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5351
- key: key,
5352
- value: this.getSum(groupByAbscissa[key], ordinate)
5353
- })));
5354
- break;
5316
+ return values.reduce((acc, val) => acc + val, 0);
5355
5317
  case Operation.DISTINCT:
5356
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5357
- key: key,
5358
- value: this.getDistinct(groupByAbscissa[key], ordinate)
5359
- })));
5360
- break;
5318
+ return [...new Set(values)].length;
5361
5319
  case Operation.AVERAGE:
5362
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5363
- key: key,
5364
- value: this.getAverage(groupByAbscissa[key], ordinate)
5365
- })));
5366
- break;
5320
+ return values.reduce((acc, val) => acc + val, 0) / values.length;
5367
5321
  case Operation.MEDIAN:
5368
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5369
- key: key,
5370
- value: this.getQuartile(groupByAbscissa[key], ordinate, 0.5)
5371
- })));
5372
- break;
5322
+ return this.getQuartile(values, 0.5);
5373
5323
  case Operation.Q1:
5374
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5375
- key: key,
5376
- value: this.getQuartile(groupByAbscissa[key], ordinate, 0.25)
5377
- })));
5378
- break;
5324
+ return this.getQuartile(values, 0.25);
5379
5325
  case Operation.Q3:
5380
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5381
- key: key,
5382
- value: this.getQuartile(groupByAbscissa[key], ordinate, 0.75)
5383
- })));
5384
- break;
5326
+ return this.getQuartile(values, 0.75);
5385
5327
  case Operation.MIN:
5386
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5387
- key: key,
5388
- value: this.getMin(groupByAbscissa[key], ordinate)
5389
- })));
5390
- break;
5328
+ return Math.min(...values);
5329
+ case Operation.MAX:
5330
+ return Math.max(...values);
5391
5331
  default:
5392
- result.push(...Object.keys(groupByAbscissa).map((key) => ({
5393
- key: key,
5394
- value: this.getMax(groupByAbscissa[key], ordinate)
5395
- })));
5396
- break;
5332
+ return -1;
5397
5333
  }
5398
- if (limit) {
5399
- result = this.formatByLimit(result, limit);
5334
+ }
5335
+ static getQuartile(values, q) {
5336
+ if (values.length === 0)
5337
+ return 0;
5338
+ values.sort((a, b) => a - b);
5339
+ let pos = (values.length - 1) * q;
5340
+ if (pos % 1 === 0) {
5341
+ return values[pos];
5400
5342
  }
5401
- return result;
5343
+ pos = Math.floor(pos);
5344
+ return values[pos + 1] !== undefined ? (values[pos] + values[pos + 1]) / 2 : values[pos];
5402
5345
  }
5403
- groupBy(items, attribute) {
5404
- let distincts = [];
5405
- items.forEach((item) => {
5406
- let value = this.getValueFromAttribute(item, attribute);
5407
- if (distincts[value.value]) {
5408
- distincts[value.value].push(item);
5346
+ static formatByLimit(data, limit, operation) {
5347
+ if (!limit.isTimeLimit) {
5348
+ data
5349
+ .sort((a, b) => (limit.order === 'asc' ? (a.value > b.value ? 1 : -1) : a.value < b.value ? 1 : -1))
5350
+ .splice(limit.at);
5351
+ return data;
5352
+ }
5353
+ return this.formatDataByTimeLimit(data, limit, operation);
5354
+ }
5355
+ static formatDataByTimeLimit(data, limit, operation) {
5356
+ let distinctValues = [];
5357
+ data
5358
+ .map((obj) => ({ ...obj, key: this.getKeyValueForTimeInterval(obj.key, limit.interval) }))
5359
+ .forEach((obj) => {
5360
+ const isNotDistinct = distinctValues.find((distinct) => obj.key === distinct.key);
5361
+ if (!!isNotDistinct) {
5362
+ isNotDistinct.values.push(obj.value);
5409
5363
  }
5410
5364
  else {
5411
- distincts[value.value] = [item];
5365
+ distinctValues.push({ key: obj.key, values: [] });
5412
5366
  }
5413
5367
  });
5414
- return distincts;
5415
- }
5416
- getValueFromAttribute(item, attribute) {
5417
- let value = ItemUtils.getAttributeValue(item, attribute);
5418
- return Array.isArray(value) ? value[0] : value;
5419
- }
5420
- getSum(items, attribute) {
5421
- let sum = 0;
5422
- items.forEach((item) => {
5423
- sum += this.getValueFromAttribute(item, attribute).value;
5424
- });
5425
- return sum;
5368
+ return distinctValues.map((obj) => ({
5369
+ ...obj,
5370
+ value: AggregationUtils.getAggregatedValueForOperation(operation, obj.values)
5371
+ }));
5426
5372
  }
5427
- getMin(items, attribute) {
5428
- const result = items.reduce((prev, curr) => {
5429
- const prevValue = this.getValueFromAttribute(prev, attribute).value;
5430
- const currValue = this.getValueFromAttribute(curr, attribute).value;
5431
- return prevValue < currValue ? prev : curr;
5432
- });
5433
- return this.getValueFromAttribute(result, attribute).value;
5373
+ static getKeyValueForTimeInterval(key, interval) {
5374
+ const date = new Date(key);
5375
+ switch (interval) {
5376
+ case 'second':
5377
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()).toISOString();
5378
+ case 'minute':
5379
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()).toISOString();
5380
+ case 'hour':
5381
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()).toISOString();
5382
+ case 'day':
5383
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString();
5384
+ case 'week':
5385
+ const daysToSubtract = (7 + date.getUTCDay()) % 7;
5386
+ date.setUTCDate(date.getUTCDate() - daysToSubtract);
5387
+ return date.toISOString();
5388
+ case 'quarter':
5389
+ const quarterStartMonths = [0, 3, 6, 9]; // January, April, July, October
5390
+ const quarter = Math.floor(date.getUTCMonth() / 3);
5391
+ const year = date.getUTCFullYear();
5392
+ const startOfQuarter = new Date(Date.UTC(year, quarterStartMonths[quarter], 1, 0, 0, 0, 0));
5393
+ return startOfQuarter.toISOString();
5394
+ case 'year':
5395
+ return new Date(new Date(key).getFullYear(), 0, 1).toISOString();
5396
+ default:
5397
+ return date.toISOString();
5398
+ }
5434
5399
  }
5435
- getMax(items, attribute) {
5436
- const result = items.reduce((prev, curr) => {
5437
- const prevValue = this.getValueFromAttribute(prev, attribute).value;
5438
- const currValue = this.getValueFromAttribute(curr, attribute).value;
5439
- return prevValue > currValue ? prev : curr;
5440
- });
5441
- return this.getValueFromAttribute(result, attribute).value;
5400
+ static filterItems(items, filters) {
5401
+ let filtersByAttribute = filters
5402
+ .map((filter) => filter.attributes.map((attribute) => ({
5403
+ ...filter,
5404
+ attribute: attribute.id,
5405
+ attributes: undefined
5406
+ })))
5407
+ .flat();
5408
+ return filters.length > 0
5409
+ ? [
5410
+ ...new Set(filtersByAttribute
5411
+ .map((filter) => items.filter((item) => this.doesItemValuePassFilter(item, filter.attribute, filter.operator, filter.value)))
5412
+ .flat())
5413
+ ]
5414
+ : items;
5415
+ }
5416
+ static doesItemValuePassFilter(item, attribute, operator, filterValue) {
5417
+ let value = AggregationUtils.getValueFromAttribute(item, attribute)?.value;
5418
+ let date1, date2;
5419
+ console.log(operator);
5420
+ switch (operator) {
5421
+ case 'EQUALS':
5422
+ return value === filterValue;
5423
+ case 'NOT_EQUALS':
5424
+ return value !== filterValue;
5425
+ case 'CONTAINS':
5426
+ return value.includes(filterValue);
5427
+ case 'START_WITH':
5428
+ return value.startsWith(filterValue);
5429
+ case 'END_WITH':
5430
+ return value.endsWith(filterValue);
5431
+ case 'GREATER_THAN':
5432
+ if (typeof filterValue === 'number') {
5433
+ return value > filterValue;
5434
+ }
5435
+ else if (typeof filterValue === 'string') {
5436
+ return new Date(value) > new Date(filterValue);
5437
+ }
5438
+ else {
5439
+ return false;
5440
+ }
5441
+ case 'LOWER_THAN':
5442
+ if (typeof filterValue === 'number') {
5443
+ return value < filterValue;
5444
+ }
5445
+ else if (typeof filterValue === 'string') {
5446
+ return new Date(value) < new Date(filterValue);
5447
+ }
5448
+ else {
5449
+ return false;
5450
+ }
5451
+ case 'INSIDE':
5452
+ [date1, date2] = [
5453
+ new Date(filterValue.split(',')[0]),
5454
+ new Date(filterValue.split(',')[1])
5455
+ ];
5456
+ return new Date(value) > new Date(date1) && new Date(value) < new Date(date2);
5457
+ case 'OUTSIDE':
5458
+ [date1, date2] = [
5459
+ new Date(filterValue.split(',')[0]),
5460
+ new Date(filterValue.split(',')[1])
5461
+ ];
5462
+ return new Date(value) < new Date(date1) && new Date(value) > new Date(date2);
5463
+ default:
5464
+ return true;
5465
+ }
5442
5466
  }
5443
- getAverage(items, attribute) {
5444
- const allValues = items.map((item) => this.getValueFromAttribute(item, attribute).value);
5445
- return allValues.reduce((acc, val) => acc + val, 0) / allValues.length;
5467
+ }
5468
+
5469
+ class PryFrontendAggregationService extends PryAggregationService {
5470
+ constructor(store) {
5471
+ super();
5472
+ this.store = store;
5446
5473
  }
5447
- getDistinct(items, attribute) {
5448
- return [...new Set(items.map((item) => this.getValueFromAttribute(item, attribute).value))].length;
5474
+ aggregate(datasources, options) {
5475
+ return combineLatest([
5476
+ this.store.select(DashboardSelectors.resultSets),
5477
+ this.store.select(ClassSelectors.classes),
5478
+ this.store.select(DashboardSelectors.filters)
5479
+ ]).pipe(map(([resultSets, classes, filters]) => {
5480
+ const resultSet = AggregationUtils.getChartDatasourceResultSet(datasources, resultSets);
5481
+ let items = Object.keys(resultSet.items)
5482
+ .map((classId) => resultSet.items[classId])
5483
+ .reduce((prev, curr) => [...prev, ...curr], []);
5484
+ items = AggregationUtils.filterItems(items, filters);
5485
+ return this.getAggregationResult(options, classes, items);
5486
+ }));
5449
5487
  }
5450
- getQuartile(items, attribute, q) {
5451
- const allValues = items.map((item) => this.getValueFromAttribute(item, attribute).value);
5452
- const allValuesSorted = allValues.sort((a, b) => a - b);
5453
- let pos = (allValuesSorted.length - 1) * q;
5454
- if (pos % 1 === 0) {
5455
- return allValuesSorted[pos];
5488
+ getAggregationResult(options, classes, items) {
5489
+ const [abscissa, ordinate] = [
5490
+ AggregationUtils.getAxisAttribute(classes, options, 'abscissa') ?? '',
5491
+ AggregationUtils.getAxisAttribute(classes, options, 'ordinate') ?? ''
5492
+ ];
5493
+ const aggregationResult = {
5494
+ operation: options.ordinate.operation,
5495
+ values: []
5496
+ };
5497
+ if (!options.groupBy) {
5498
+ if (!!options.ordinate.operation)
5499
+ console.warn('no ordinate operation selected');
5500
+ aggregationResult.values = this.getChartDataBasedOnOperation(items, options.ordinate.operation, abscissa, ordinate, options.abscissa.limit);
5501
+ console.log(aggregationResult.values);
5456
5502
  }
5457
- pos = Math.floor(pos);
5458
- if (allValuesSorted[pos + 1] !== undefined) {
5459
- return (allValuesSorted[pos] + allValuesSorted[pos + 1]) / 2;
5503
+ else {
5504
+ const groupByAttribute = classes
5505
+ .map((clazz) => clazz.attributes.find((attr) => attr.id === options.groupBy?.attribute))
5506
+ .find((attr) => !!attr)?.name ?? '';
5507
+ const itemsGrouped = this.getItemsGroupedByAttributeValue(items, groupByAttribute);
5508
+ aggregationResult.values = Object.keys(itemsGrouped).map((key) => ({
5509
+ key,
5510
+ groupBy: this.getChartDataBasedOnOperation(itemsGrouped[key], options.ordinate.operation, abscissa, ordinate, options.abscissa.limit)
5511
+ }));
5460
5512
  }
5461
- return allValuesSorted[pos];
5462
- }
5463
- getPercentile(items, attribute, q) {
5464
- const allValues = items.map((item) => this.getValueFromAttribute(item, attribute).value);
5465
- const allValuesSorted = allValues.sort((a, b) => a - b);
5466
- return ((100 * allValuesSorted.reduce((acc, v) => acc + (v < q ? 1 : 0) + (v === q ? 0.5 : 0), 0)) /
5467
- allValuesSorted.length);
5513
+ return aggregationResult;
5468
5514
  }
5469
- formatByLimit(data, limit) {
5470
- if (!limit.isTimeLimit) {
5471
- data
5472
- .sort((a, b) => (limit.order === 'asc' ? (a.value > b.value ? 1 : -1) : a.value < b.value ? 1 : -1))
5473
- .splice(limit.at);
5474
- return data;
5475
- }
5476
- return data;
5515
+ getItemsGroupedByAttributeValue(items, attribute) {
5516
+ if (attribute === '')
5517
+ console.warn('groupBy attribute not found');
5518
+ let distinctValues = {};
5519
+ items.forEach((item) => {
5520
+ let value = AggregationUtils.getValueFromAttribute(item, attribute)?.value;
5521
+ if (!!value)
5522
+ distinctValues[value] = [...(distinctValues[value] ?? []), item];
5523
+ });
5524
+ return distinctValues;
5525
+ }
5526
+ // get { key, value } object list that will be our data to show in the chart
5527
+ getChartDataBasedOnOperation(items, operation, abscissa, ordinate, limit) {
5528
+ if (abscissa === '' || ordinate === '')
5529
+ console.warn('abscissa or ordinate attribute not found');
5530
+ const groupByAbscissa = this.getItemsGroupedByAttributeValue(items, abscissa);
5531
+ let result = [
5532
+ ...Object.keys(groupByAbscissa).map((abscissaAttribute) => {
5533
+ console.log(AggregationUtils.getAggregatedValue(operation, groupByAbscissa, abscissaAttribute, ordinate ?? ''));
5534
+ return {
5535
+ key: +abscissaAttribute || abscissaAttribute,
5536
+ value: AggregationUtils.getAggregatedValue(operation, groupByAbscissa, abscissaAttribute, ordinate ?? '')
5537
+ };
5538
+ })
5539
+ ];
5540
+ if (limit)
5541
+ result = AggregationUtils.formatByLimit(result, limit, operation);
5542
+ return result;
5477
5543
  }
5478
5544
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.6", ngImport: i0, type: PryFrontendAggregationService, deps: [{ token: i1.Store }], target: i0.ɵɵFactoryTarget.Injectable }); }
5479
5545
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.6", ngImport: i0, type: PryFrontendAggregationService, providedIn: 'root' }); }