-
Notifications
You must be signed in to change notification settings - Fork 891
feat(premium-analytics): port formatters package from next-woocommerce-analytics #49226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
chihsuan
merged 26 commits into
trunk
from
wooa7s-1313-integrate-formatters-package-into-analytics
Jun 5, 2026
Merged
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
00fed2d
feat(premium-analytics): add tsconfig paths and typecheck for interna…
chihsuan 20e6241
Potential fix for pull request finding
chihsuan 662c887
Potential fix for pull request finding
chihsuan d4da9ca
docs(premium-analytics): clarify internal-package naming and rename init
chihsuan c2b0fae
chore(premium-analytics): copy formatters package from next-woocommer…
chihsuan 5c2cd81
chore(premium-analytics): adapt formatters package.json for internal-…
chihsuan a0a3a41
chore(premium-analytics): pin formatters deps for Jetpack monorepo
chihsuan 27b23d2
docs(premium-analytics): adapt formatters README and code comments fo…
chihsuan 390d7d8
chore(premium-analytics): wire formatters deps into parent and relax …
chihsuan ae03177
chore(premium-analytics): format formatters and clean up lint issues
chihsuan 216f6e2
changelog(premium-analytics): add entry for WOOA7S-1313 formatters port
chihsuan d8ad326
chore(premium-analytics): add @types/jest for formatters test typecheck
chihsuan 528458b
Merge branch 'trunk' into wooa7s-1320-configure-workspace-and-typescr…
chihsuan feeb212
docs(premium-analytics): revert internal-packages README section
chihsuan 2beeaa9
fix(premium-analytics): align route name with internal-package conven…
chihsuan e028a6d
Merge remote-tracking branch 'origin/wooa7s-1320-configure-workspace-…
chihsuan b33e257
docs(premium-analytics): use canonical package name in formatters README
chihsuan 58e4756
Update projects/packages/premium-analytics/packages/formatters/README.md
chihsuan b3cbdeb
Update projects/packages/premium-analytics/packages/formatters/README.md
chihsuan 952a10c
Merge remote-tracking branch 'origin/trunk' into wooa7s-1320-configur…
chihsuan 0601ef2
Merge remote-tracking branch 'origin/wooa7s-1320-configure-workspace-…
chihsuan 50d1d3f
Merge remote-tracking branch 'origin/trunk' into wooa7s-1313-integrat…
chihsuan c52d032
Update projects/packages/premium-analytics/packages/formatters/src/me…
chihsuan a05f141
Merge remote-tracking branch 'origin/trunk' into wooa7s-1313-integrat…
chihsuan 6f52ebf
Merge branch 'wooa7s-1313-integrate-formatters-package-into-analytics…
chihsuan d3a8e33
Merge remote-tracking branch 'origin/trunk' into wooa7s-1313-integrat…
chihsuan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
projects/packages/premium-analytics/changelog/add-internal-package-resolution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: added | ||
|
|
||
| Add a tsconfig paths alias and typecheck script so internal packages/* resolve for types/IDE. |
4 changes: 4 additions & 0 deletions
4
...kages/premium-analytics/changelog/wooa7s-1313-integrate-formatters-package-into-analytics
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: added | ||
|
|
||
| Port formatters package (number/currency/percentage metric formatter and date helpers) as an internal package from next-woocommerce-analytics. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { makeBaseConfig, defineConfig } from 'jetpack-js-tools/eslintrc/base.mjs'; | ||
|
|
||
| /** | ||
| * Soften JSDoc rules for `packages/formatters/**` so the initial port can | ||
| * land with the upstream JSDoc style (descriptions on the function body, | ||
| * not on per-param tags). Temporary — backfill proper JSDoc on the | ||
| * helpers and remove this override (at which point this whole file can go | ||
| * away). | ||
| */ | ||
| export default defineConfig( makeBaseConfig( import.meta.url ), { | ||
| files: [ 'packages/formatters/**' ], | ||
| rules: { | ||
| 'jsdoc/require-description': 'off', | ||
| 'jsdoc/require-param': 'off', | ||
| 'jsdoc/require-param-description': 'off', | ||
| 'jsdoc/require-returns': 'off', | ||
| 'jsdoc/check-indentation': 'off', | ||
| }, | ||
| } ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
projects/packages/premium-analytics/packages/formatters/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| # @automattic/jetpack-premium-analytics-formatters | ||
|
|
||
| Locale-aware formatting utilities for Jetpack Premium Analytics. | ||
|
|
||
| Thin wrapper over `@automattic/number-formatters` (numbers, currency) and | ||
| `date-fns` (dates), plus a domain-specific orchestrator (`formatMetricValue`) | ||
| that routes between formatters by analytics metric type. | ||
|
|
||
| ## Exports | ||
|
|
||
| ```typescript | ||
| import { | ||
| formatMetricValue, | ||
| formatDate, | ||
| formatDateRange, | ||
| } from '@automattic/jetpack-premium-analytics-formatters'; | ||
| ``` | ||
|
|
||
| ## `formatMetricValue( value, type?, options? )` | ||
|
|
||
| Format a numeric value based on its metric type. | ||
| Returns `''` for null, undefined, or NaN. | ||
|
|
||
| ```typescript | ||
| formatMetricValue( 9876 ); // '9,877' | ||
|
chihsuan marked this conversation as resolved.
Outdated
|
||
| formatMetricValue( 1500, 'number', { | ||
| useMultipliers: true, | ||
| decimals: 1, | ||
| } ); // '1.5K' | ||
| formatMetricValue( 192088.05, 'currency' ); // '$192,088.05' | ||
| formatMetricValue( 0.25, 'percentage' ); // '+25%' | ||
| formatMetricValue( 4.75, 'average' ); // '4.75' | ||
| formatMetricValue( 192088, 'currency', { | ||
| useMultipliers: true, | ||
| currencyCode: 'EUR', | ||
| } ); // '192.09K€' | ||
| ``` | ||
|
|
||
| | Parameter | Type | Default | Description | | ||
| | ------------------------ | ----------------------------------------------------- | ---------------------------------------- | ---------------------------------------------- | | ||
| | `value` | `string \| number \| null` | | Value to format | | ||
| | `type` | `'number' \| 'currency' \| 'percentage' \| 'average'` | `'number'` | Formatting strategy | | ||
| | `options.decimals` | `number` | varies by type | Decimal precision (0 for number, 2 for others) | | ||
| | `options.useMultipliers` | `boolean` | `false` | Compact notation (K/M suffixes) | | ||
| | `options.signDisplay` | `Intl` sign mode | `'auto'` (`'exceptZero'` for percentage) | Sign display | | ||
| | `options.currencyCode` | `string` | `'USD'` | ISO 4217 currency code | | ||
|
|
||
| ## `formatDate( date, format? )` | ||
|
|
||
| Format a date using a named preset or custom `date-fns` pattern. | ||
| Defaults to `'medium'`. | ||
|
|
||
| ```typescript | ||
| formatDate( new Date( '2025-06-21' ) ); // 'Jun 21, 2025' | ||
| formatDate( new Date( '2025-06-21' ), 'short' ); // 'Jun 21' | ||
| formatDate( new Date( '2025-06-21' ), 'long' ); // 'June 21, 2025' | ||
| formatDate( new Date( '2025-06-21' ), 'dd/MM/yyyy' ); // '21/06/2025' | ||
| ``` | ||
|
|
||
| **Named presets:** `short`, `medium` (default), `long`, `full`, `day`, `month`, `year`, `monthYear`, `numeric`, `iso`, `dateTime`. | ||
|
|
||
| ## `formatDateRange( range? )` | ||
|
|
||
| Format a date range into a human-readable string. | ||
| Returns `''` when range or dates are missing. | ||
|
|
||
| ```typescript | ||
| formatDateRange( { from, to } ); | ||
| // same day: 'Jun 21, 2025' | ||
| // same month: 'Jun 21-25, 2025' | ||
| // same year: 'Jun 21-Jul 25, 2025' | ||
| // cross-year: 'Jun 21, 2024-Jul 25, 2025' | ||
| ``` | ||
|
|
||
| | Parameter | Type | Description | | ||
| | --------- | ---------------------------- | ----------------- | | ||
| | `range` | `{ from?: Date; to?: Date }` | Date range object | | ||
|
|
||
| ## Architecture | ||
|
|
||
| Number and currency formatting delegates to `@automattic/number-formatters` | ||
| (a tier-2 published Jetpack package). Date formatting uses `date-fns`. The | ||
| `formatMetricValue` orchestrator is domain-specific — it routes to the right | ||
| formatter based on the metric type. | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - `@automattic/number-formatters` — number/currency primitives | ||
| - `date-fns` — date formatting | ||
13 changes: 13 additions & 0 deletions
13
projects/packages/premium-analytics/packages/formatters/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "name": "@automattic/jetpack-premium-analytics-formatters", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "main": "src/index.ts", | ||
| "types": "src/index.ts", | ||
| "sideEffects": false, | ||
| "dependencies": { | ||
| "@automattic/number-formatters": "workspace:*", | ||
| "date-fns": "4.1.0" | ||
| } | ||
| } |
127 changes: 127 additions & 0 deletions
127
...ckages/premium-analytics/packages/formatters/src/date/__tests__/format-date-range.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { formatDate } from '../format-date'; | ||
| import { formatDateRange } from '../format-date-range'; | ||
|
|
||
| jest.mock( '../format-date' ); | ||
|
|
||
| describe( 'formatDateRange', () => { | ||
| /** | ||
| * Setup mock for formatDate function. | ||
| */ | ||
| const setupMocks = () => { | ||
| ( formatDate as jest.Mock ).mockImplementation( ( date: Date, formatString?: string ) => { | ||
| const monthNames = [ | ||
| 'Jan', | ||
| 'Feb', | ||
| 'Mar', | ||
| 'Apr', | ||
| 'May', | ||
| 'Jun', | ||
| 'Jul', | ||
| 'Aug', | ||
| 'Sep', | ||
| 'Oct', | ||
| 'Nov', | ||
| 'Dec', | ||
| ]; | ||
|
|
||
| const dateStr = date.toISOString().split( 'T' )[ 0 ]; | ||
|
|
||
| if ( formatString === 'iso' ) { | ||
| return dateStr; | ||
| } | ||
|
|
||
| // eslint-disable-next-line @wordpress/no-unused-vars-before-return -- allow unused vars before return for test mock | ||
| const [ year, month, day ] = dateStr.split( '-' ); | ||
| const monthName = monthNames[ parseInt( month, 10 ) - 1 ]; | ||
|
|
||
| if ( formatString === 'year' ) { | ||
| return year; | ||
| } | ||
|
|
||
| if ( formatString === 'monthYear' ) { | ||
| return `${ monthName } ${ year }`; | ||
| } | ||
|
|
||
| const dayNum = parseInt( day, 10 ); | ||
| if ( formatString === 'short' ) { | ||
| return `${ monthName } ${ dayNum }`; | ||
| } | ||
|
|
||
| if ( formatString === 'd, yyyy' ) { | ||
| return `${ dayNum }, ${ year }`; | ||
| } | ||
|
|
||
| // Default: medium format | ||
| return `${ monthName } ${ dayNum }, ${ year }`; | ||
| } ); | ||
| }; | ||
|
|
||
| beforeEach( () => { | ||
| jest.clearAllMocks(); | ||
| setupMocks(); | ||
| } ); | ||
|
|
||
| describe( 'edge cases', () => { | ||
| it( 'returns empty string when "from" is missing', () => { | ||
| const result = formatDateRange( { | ||
| from: undefined, | ||
| to: new Date( '2025-06-21' ), | ||
| } ); | ||
| expect( result ).toBe( '' ); | ||
| } ); | ||
|
|
||
| it( 'returns empty string when "to" is missing', () => { | ||
| const result = formatDateRange( { | ||
| from: new Date( '2025-06-21' ), | ||
| to: undefined, | ||
| } ); | ||
| expect( result ).toBe( '' ); | ||
| } ); | ||
|
|
||
| it( 'returns empty string when both dates are missing', () => { | ||
| const result = formatDateRange( { | ||
| from: undefined, | ||
| to: undefined, | ||
| } ); | ||
| expect( result ).toBe( '' ); | ||
| } ); | ||
| } ); | ||
|
|
||
| describe( 'same date', () => { | ||
| it( 'formats same date as single date', () => { | ||
| const date = new Date( '2025-06-21' ); | ||
| const result = formatDateRange( { from: date, to: date } ); | ||
| expect( result ).toBe( 'Jun 21, 2025' ); | ||
| } ); | ||
| } ); | ||
|
|
||
| describe( 'same month and year', () => { | ||
| it( 'formats date range within same month', () => { | ||
| const from = new Date( '2025-06-21' ); | ||
| const to = new Date( '2025-06-25' ); | ||
| const result = formatDateRange( { from, to } ); | ||
| expect( result ).toBe( 'Jun 21-25, 2025' ); | ||
| } ); | ||
| } ); | ||
|
|
||
| describe( 'same year, different months', () => { | ||
| it( 'formats date range across months in same year', () => { | ||
| const from = new Date( '2025-06-21' ); | ||
| const to = new Date( '2025-07-25' ); | ||
| const result = formatDateRange( { from, to } ); | ||
| expect( result ).toBe( 'Jun 21-Jul 25, 2025' ); | ||
| } ); | ||
| } ); | ||
|
|
||
| describe( 'different years', () => { | ||
| it( 'formats date range across different years', () => { | ||
| const from = new Date( '2024-06-21' ); | ||
| const to = new Date( '2025-07-25' ); | ||
| const result = formatDateRange( { from, to } ); | ||
| expect( result ).toBe( 'Jun 21, 2024-Jul 25, 2025' ); | ||
| } ); | ||
| } ); | ||
| } ); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.