From f4011ce7b22290776fb8b4076e3de378d225e1c6 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Mon, 21 Feb 2022 12:33:19 +0100 Subject: [PATCH 1/5] Add groupByDate tests --- .../__tests__/operations/groupByDate.test.js | 136 ++++++++++++++++++ .../react-core/src/operations/groupByDate.js | 30 ++-- packages/react-core/src/utils/dateUtils.js | 11 +- .../TimeSeriesWidgetUI/TimeSeriesWidgetUI.js | 2 +- 4 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 packages/react-core/__tests__/operations/groupByDate.test.js diff --git a/packages/react-core/__tests__/operations/groupByDate.test.js b/packages/react-core/__tests__/operations/groupByDate.test.js new file mode 100644 index 000000000..7306cbefb --- /dev/null +++ b/packages/react-core/__tests__/operations/groupByDate.test.js @@ -0,0 +1,136 @@ +import { AggregationTypes } from '../../src/operations/aggregation/AggregationTypes'; +import { GroupDateTypes } from '../../src/operations/GroupDateTypes'; +import { groupValuesByDateColumn } from '../../src/operations/groupByDate'; + +const REVENUE = 50; + +const DATES_VALUES = [ + Date.UTC(1970, 0, 1, 0, 0), + Date.UTC(1970, 0, 1, 0, 30), + Date.UTC(1970, 1, 1, 0, 0), + Date.UTC(1970, 1, 1, 0, 30), + Date.UTC(1971, 0, 1, 1, 0) +]; + +const DATE_COLUMN = 'test_date'; +const OPERATION_COLUMN = 'test_revenue'; + +const FEATURES = DATES_VALUES.map((value) => ({ + [DATE_COLUMN]: value, + [OPERATION_COLUMN]: REVENUE +})); + +describe('groupValuesByDateColumn', () => { + test('should return null due to empty data array', () => { + expect(groupValuesByDateColumn([])).toEqual(null); + }); + + describe('valid features', () => { + test('should return null due to invalid grouping operation', () => { + expect( + groupValuesByDateColumn( + FEATURES, + OPERATION_COLUMN, + DATE_COLUMN, + '__fake_group_type__', + AggregationTypes.COUNT + ) + ).toEqual(null); + }); + + test('should return an empty array due to invalid operation', () => { + expect( + groupValuesByDateColumn( + FEATURES, + OPERATION_COLUMN, + DATE_COLUMN, + GroupDateTypes.DAYS, + '__fake_operation__' + ) + ).toEqual([]); + }); + + describe('grouping operation tests', () => { + const ARGS = [FEATURES, OPERATION_COLUMN, DATE_COLUMN, GroupDateTypes.YEARS]; + + test(GroupDateTypes.YEARS, () => { + const RESULT = [ + { name: Date.UTC(1970, 0, 1), value: 4 }, + { name: Date.UTC(1971, 0, 1), value: 1 } + ]; + + executeGroupByDateFnTests(ARGS, RESULT); + }); + + test(GroupDateTypes.MONTHS, () => { + ARGS[3] = GroupDateTypes.MONTHS; + + const RESULT = [ + { name: Date.UTC(1970, 0, 1), value: 2 }, + { name: Date.UTC(1970, 1, 1), value: 2 }, + { name: Date.UTC(1971, 0, 1), value: 1 } + ]; + + executeGroupByDateFnTests(ARGS, RESULT); + }); + + test(GroupDateTypes.WEEKS, () => { + ARGS[3] = GroupDateTypes.WEEKS; + + const RESULT = [ + { name: Date.UTC(1969, 11, 29), value: 2 }, + { name: Date.UTC(1970, 0, 26), value: 2 }, + { name: Date.UTC(1970, 11, 28), value: 1 } + ]; + + executeGroupByDateFnTests(ARGS, RESULT); + }); + + test(GroupDateTypes.DAYS, () => { + ARGS[3] = GroupDateTypes.DAYS; + + const RESULT = [ + { name: Date.UTC(1970, 0, 1), value: 2 }, + { name: Date.UTC(1970, 1, 1), value: 2 }, + { name: Date.UTC(1971, 0, 1), value: 1 } + ]; + + executeGroupByDateFnTests(ARGS, RESULT); + }); + + test(GroupDateTypes.HOURS, () => { + ARGS[3] = GroupDateTypes.HOURS; + + const RESULT = [ + { name: Date.UTC(1970, 0, 1, 0, 0), value: 2 }, + { name: Date.UTC(1970, 1, 1, 0, 0), value: 2 }, + { name: Date.UTC(1971, 0, 1, 1, 0), value: 1 } + ]; + + executeGroupByDateFnTests(ARGS, RESULT); + }); + + test(GroupDateTypes.MINUTES, () => { + ARGS[3] = GroupDateTypes.MINUTES; + + const RESULT = DATES_VALUES.map((dateValue) => ({ name: dateValue, value: 1 })); + + executeGroupByDateFnTests(ARGS, RESULT); + }); + }); + }); +}); + +// Aux +function executeGroupByDateFnTests(args, result) { + const sumResult = result.map((item) => ({ + ...item, + value: item.value * REVENUE + })); + + [AggregationTypes.COUNT, AggregationTypes.SUM].forEach((aggregation, idx) => + expect(groupValuesByDateColumn(...args, aggregation)).toEqual( + idx ? sumResult : result + ) + ); +} diff --git a/packages/react-core/src/operations/groupByDate.js b/packages/react-core/src/operations/groupByDate.js index 45059ff4d..649fa663b 100644 --- a/packages/react-core/src/operations/groupByDate.js +++ b/packages/react-core/src/operations/groupByDate.js @@ -4,25 +4,25 @@ import { GroupDateTypes } from './GroupDateTypes'; const GROUP_KEY_FN_MAPPING = { // @ts-ignore - [GroupDateTypes.YEARS]: (date) => new Date(Date.UTC(date.getFullYear())), - [GroupDateTypes.MONTHS]: (date) => - new Date(Date.UTC(date.getFullYear(), date.getMonth())), + [GroupDateTypes.YEARS]: (date) => Date.UTC(date.getUTCFullYear()), + [GroupDateTypes.MONTHS]: (date) => Date.UTC(date.getUTCFullYear(), date.getUTCMonth()), [GroupDateTypes.WEEKS]: (date) => getMonday(date), [GroupDateTypes.DAYS]: (date) => - new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())), + Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()), [GroupDateTypes.HOURS]: (date) => - new Date( - Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()) + Date.UTC( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours() ), [GroupDateTypes.MINUTES]: (date) => - new Date( - Date.UTC( - date.getFullYear(), - date.getMonth(), - date.getDate(), - date.getHours(), - date.getMinutes() - ) + Date.UTC( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes() ) }; @@ -46,7 +46,7 @@ export function groupValuesByDateColumn( const groups = data.reduce((acc, item) => { const value = item[keysColumn]; const formattedValue = new Date(value); - const group = groupKeyFn(formattedValue).getTime(); + const group = groupKeyFn(formattedValue); let groupedValues = acc.get(group); if (!groupedValues) { diff --git a/packages/react-core/src/utils/dateUtils.js b/packages/react-core/src/utils/dateUtils.js index af87ec5fd..a415f919e 100644 --- a/packages/react-core/src/utils/dateUtils.js +++ b/packages/react-core/src/utils/dateUtils.js @@ -1,8 +1,7 @@ export function getMonday(date) { - const day = date.getDay(); - const diff = date.getDate() - day + (day ? 1 : -6); // adjust when day is sunday - date.setDate(diff); - // Ignore hours - date.setHours(0, 0, 0, 0); - return date; + const dateCp = new Date(date); + const day = dateCp.getDay(); + const diff = dateCp.getDate() - day + (day ? 1 : -6); // adjust when day is sunday + dateCp.setDate(diff); + return Date.UTC(dateCp.getUTCFullYear(), dateCp.getUTCMonth(), dateCp.getUTCDate()); } diff --git a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/TimeSeriesWidgetUI.js b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/TimeSeriesWidgetUI.js index 158267012..077dacfe1 100644 --- a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/TimeSeriesWidgetUI.js +++ b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/TimeSeriesWidgetUI.js @@ -421,7 +421,7 @@ function daysCurrentDateRange(date) { } function weeksCurrentDateRange(date) { - return `Week of ${getMonday(date).toLocaleDateString()}`; + return `Week of ${new Date(getMonday(date)).toLocaleDateString()}`; } function yearCurrentDateRange(date) { From 590f19ddb4d8b8587a147239ba1548c5600ab66a Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Mon, 21 Feb 2022 12:34:17 +0100 Subject: [PATCH 2/5] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ec8c5d5..1b0b2ae1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Not released +- Add groupByDate tests [#346](https://github.com/CartoDB/carto-react/pull/346) + ## 1.2 ### 1.2.1-beta.7 (2022-02-18) From 01ad500a223e06f75e07e231df259fdb1790121c Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Mon, 21 Feb 2022 12:44:57 +0100 Subject: [PATCH 3/5] Add tests for invalid values --- .../__tests__/operations/groupByDate.test.js | 13 +++++++++ .../react-core/src/operations/groupByDate.js | 27 ++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/react-core/__tests__/operations/groupByDate.test.js b/packages/react-core/__tests__/operations/groupByDate.test.js index 7306cbefb..58af2de8b 100644 --- a/packages/react-core/__tests__/operations/groupByDate.test.js +++ b/packages/react-core/__tests__/operations/groupByDate.test.js @@ -119,6 +119,19 @@ describe('groupValuesByDateColumn', () => { }); }); }); + + describe('invalid features', () => { + test('invalid date columns are not taken into consideration', () => { + const h = groupValuesByDateColumn( + [{ [DATE_COLUMN]: '__non_number__', [OPERATION_COLUMN]: 100 }], + OPERATION_COLUMN, + DATE_COLUMN, + GroupDateTypes.DAYS, + AggregationTypes.COUNT + ); + expect(h).toEqual([]); + }); + }); }); // Aux diff --git a/packages/react-core/src/operations/groupByDate.js b/packages/react-core/src/operations/groupByDate.js index 649fa663b..92fd68d43 100644 --- a/packages/react-core/src/operations/groupByDate.js +++ b/packages/react-core/src/operations/groupByDate.js @@ -46,19 +46,21 @@ export function groupValuesByDateColumn( const groups = data.reduce((acc, item) => { const value = item[keysColumn]; const formattedValue = new Date(value); - const group = groupKeyFn(formattedValue); + const groupKey = groupKeyFn(formattedValue); - let groupedValues = acc.get(group); - if (!groupedValues) { - groupedValues = []; - acc.set(group, groupedValues); - } + if (!isNaN(groupKey)) { + let groupedValues = acc.get(groupKey); + if (!groupedValues) { + groupedValues = []; + acc.set(groupKey, groupedValues); + } - const isValid = item[valuesColumn] !== null && item[valuesColumn] !== undefined; + const isValid = item[valuesColumn] !== null && item[valuesColumn] !== undefined; - if (isValid) { - groupedValues.push(item[valuesColumn]); - acc.set(group, groupedValues); + if (isValid) { + groupedValues.push(item[valuesColumn]); + acc.set(groupKey, groupedValues); + } } return acc; @@ -77,3 +79,8 @@ export function groupValuesByDateColumn( return []; } + +// Aux +function isValidDate(d) { + return typeof d === 'number' || (d instanceof Date && !isNaN(d)); +} From b487b2b280c277ffd37737b51f667ae3c5a5ddce Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Mon, 21 Feb 2022 12:49:50 +0100 Subject: [PATCH 4/5] Remove unused code --- packages/react-core/src/operations/groupByDate.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/react-core/src/operations/groupByDate.js b/packages/react-core/src/operations/groupByDate.js index 92fd68d43..2518d7bac 100644 --- a/packages/react-core/src/operations/groupByDate.js +++ b/packages/react-core/src/operations/groupByDate.js @@ -79,8 +79,3 @@ export function groupValuesByDateColumn( return []; } - -// Aux -function isValidDate(d) { - return typeof d === 'number' || (d instanceof Date && !isNaN(d)); -} From 923b2d15694c357d32d86b6602172d955a0aa55c Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Mon, 21 Feb 2022 17:18:12 +0100 Subject: [PATCH 5/5] Improve groupByDate code readibility --- .../__tests__/operations/groupByDate.test.js | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/react-core/__tests__/operations/groupByDate.test.js b/packages/react-core/__tests__/operations/groupByDate.test.js index 58af2de8b..a5cb35489 100644 --- a/packages/react-core/__tests__/operations/groupByDate.test.js +++ b/packages/react-core/__tests__/operations/groupByDate.test.js @@ -51,19 +51,25 @@ describe('groupValuesByDateColumn', () => { }); describe('grouping operation tests', () => { - const ARGS = [FEATURES, OPERATION_COLUMN, DATE_COLUMN, GroupDateTypes.YEARS]; + const params = { + features: FEATURES, + valuesColumn: OPERATION_COLUMN, + keysColumn: DATE_COLUMN + }; test(GroupDateTypes.YEARS, () => { + params.operation = GroupDateTypes.YEARS; + const RESULT = [ { name: Date.UTC(1970, 0, 1), value: 4 }, { name: Date.UTC(1971, 0, 1), value: 1 } ]; - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); test(GroupDateTypes.MONTHS, () => { - ARGS[3] = GroupDateTypes.MONTHS; + params.operation = GroupDateTypes.MONTHS; const RESULT = [ { name: Date.UTC(1970, 0, 1), value: 2 }, @@ -71,11 +77,11 @@ describe('groupValuesByDateColumn', () => { { name: Date.UTC(1971, 0, 1), value: 1 } ]; - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); test(GroupDateTypes.WEEKS, () => { - ARGS[3] = GroupDateTypes.WEEKS; + params.operation = GroupDateTypes.WEEKS; const RESULT = [ { name: Date.UTC(1969, 11, 29), value: 2 }, @@ -83,11 +89,11 @@ describe('groupValuesByDateColumn', () => { { name: Date.UTC(1970, 11, 28), value: 1 } ]; - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); test(GroupDateTypes.DAYS, () => { - ARGS[3] = GroupDateTypes.DAYS; + params.operation = GroupDateTypes.DAYS; const RESULT = [ { name: Date.UTC(1970, 0, 1), value: 2 }, @@ -95,11 +101,11 @@ describe('groupValuesByDateColumn', () => { { name: Date.UTC(1971, 0, 1), value: 1 } ]; - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); test(GroupDateTypes.HOURS, () => { - ARGS[3] = GroupDateTypes.HOURS; + params.operation = GroupDateTypes.HOURS; const RESULT = [ { name: Date.UTC(1970, 0, 1, 0, 0), value: 2 }, @@ -107,15 +113,15 @@ describe('groupValuesByDateColumn', () => { { name: Date.UTC(1971, 0, 1, 1, 0), value: 1 } ]; - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); test(GroupDateTypes.MINUTES, () => { - ARGS[3] = GroupDateTypes.MINUTES; + params.operation = GroupDateTypes.MINUTES; const RESULT = DATES_VALUES.map((dateValue) => ({ name: dateValue, value: 1 })); - executeGroupByDateFnTests(ARGS, RESULT); + executeGroupByDateFnTests(params, RESULT); }); }); }); @@ -142,7 +148,7 @@ function executeGroupByDateFnTests(args, result) { })); [AggregationTypes.COUNT, AggregationTypes.SUM].forEach((aggregation, idx) => - expect(groupValuesByDateColumn(...args, aggregation)).toEqual( + expect(groupValuesByDateColumn(...Object.values(args), aggregation)).toEqual( idx ? sumResult : result ) );