dev: moved geolocation utils from wccom to here (#38356)

This commit is contained in:
RJ 2023-05-19 17:02:39 +10:00 committed by GitHub
parent 5ba15f84cd
commit 06e6f5012f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 26590 additions and 8 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Moved geolocation country matching functions to @woocommerce/onboarding

View File

@ -0,0 +1,4 @@
{
"rootDir": "./src",
"preset": "../node_modules/@woocommerce/internal-js-tests/jest-preset.js"
}

View File

@ -37,14 +37,19 @@
"@wordpress/components": "wp-6.0", "@wordpress/components": "wp-6.0",
"@wordpress/element": "wp-6.0", "@wordpress/element": "wp-6.0",
"@wordpress/i18n": "wp-6.0", "@wordpress/i18n": "wp-6.0",
"string-similarity": "4.0.4",
"gridicons": "^3.4.0" "gridicons": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.5", "@babel/core": "^7.17.5",
"@testing-library/react": "^12.1.3",
"@types/string-similarity" : "4.0.0",
"@types/wordpress__components": "^19.10.3", "@types/wordpress__components": "^19.10.3",
"@types/wordpress__data": "^6.0.0", "@types/wordpress__data": "^6.0.0",
"@types/jest": "^27.4.1",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*", "@woocommerce/internal-style-build": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@wordpress/browserslist-config": "wp-6.0", "@wordpress/browserslist-config": "wp-6.0",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",
@ -62,6 +67,7 @@
}, },
"scripts": { "scripts": {
"turbo:build": "pnpm run build:js && pnpm run build:css", "turbo:build": "pnpm run build:js && pnpm run build:css",
"turbo:test": "jest --config ./jest.config.json",
"prepare": "composer install", "prepare": "composer install",
"changelog": "composer exec -- changelogger", "changelog": "composer exec -- changelogger",
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*", "clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
@ -70,12 +76,15 @@
"build:js": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json", "build:js": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json",
"build:css": "webpack", "build:css": "webpack",
"start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\" \"webpack --watch\"", "start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\" \"webpack --watch\"",
"test": "pnpm -w exec turbo run turbo:test --filter=$npm_package_name",
"prepack": "pnpm run clean && pnpm run build", "prepack": "pnpm run clean && pnpm run build",
"lint:fix": "eslint src --fix" "lint:fix": "eslint src --fix",
"test-staged": "jest --bail --config ./jest.config.json --findRelatedTests"
}, },
"lint-staged": { "lint-staged": {
"*.(t|j)s?(x)": [ "*.(t|j)s?(x)": [
"pnpm lint:fix" "pnpm lint:fix",
"pnpm test-staged"
] ]
} }
} }

View File

@ -15,3 +15,4 @@ export { WooPaymentGatewayConfigure } from './components/WooPaymentGatewayConfig
export { WooOnboardingTaskListItem } from './components/WooOnboardingTaskListItem'; export { WooOnboardingTaskListItem } from './components/WooOnboardingTaskListItem';
export { WooOnboardingTaskListHeader } from './components/WooOnboardingTaskListHeader'; export { WooOnboardingTaskListHeader } from './components/WooOnboardingTaskListHeader';
export { WooOnboardingTask } from './components/WooOnboardingTask'; export { WooOnboardingTask } from './components/WooOnboardingTask';
export * from './utils/countries';

View File

@ -0,0 +1,79 @@
/**
* External dependencies
*/
import stringSimilarity from 'string-similarity';
/**
* Internal dependencies
*/
import { getMappingRegion } from './location-mapping';
/**
* Country state option.
*/
export type CountryStateOption = {
key: string;
label: string;
};
export type Location = {
country_short?: string;
region?: string;
city?: string;
};
/**
* Returns a country option for the given location.
*/
export const findCountryOption = (
countryStateOptions: CountryStateOption[],
location: Location | undefined,
minimumSimilarity = 0.7
) => {
if ( ! location ) {
return null;
}
let match = null;
let matchSimilarity = minimumSimilarity;
// eslint-disable-next-line @wordpress/no-unused-vars-before-return -- don't want to put this inside the loop
const mappingRegion = getMappingRegion( location );
for ( const option of countryStateOptions ) {
// Country matches exactly.
if ( option.key === location.country_short ) {
return option;
}
// Countries have regions such as 'US:CA'.
const countryCode = option.key.split( ':' )[ 0 ];
if (
countryCode === location.country_short &&
option.label.includes( '—' )
) {
const wcRegion = option.label.split( '—' )[ 1 ].trim();
// Region matches exactly with mapping.
if ( mappingRegion === wcRegion ) {
return option;
}
// Find the region with the highest similarity.
const similarity = Math.max(
stringSimilarity.compareTwoStrings(
wcRegion,
location.region || ''
),
stringSimilarity.compareTwoStrings(
wcRegion,
location.city || ''
)
);
if ( similarity >= matchSimilarity ) {
match = option;
matchSimilarity = similarity;
}
}
}
return match;
};

