diff --git a/projects/packages/premium-analytics/changelog/uni-754-to-date-presets b/projects/packages/premium-analytics/changelog/uni-754-to-date-presets new file mode 100644 index 000000000000..4aebad88e7f3 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/uni-754-to-date-presets @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Date picker: add the Month to date and Year to date periods. diff --git a/projects/packages/premium-analytics/packages/data/src/utils/__tests__/compute-date-range-from-preset.test.ts b/projects/packages/premium-analytics/packages/data/src/utils/__tests__/compute-date-range-from-preset.test.ts index 736b9fe462a2..39ce9b15bfcb 100644 --- a/projects/packages/premium-analytics/packages/data/src/utils/__tests__/compute-date-range-from-preset.test.ts +++ b/projects/packages/premium-analytics/packages/data/src/utils/__tests__/compute-date-range-from-preset.test.ts @@ -110,6 +110,14 @@ describe( 'computeDateRangeFromPreset', () => { expect( range!.to ).toBe( toSiteISO( TODAY_END ) ); } ); + it( 'returns the running calendar month through the end of today for "month-to-date"', () => { + const range = computeDateRangeFromPreset( 'month-to-date' ); + + expect( range ).toBeDefined(); + expect( range!.from ).toBe( toSiteISO( startOfMonth( TODAY_START, { in: UTC } ) ) ); + expect( range!.to ).toBe( toSiteISO( TODAY_END ) ); + } ); + it( 'returns last calendar month for "last-month"', () => { const range = computeDateRangeFromPreset( 'last-month' ); @@ -118,6 +126,14 @@ describe( 'computeDateRangeFromPreset', () => { expect( range!.to ).toBe( toSiteISO( endOfMonth( LAST_MONTH, { in: UTC } ) ) ); } ); + it( 'returns the running calendar year through the end of today for "year-to-date"', () => { + const range = computeDateRangeFromPreset( 'year-to-date' ); + + expect( range ).toBeDefined(); + expect( range!.from ).toBe( toSiteISO( startOfYear( TODAY_START, { in: UTC } ) ) ); + expect( range!.to ).toBe( toSiteISO( TODAY_END ) ); + } ); + it( 'returns twelve whole calendar months ending today for "last-12-months"', () => { const range = computeDateRangeFromPreset( 'last-12-months' ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts index 48c97c2f7637..08b0bfe1518b 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts @@ -480,5 +480,49 @@ describe( 'getComparisonRangeFromPreset', () => { to: new Date( 2025, 7, 31, 23, 59, 59, 999 ), } ); } ); + + // Completed, it would shift onto a unit of another length: month to date + // read on 8 March came back three days short, and year to date read on + // 1 January 2028 came back inverted. + it.each( [ + [ 'March, after a shorter February', new Date( 2026, 2, 1 ), new Date( 2026, 2, 8 ) ], + [ 'February, after a longer January', new Date( 2026, 1, 1 ), new Date( 2026, 1, 15 ) ], + [ 'September, after a longer August', new Date( 2026, 8, 1 ), new Date( 2026, 8, 8 ) ], + ] )( 'mirrors month to date with an equal-length window in %s', ( _label, first, last ) => { + const monthToDate = { + from: new Date( first.getFullYear(), first.getMonth(), first.getDate(), 0, 0, 0, 0 ), + to: new Date( last.getFullYear(), last.getMonth(), last.getDate(), 23, 59, 59, 999 ), + }; + + const comparison = getComparisonRangeFromPreset( monthToDate, 'previous-period', { + primaryPresetId: 'month-to-date', + } ); + + expect( differenceInDays( comparison!.to!, comparison!.from! ) ).toBe( + differenceInDays( monthToDate.to, monthToDate.from ) + ); + expect( comparison!.to!.getTime() ).toBeLessThan( monthToDate.from.getTime() ); + } ); + + it.each( [ + [ 'the first day of a leap year', new Date( 2028, 0, 1 ), new Date( 2028, 0, 1 ) ], + [ 'the first day after one', new Date( 2025, 0, 1 ), new Date( 2025, 0, 1 ) ], + [ 'a day well into a leap year', new Date( 2028, 0, 1 ), new Date( 2028, 2, 5 ) ], + ] )( 'mirrors year to date with an equal-length window on %s', ( _label, first, last ) => { + const yearToDate = { + from: new Date( first.getFullYear(), first.getMonth(), first.getDate(), 0, 0, 0, 0 ), + to: new Date( last.getFullYear(), last.getMonth(), last.getDate(), 23, 59, 59, 999 ), + }; + + const comparison = getComparisonRangeFromPreset( yearToDate, 'previous-period', { + primaryPresetId: 'year-to-date', + } ); + + expect( comparison!.to!.getTime() ).toBeGreaterThan( comparison!.from!.getTime() ); + expect( differenceInDays( comparison!.to!, comparison!.from! ) ).toBe( + differenceInDays( yearToDate.to, yearToDate.from ) + ); + expect( comparison!.to!.getTime() ).toBeLessThan( yearToDate.from.getTime() ); + } ); } ); } ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/menu-surface-presets.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/menu-surface-presets.test.ts index 94625b543dbe..c9de033c957d 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/menu-surface-presets.test.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/__tests__/menu-surface-presets.test.ts @@ -21,8 +21,8 @@ describe( 'getMenuSurfacePresetGroups', () => { 'last-90-days', 'last-365-days', ], - [ 'last-month' ], - [ 'last-12-months', 'last-year' ], + [ 'month-to-date', 'last-month' ], + [ 'year-to-date', 'last-12-months', 'last-year' ], ] ); } ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts index e44f0a689e98..6df9e1f8fa51 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts @@ -79,6 +79,15 @@ describe( 'completeToDateRange', () => { expect( completeToDateRange( complete, 'last-12-months' ) ).toEqual( complete ); } ); + // Completing either shifts it onto a unit of another length, and its + // previous period stops coming back equal — see the comparison tests. + it.each( [ + [ 'month-to-date' as const, { from: at( 2026, 8, 1 ), to: endOf( 2026, 8, 20 ) } ], + [ 'year-to-date' as const, { from: at( 2026, 1, 1 ), to: endOf( 2026, 8, 20 ) } ], + ] )( 'leaves %s alone', ( presetId, range ) => { + expect( completeToDateRange( range, presetId ) ).toBe( range ); + } ); + it( 'returns any other preset’s range as is, even one starting on the first', () => { // A hand-picked range has no running month to complete. expect( completeToDateRange( toDate, 'custom' ) ).toBe( toDate ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/index.ts b/projects/packages/premium-analytics/packages/datetime/src/index.ts index fba3832bddee..388d53a40966 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/index.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/index.ts @@ -49,7 +49,9 @@ export { PRESET_LAST_30_DAYS, PRESET_LAST_90_DAYS, PRESET_LAST_365_DAYS, + PRESET_MONTH_TO_DATE, PRESET_LAST_MONTH, + PRESET_YEAR_TO_DATE, PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR, PRESET_CUSTOM, diff --git a/projects/packages/premium-analytics/packages/datetime/src/presets/index.ts b/projects/packages/premium-analytics/packages/datetime/src/presets/index.ts index 899af5fefea2..c3f235925de7 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/presets/index.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/presets/index.ts @@ -7,7 +7,9 @@ export { PRESET_LAST_30_DAYS, PRESET_LAST_90_DAYS, PRESET_LAST_365_DAYS, + PRESET_MONTH_TO_DATE, PRESET_LAST_MONTH, + PRESET_YEAR_TO_DATE, PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR, PRESET_CUSTOM, diff --git a/projects/packages/premium-analytics/packages/datetime/src/presets/primary.ts b/projects/packages/premium-analytics/packages/datetime/src/presets/primary.ts index 6abeb30b4e31..6b3393e098f7 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/presets/primary.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/presets/primary.ts @@ -28,7 +28,9 @@ import { PRESET_LAST_30_DAYS, PRESET_LAST_90_DAYS, PRESET_LAST_365_DAYS, + PRESET_MONTH_TO_DATE, PRESET_LAST_MONTH, + PRESET_YEAR_TO_DATE, PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR, PRESET_CUSTOM, @@ -139,6 +141,14 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [ to: endOfToday, } ), }, + { + id: PRESET_MONTH_TO_DATE, + getLabel: () => __( 'Month to date', 'jetpack-premium-analytics-pkg' ), + getRange: ( { initOfToday, endOfToday } ) => ( { + from: startOfMonth( initOfToday ), + to: endOfToday, + } ), + }, { id: PRESET_LAST_MONTH, getLabel: () => __( 'Last month', 'jetpack-premium-analytics-pkg' ), @@ -147,6 +157,14 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [ to: endOfLastMonth, } ), }, + { + id: PRESET_YEAR_TO_DATE, + getLabel: () => __( 'Year to date', 'jetpack-premium-analytics-pkg' ), + getRange: ( { initOfToday, endOfToday } ) => ( { + from: startOfYear( initOfToday ), + to: endOfToday, + } ), + }, { id: PRESET_LAST_12_MONTHS, getLabel: () => __( 'Last 12 months', 'jetpack-premium-analytics-pkg' ), diff --git a/projects/packages/premium-analytics/packages/datetime/src/presets/types.ts b/projects/packages/premium-analytics/packages/datetime/src/presets/types.ts index 4654b5f218c2..d49e0faa557f 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/presets/types.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/presets/types.ts @@ -8,7 +8,9 @@ export const PRESET_LAST_7_DAYS = 'last-7-days' as const; export const PRESET_LAST_30_DAYS = 'last-30-days' as const; export const PRESET_LAST_90_DAYS = 'last-90-days' as const; export const PRESET_LAST_365_DAYS = 'last-365-days' as const; +export const PRESET_MONTH_TO_DATE = 'month-to-date' as const; export const PRESET_LAST_MONTH = 'last-month' as const; +export const PRESET_YEAR_TO_DATE = 'year-to-date' as const; export const PRESET_LAST_12_MONTHS = 'last-12-months' as const; export const PRESET_LAST_YEAR = 'last-year' as const; @@ -23,7 +25,9 @@ export const SELECTABLE_PRESETS = [ PRESET_LAST_30_DAYS, PRESET_LAST_90_DAYS, PRESET_LAST_365_DAYS, + PRESET_MONTH_TO_DATE, PRESET_LAST_MONTH, + PRESET_YEAR_TO_DATE, PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR, ] as const; @@ -70,8 +74,8 @@ export const MENU_SURFACE_PRESET_GROUPS = [ PRESET_LAST_90_DAYS, PRESET_LAST_365_DAYS, ], - [ PRESET_LAST_MONTH ], - [ PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR ], + [ PRESET_MONTH_TO_DATE, PRESET_LAST_MONTH ], + [ PRESET_YEAR_TO_DATE, PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR ], [ PRESET_ALL_TIME ], ] as const; diff --git a/projects/packages/premium-analytics/packages/fields/src/report-params-field/__tests__/report-params-field.test.tsx b/projects/packages/premium-analytics/packages/fields/src/report-params-field/__tests__/report-params-field.test.tsx index 3722a0796588..00507f083a18 100644 --- a/projects/packages/premium-analytics/packages/fields/src/report-params-field/__tests__/report-params-field.test.tsx +++ b/projects/packages/premium-analytics/packages/fields/src/report-params-field/__tests__/report-params-field.test.tsx @@ -159,7 +159,9 @@ describe( 'report params field', () => { 'Last 30 days', 'Last 90 days', 'Last 365 days', + 'Month to date', 'Last month', + 'Year to date', 'Last 12 months', 'Last year', 'Custom range', diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx index cb3c56f7b347..f8d310fa5b2e 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx @@ -112,7 +112,9 @@ describe( 'DateFiltersPanel', () => { 'Last 30 days', 'Last 90 days', 'Last 365 days', + 'Month to date', 'Last month', + 'Year to date', 'Last 12 months', 'Last year', 'All time', diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/__tests__/date-period-dropdown.test.tsx b/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/__tests__/date-period-dropdown.test.tsx index de6363761944..78ad16be6d14 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/__tests__/date-period-dropdown.test.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/__tests__/date-period-dropdown.test.tsx @@ -65,7 +65,9 @@ describe( 'DatePeriodDropdown', () => { 'Last 30 days', 'Last 90 days', 'Last 365 days', + 'Month to date', 'Last month', + 'Year to date', 'Last 12 months', 'Last year', 'Custom range', diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/date-period-dropdown.scss b/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/date-period-dropdown.scss index 4caceabddfc4..29f1bb873729 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/date-period-dropdown.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-period-dropdown/date-period-dropdown.scss @@ -38,10 +38,11 @@ } // The calendar opens beside the list rather than replacing it, so the panel - // is the row holding both. + // is the row holding both. Both halves stretch: the seam below is drawn on + // the calendar, so a calendar shorter than the list would cut it short. &__panel { display: flex; - align-items: flex-start; + align-items: stretch; } // Owned here rather than by the calendar: it is the seam between the diff --git a/projects/plugins/jetpack/changelog/uni-754-to-date-presets b/projects/plugins/jetpack/changelog/uni-754-to-date-presets new file mode 100644 index 000000000000..c58051096d02 --- /dev/null +++ b/projects/plugins/jetpack/changelog/uni-754-to-date-presets @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Premium Analytics: add the Month to date and Year to date periods to the date picker. diff --git a/projects/plugins/premium-analytics/changelog/uni-754-to-date-presets b/projects/plugins/premium-analytics/changelog/uni-754-to-date-presets new file mode 100644 index 000000000000..4aebad88e7f3 --- /dev/null +++ b/projects/plugins/premium-analytics/changelog/uni-754-to-date-presets @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Date picker: add the Month to date and Year to date periods.