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) 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..a5cb35489 --- /dev/null +++ b/packages/react-core/__tests__/operations/groupByDate.test.js @@ -0,0 +1,155 @@ +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 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(params, RESULT); + }); + + test(GroupDateTypes.MONTHS, () => { + params.operation = 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(params, RESULT); + }); + + test(GroupDateTypes.WEEKS, () => { + params.operation = 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(params, RESULT); + }); + + test(GroupDateTypes.DAYS, () => { + params.operation = 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(params, RESULT); + }); + + test(GroupDateTypes.HOURS, () => { + params.operation = 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(params, RESULT); + }); + + test(GroupDateTypes.MINUTES, () => { + params.operation = GroupDateTypes.MINUTES; + + const RESULT = DATES_VALUES.map((dateValue) => ({ name: dateValue, value: 1 })); + + executeGroupByDateFnTests(params, RESULT); + }); + }); + }); + + 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 +function executeGroupByDateFnTests(args, result) { + const sumResult = result.map((item) => ({ + ...item, + value: item.value * REVENUE + })); + + [AggregationTypes.COUNT, AggregationTypes.SUM].forEach((aggregation, idx) => + expect(groupValuesByDateColumn(...Object.values(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..2518d7bac 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,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).getTime(); + 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; 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) {