View File

@ -0,0 +1,58 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { Location } from '.';
/**
* This file is used to map a location to a WC region label
* so that we can find the correct country option
* since WPCOM geolocation returns a different
* region or city name.
*/
// Key is the country code, value is an object with keys as region/city names and values as WC region labels.
const MAPPING: Record< string, Record< string, string > > = {
PH: {
'National Capital Region': __( 'Metro Manila', 'woocommerce' ),
},
IT: {
Rome: __( 'Roma', 'woocommerce' ),
},
};
/**
* Returns a WC mapping region name for the given country, region and city.
*/
export const getMappingRegion = ( {
country_short: countryCode,
region = '',
city = '',
}: Location ) => {
if ( ! countryCode ) {
return null;
}
const countryMapping = MAPPING[ countryCode ];
if ( ! countryMapping ) {
return null;
}
const regionMapping = countryMapping[ region ];
if ( regionMapping ) {
return regionMapping;
}
const cityMapping = countryMapping[ city ];
if ( cityMapping ) {
return cityMapping;
}
return null;
};
export default MAPPING;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
/* eslint-disable camelcase */
/* eslint-disable no-undef */
/**
* Internal dependencies
*/
import { findCountryOption } from '../../';
import { countryStateOptions } from './country-options';
import { locations } from './locations';
describe( 'findCountryOption', () => {
it( 'should return null on undefined location', () => {
const location = undefined;
expect( findCountryOption( countryStateOptions, location ) ).toEqual(
null
);
} );
it( 'should return null when not found', () => {
const location = { country_short: 'US' };
expect( findCountryOption( countryStateOptions, location ) ).toBeNull();
} );
it.each( [
{
location: { country_short: 'TW' },
expected: {
key: 'TW',
label: 'Taiwan',
},
},
{
location: { country_short: 'US', region: 'California' },
expected: {
key: 'US:CA',
label: 'United States (US) — California',
},
},
{
location: { country_short: 'ES', region: 'Madrid, Comunidad de' },
expected: {
key: 'ES:M',
label: 'Spain — Madrid',
},
},
{
location: {
country_short: 'AR',
region: 'Ciudad Autonoma de Buenos Aires',
},
expected: {
key: 'AR:C',
label: 'Argentina — Ciudad Autónoma de Buenos Aires',
},
},
{
location: {
country_short: 'IT',
region: 'Lazio',
city: 'Rome',
},
expected: {
key: 'IT:RM',
label: 'Italy — Roma',
},
},
{
location: {
country_short: 'PH',
region: 'National Capital Region',
city: 'Makati',
},
expected: {
key: 'PH:00',
label: 'Philippines — Metro Manila',
},
},
] )(
'should return the country option for location $expected',
( { location, expected } ) => {
expect(
findCountryOption( countryStateOptions, location, 0.4 )
).toEqual( expected );
}
);
it( 'should return a country option for > 98% locations', () => {
let matchCount = 0;
locations.forEach( ( location ) => {
if ( findCountryOption( countryStateOptions, location, 0.4 ) ) {
matchCount++;
}
} );
expect( matchCount / locations.length ).toBeGreaterThan( 0.98 );
} );
} );

View File

@ -0,0 +1,41 @@
/* eslint-disable camelcase */
/**
* Internal dependencies
*/
import { getMappingRegion } from '../../location-mapping';
describe( 'getMappingRegion', () => {
it( 'should return null for an empty location', () => {
expect( getMappingRegion( {} ) ).toBeNull();
} );
it( 'should return null for a location that is not in the mapping', () => {
expect(
getMappingRegion( { country_short: 'US', region: 'California' } )
).toBeNull();
} );
it( 'should return null for a location with no region', () => {
expect( getMappingRegion( { country_short: 'PH' } ) ).toBeNull();
} );
it( 'should return the region for a location that is in the mapping with a region', () => {
expect(
getMappingRegion( {
country_short: 'PH',
region: 'National Capital Region',
} )
).toBe( 'Metro Manila' );
} );
it( 'should return the region for a location that is in the mapping with a city', () => {
expect(
getMappingRegion( {
country_short: 'IT',
region: 'Lazio',
city: 'Rome',
} )
).toBe( 'Roma' );
} );
} );

File diff suppressed because it is too large Load Diff

View File

