Skip to content

Commit

Permalink
feat(grouping): add more Grouping & Aggregators code
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Mar 20, 2020
1 parent 9308d4b commit 8c20808
Show file tree
Hide file tree
Showing 32 changed files with 314 additions and 43 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ npm run test:watch

## TODO
#### Code
- [x] Aggregators (4)
- [x] Aggregators (6)
- [ ] Editors
- [ ] Autocomplete
- [x] Checkbox
Expand Down Expand Up @@ -118,12 +118,14 @@ npm run test:watch
- [x] Sort

#### Other Todos
- [x] VScode Chrome Debugger
- [x] Jest Debugger
- [ ] Add Multiple Example Demos with Vanilla implementation
- [ ] Add GitHub Demo website
- [ ] Add Cypress E2E tests
- [ ] Add CI/CD (CircleCI or GitHub Actions)
- [ ] Add Jest Unit tests
- [ ] Build and run on every PR
- [ ] Add Code Coverage (codecov)
- [x] VScode Chrome Debugger
- [x] Jest Debugger
- [ ] Remove any Deprecated code
- [ ] Create a Migration Guide
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('avgAggregator', () => {
aggregator.storeResult(groupTotals);

// assert
expect(undefined).toBe(groupTotals['avg'][fieldName]);
expect(groupTotals['avg'][fieldName]).toBe(undefined);
});