@ -1949,10 +1949,22 @@ importers:
gridicons: gridicons:
specifier: ^3.4.0 specifier: ^3.4.0
version: 3.4.0(react@17.0.2) version: 3.4.0(react@17.0.2)
string-similarity:
specifier: 4.0.4
version: 4.0.4
devDependencies: devDependencies:
'@babel/core': '@babel/core':
specifier: ^7.17.5 specifier: ^7.17.5
version: 7.17.8 version: 7.17.8
'@testing-library/react':
specifier: ^12.1.3
version: 12.1.4(react-dom@16.14.0)(react@17.0.2)
'@types/jest':
specifier: ^27.4.1
version: 27.4.1
'@types/string-similarity':
specifier: 4.0.0
version: 4.0.0
'@types/wordpress__components': '@types/wordpress__components':
specifier: ^19.10.3 specifier: ^19.10.3
version: 19.10.5(react-dom@16.14.0)(react@17.0.2) version: 19.10.5(react-dom@16.14.0)(react@17.0.2)
@ -1962,6 +1974,9 @@ importers:
'@woocommerce/eslint-plugin': '@woocommerce/eslint-plugin':
specifier: workspace:* specifier: workspace:*
version: link:../eslint-plugin version: link:../eslint-plugin
'@woocommerce/internal-js-tests':
specifier: workspace:*
version: link:../internal-js-tests
'@woocommerce/internal-style-build': '@woocommerce/internal-style-build':
specifier: workspace:* specifier: workspace:*
version: link:../internal-style-build version: link:../internal-style-build
@ -15537,6 +15552,20 @@ packages:
react-error-boundary: 3.1.4(react@17.0.2) react-error-boundary: 3.1.4(react@17.0.2)
dev: true dev: true
/@testing-library/react@12.1.4(react-dom@16.14.0)(react@17.0.2):
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
engines: {node: '>=12'}
peerDependencies:
react: '*'
react-dom: '*'
dependencies:
'@babel/runtime': 7.21.0
'@testing-library/dom': 8.11.3
'@types/react-dom': 17.0.17
react: 17.0.2
react-dom: 16.14.0(react@17.0.2)
dev: true
/@testing-library/react@12.1.4(react-dom@17.0.2)(react@17.0.2): /@testing-library/react@12.1.4(react-dom@17.0.2)(react@17.0.2):
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==} resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -16093,6 +16122,10 @@ packages:
/@types/stack-utils@2.0.1: /@types/stack-utils@2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
/@types/string-similarity@4.0.0:
resolution: {integrity: sha512-dMS4S07fbtY1AILG/RhuwmptmzK1Ql8scmAebOTJ/8iBtK/KI17NwGwKzu1uipjj8Kk+3mfPxum56kKZE93mzQ==}
dev: true
/@types/strip-ansi@3.0.0: /@types/strip-ansi@3.0.0:
resolution: {integrity: sha512-wVhzc+WJ/JNdV25MeaK0skxGdbdOFeqYv1sqY8yPXbsshZ0XwSbWWwfKzj836cPW+e+PpqUNvKoiac9ZqCdyRQ==} resolution: {integrity: sha512-wVhzc+WJ/JNdV25MeaK0skxGdbdOFeqYv1sqY8yPXbsshZ0XwSbWWwfKzj836cPW+e+PpqUNvKoiac9ZqCdyRQ==}
dev: false dev: false
@ -20969,13 +21002,13 @@ packages:
peerDependencies: peerDependencies:
eslint: '>= 4.12.1' eslint: '>= 4.12.1'
dependencies: dependencies:
'@babel/code-frame': 7.18.6 '@babel/code-frame': 7.16.7
'@babel/parser': 7.21.3 '@babel/parser': 7.17.8
'@babel/traverse': 7.21.3 '@babel/traverse': 7.17.3
'@babel/types': 7.21.3 '@babel/types': 7.17.0
eslint: 7.32.0 eslint: 7.32.0
eslint-visitor-keys: 1.3.0 eslint-visitor-keys: 1.3.0
resolve: 1.22.1 resolve: 1.20.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -27360,7 +27393,7 @@ packages:
vue-template-compiler: vue-template-compiler:
optional: true optional: true
dependencies: dependencies:
'@babel/code-frame': 7.18.6 '@babel/code-frame': 7.16.7
'@types/json-schema': 7.0.9 '@types/json-schema': 7.0.9
chalk: 4.1.2 chalk: 4.1.2
chokidar: 3.5.3 chokidar: 3.5.3
@ -40521,6 +40554,11 @@ packages:
char-regex: 1.0.2 char-regex: 1.0.2
strip-ansi: 6.0.1 strip-ansi: 6.0.1
/string-similarity@4.0.4:
resolution: {integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
dev: false
/string-template@0.2.1: /string-template@0.2.1:
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==} resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
dev: true dev: true