it('should calculate an average when the chosen field from the dataset contains only numbers', () => {
Expand All @@ -39,7 +39,7 @@ describe('avgAggregator', () => {
aggregator.storeResult(groupTotals);

const avg = (55 + 87 + 60 + (-2) + 15) / 5;
expect(avg).toBe(groupTotals.avg[fieldName]);
expect(groupTotals.avg[fieldName]).toBe(avg);
});

it('should calculate an average with only the valid numbers when dataset contains numbers provided as tring and other and invalid char', () => {
Expand All @@ -52,6 +52,6 @@ describe('avgAggregator', () => {
aggregator.storeResult(groupTotals);

const avg = (58 + 14 + 87) / 3;
expect(avg).toBe(groupTotals.avg[fieldName]);
expect(groupTotals.avg[fieldName]).toBe(avg);
});
});
44 changes: 44 additions & 0 deletions packages/common/src/aggregators/__tests__/cloneAggregator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CloneAggregator } from '../cloneAggregator';

describe('CloneAggregator', () => {
let aggregator: CloneAggregator;
let dataset = [];

beforeEach(() => {
dataset = [
{ id: 0, title: 'Product 0', price: 58.5, productGroup: 'Sub-Cat1' },
{ id: 1, title: 'Product 1', price: 14, productGroup: 'Sub-Cat1' },
{ id: 2, title: 'Product 2', price: 2, productGroup: 'Sub-Cat2' },
{ id: 3, title: 'Product 3', price: 87, productGroup: 'Sub-Cat1' },
{ id: 4, title: 'Product 4', price: null, productGroup: 'Sub-Cat2' },
];
});

it('should return empty string when the field provided does not exist', () => {
// arrange
const fieldName = 'invalid';
const groupTotals = {};
aggregator = new CloneAggregator(fieldName);
aggregator.init();

// act
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

// assert
expect(groupTotals['clone'][fieldName]).toBe('');
});

it('should return last text analyzed by the aggregator when the chosen field is the product group', () => {
const fieldName = 'productGroup';
const lastGroupName = 'Sub-Cat2';
const groupTotals = { clone: {} };
aggregator = new CloneAggregator(fieldName);
aggregator.init();

dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(groupTotals.clone[fieldName]).toBe(lastGroupName);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DistinctAggregator } from '../distinctAggregator';

describe('disctinctAggregator', () => {
let aggregator: DistinctAggregator;
let dataset = [];

beforeEach(() => {
dataset = [
{ id: 0, title: 'Task 0', duration: '58', percentComplete: 55 },
{ id: 1, title: 'Task 1', duration: '14', percentComplete: 87 },
{ id: 2, title: 'Task 2', duration: '', percentComplete: 60 },
{ id: 3, title: 'Task 3', duration: '58', percentComplete: 87 },
{ id: 4, title: 'Task 4', duration: null, percentComplete: 55 },
{ id: 4, title: 'Task 5', duration: 32, percentComplete: 52 },
{ id: 4, title: 'Task 6', duration: 58, percentComplete: 52 },
];
});

it('should return empty array when the field provided does not exist', () => {
// arrange
const fieldName = 'invalid';
const groupTotals = {};
aggregator = new DistinctAggregator(fieldName);
aggregator.init();

// act
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

// assert
expect(groupTotals['distinct'][fieldName]).toEqual([]);
});

it('should return the distinct number values when provided field property values are all numbers', () => {
const fieldName = 'percentComplete';
const groupTotals = { distinct: {} };
aggregator = new DistinctAggregator(fieldName);
aggregator.init();

dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(groupTotals.distinct[fieldName]).toEqual([55, 87, 60, 52]);
});

it('should return the distinct mixed values when provided field property values are all mixed types', () => {
const fieldName = 'duration';
const groupTotals = { distinct: {} };
aggregator = new DistinctAggregator(fieldName);
aggregator.init();

dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(groupTotals.distinct[fieldName]).toEqual(['58', '14', '', null, 32, 58]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('maxAggregator', () => {
aggregator.storeResult(groupTotals);

// assert
expect(null).toBe(groupTotals['max'][fieldName]);
expect(groupTotals['max'][fieldName]).toBe(null);
});

it('should return the maximum value when the chosen field from the dataset contains only numbers', () => {
Expand All @@ -38,7 +38,7 @@ describe('maxAggregator', () => {
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(87).toBe(groupTotals.max[fieldName]);
expect(groupTotals.max[fieldName]).toBe(87);
});

it('should return the maximum valid number when dataset contains numbers provided as tring and other and invalid char', () => {
Expand All @@ -50,6 +50,6 @@ describe('maxAggregator', () => {
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(897).toBe(groupTotals.max[fieldName]);
expect(groupTotals.max[fieldName]).toBe(897);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('minAggregator', () => {
aggregator.storeResult(groupTotals);

// assert
expect(null).toBe(groupTotals['min'][fieldName]);
expect(groupTotals['min'][fieldName]).toBe(null);
});

it('should return the minimum value when the chosen field from the dataset contains only numbers', () => {
Expand All @@ -38,7 +38,7 @@ describe('minAggregator', () => {
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(-2).toBe(groupTotals.min[fieldName]);
expect(groupTotals.min[fieldName]).toBe(-2);
});

it('should return the minimum valid number when dataset contains numbers provided as tring and other and invalid char', () => {
Expand All @@ -50,6 +50,6 @@ describe('minAggregator', () => {
dataset.forEach((row) => aggregator.accumulate(row));
aggregator.storeResult(groupTotals);

expect(14).toBe(groupTotals.min[fieldName]);
expect(groupTotals.min[fieldName]).toBe(14);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('sumAggregator', () => {
aggregator.storeResult(groupTotals);

// assert
expect(0).toBe(groupTotals['sum'][fieldName]);
expect(groupTotals['sum'][fieldName]).toBe(0);
});

it('should return the sum value when the chosen field from the dataset contains only numbers', () => {
Expand All @@ -39,7 +39,7 @@ describe('sumAggregator', () => {
aggregator.storeResult(groupTotals);

const avg = (55 + 87 + 60 + (-2) + 15);
expect(avg).toBe(groupTotals.sum[fieldName]);
expect(groupTotals.sum[fieldName]).toBe(avg);
});

it('should return the sum valid number when dataset contains numbers provided as tring and other and invalid char', () => {
Expand All @@ -52,6 +52,6 @@ describe('sumAggregator', () => {
aggregator.storeResult(groupTotals);

const avg = 58 + 14 + 87;
expect(avg).toBe(groupTotals.sum[fieldName]);
expect(groupTotals.sum[fieldName]).toBe(avg);
});
});
2 changes: 1 addition & 1 deletion packages/common/src/aggregators/avgAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class AvgAggregator implements Aggregator {
accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : null;
this._count++;
if (val != null && val !== '' && !isNaN(val)) {
if (val !== null && val !== '' && !isNaN(val)) {
this._nonNullCount++;
this._sum += parseFloat(val);
}
Expand Down
28 changes: 28 additions & 0 deletions packages/common/src/aggregators/cloneAggregator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Aggregator } from './../interfaces/aggregator.interface';

export class CloneAggregator implements Aggregator {
private _field: number | string;
private _data: any;

constructor(field: number | string) {
this._field = field;
}

init(): void {
this._data = '';
}

accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : null;
if (val !== null && val !== '') {
this._data = val;
}
}

storeResult(groupTotals: any) {
if (!groupTotals || groupTotals.clone === undefined) {
groupTotals.clone = {};
}
groupTotals.clone[this._field] = this._data;
}
}
28 changes: 28 additions & 0 deletions packages/common/src/aggregators/distinctAggregator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Aggregator } from './../interfaces/aggregator.interface';

export class DistinctAggregator implements Aggregator {
private _field: number | string;
private _distinctValues: any[];

constructor(field: number | string) {
this._field = field;
}

init(): void {
this._distinctValues = [];
}

accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : undefined;
if (this._distinctValues.indexOf(val) === -1 && val !== undefined) {
this._distinctValues.push(val);
}
}

storeResult(groupTotals: any) {
if (!groupTotals || groupTotals.avg === undefined) {
groupTotals.distinct = {};
}
groupTotals.distinct[this._field] = this._distinctValues;
}
}
15 changes: 15 additions & 0 deletions packages/common/src/aggregators/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { AvgAggregator } from './avgAggregator';
import { CloneAggregator } from './cloneAggregator';
import { DistinctAggregator } from './distinctAggregator';
import { MinAggregator } from './minAggregator';
import { MaxAggregator } from './maxAggregator';
import { SumAggregator } from './sumAggregator';

/** Provides a list of different Aggregators for the Group Formatter */
export const Aggregators = {
/** Average Aggregator which calculate the average of a given group */
Avg: AvgAggregator,

/** Clone Aggregator will simply clone (copy) over the last defined value of a given group */
Clone: CloneAggregator,

/** Distinct Aggregator will return an array of distinct values found inside the given group */
Distinct: DistinctAggregator,

/** Minimum Aggregator which will find the minimum value inside the given group */
Min: MinAggregator,

/** Maximum Aggregator which will find the maximum value inside the given group */
Max: MaxAggregator,

/** Sum Aggregator which calculate the sum of a given group */
Sum: SumAggregator
};
2 changes: 1 addition & 1 deletion packages/common/src/aggregators/maxAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class MaxAggregator implements Aggregator {

accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : null;
if (val != null && val !== '' && !isNaN(val)) {
if (val !== null && val !== '' && !isNaN(val)) {
if (this._max == null || val > this._max) {
this._max = parseFloat(val);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/aggregators/minAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class MinAggregator implements Aggregator {

accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : null;
if (val != null && val !== '' && !isNaN(val)) {
if (val !== null && val !== '' && !isNaN(val)) {
if (this._min == null || val < this._min) {
this._min = parseFloat(val);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/aggregators/sumAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class SumAggregator implements Aggregator {

accumulate(item: any) {
const val = (item && item.hasOwnProperty(this._field)) ? item[this._field] : null;
if (val != null && val !== '' && !isNaN(val)) {
if (val !== null && val !== '' && !isNaN(val)) {
this._sum += parseFloat(val);
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/global-grid-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export const GlobalGridOptions: GridOption = {
hideExportTextDelimitedCommand: true,
hideMenuOnScroll: true,
hideOptionSection: false,
iconCollapseAllGroupsCommand: 'fa fa-compress mdi mdi-arrow-collapse',
iconExpandAllGroupsCommand: 'fa fa-expand mdi mdi-arrow-expand',
iconClearGroupingCommand: 'fa fa-times mdi mdi-close',
iconCopyCellValueCommand: 'fa fa-clone mdi mdi-content-copy',
iconExportCsvCommand: 'fa fa-download mdi mdi-download',
iconExportExcelCommand: 'fa fa-file-excel-o text-success',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const avgTotalsDollarFormatter: GroupTotalsFormatter = (totals: any, colu
const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, '');
const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false);

if (val != null && !isNaN(+val)) {
if (val !== null && !isNaN(+val)) {
const formattedNumber = formatNumber(val, minDecimal, maxDecimal, displayNegativeNumberWithParentheses, '$', '', decimalSeparator, thousandSeparator);
return `${prefix}${formattedNumber}${suffix}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const avgTotalsFormatter: GroupTotalsFormatter = (totals: any, columnDef:
const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, '');
const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false);

if (val != null && !isNaN(+val)) {
if (val !== null && !isNaN(+val)) {
if (val < 0) {
val = Math.abs(val);
if (!displayNegativeNumberWithParentheses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const avgTotalsPercentageFormatter: GroupTotalsFormatter = (totals: any,
const thousandSeparator = getValueFromParamsOrFormatterOptions('thousandSeparator', columnDef, grid, '');
const displayNegativeNumberWithParentheses = getValueFromParamsOrFormatterOptions('displayNegativeNumberWithParentheses', columnDef, grid, false);

if (val != null && !isNaN(+val)) {
if (val !== null && !isNaN(+val)) {
if (val < 0) {
val = Math.abs(val);
if (!displayNegativeNumberWithParentheses) {
Expand Down
Loading

0 comments on commit 8c20808

Please sign in to comment.