From d728d38219947684a87c3514a57c969b87da4703 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Mon, 23 Sep 2019 17:47:08 -0400 Subject: [PATCH] Refactor: Alternative approach to implementing extendable settings from the server (https://github.com/woocommerce/woocommerce-admin/pull/2917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * integrate with new asset data registration (php side) - includes back-compatibility. * update js configuration and implement settings alias - this aliases `@woocommerce/wc-admin-settings` to the settings api exposed via blocks (either the blocks plugin or core) and all the settings provided via the server. - Adds fallback for back-compat if `wc.wcSettings` is not available in the environment (fallsback to `wcSettings`). * initial pass to update all wcSettings direct usage to the new api - this is just an initial pass, more can be done in separate pulls. * missed one spot for adding new filter implementation * fix incorrect jest config * Avoid unnecessary assignment and directly return. * Remove unnecessary defaults * Fix inline comment text case. Co-Authored-By: Albert Juhé Lluveras * Remove unnecessary inline comment. * use @todo instead of TODO Co-Authored-By: Albert Juhé Lluveras * fix incorrect reference in webpack config. * add missing import and slightly delay dependency injection for scripts * update get-setting and set-setting callbacks * disable lint rule for console.error on dev doc builds --- .../components/report-table/index.js | 3 +- .../analytics/report/customers/config.js | 4 +- .../analytics/report/customers/table.js | 5 +- .../client/analytics/report/index.js | 4 +- .../client/analytics/report/orders/config.js | 7 +- .../report/products/table-variations.js | 13 ++- .../client/analytics/report/products/table.js | 13 ++- .../client/analytics/report/stock/index.js | 1 - .../client/analytics/report/stock/table.js | 4 +- .../client/analytics/settings/config.js | 35 ++++--- .../client/analytics/settings/index.js | 3 + .../settings/general/store-address.js | 3 +- .../client/dashboard/leaderboards/index.js | 3 +- .../profile-wizard/steps/business-details.js | 7 +- .../profile-wizard/steps/industry.js | 9 +- .../dashboard/store-performance/index.js | 8 +- .../client/dashboard/task-list/index.js | 9 +- .../task-list/tasks/shipping/rates.js | 19 +--- .../client/devdocs/example.js | 1 + .../client/header/activity-panel/index.js | 8 +- .../header/activity-panel/panels/inbox.js | 7 +- .../header/activity-panel/panels/orders.js | 1 + .../activity-panel/unread-indicators.js | 4 + .../client/settings/fallbacks.js | 92 +++++++++++++++++++ .../client/settings/index.js | 28 ++++++ .../src/advanced-filters/docs/example.js | 6 +- .../components/src/advanced-filters/index.js | 4 +- .../src/advanced-filters/number-filter.js | 4 +- .../packages/components/src/chart/index.js | 15 ++- .../components/src/filters/docs/example.js | 6 +- .../packages/currency/src/index.js | 14 +-- .../packages/currency/test/index.js | 67 ++++++++------ .../packages/date/src/index.js | 11 ++- .../packages/date/test/index.js | 28 ++++-- .../packages/number/src/index.js | 14 ++- .../packages/number/src/test/index.js | 33 ++++++- .../src/Features/ActivityPanels.php | 5 +- .../src/Features/Onboarding.php | 7 +- .../src/Features/OnboardingTasks.php | 6 +- plugins/woocommerce-admin/src/Loader.php | 71 +++++++++++--- .../tests/js/jest.config.json | 1 + .../tests/js/setup-globals.js | 63 +++++++------ plugins/woocommerce-admin/webpack.config.js | 5 + 43 files changed, 476 insertions(+), 175 deletions(-) create mode 100644 plugins/woocommerce-admin/client/settings/fallbacks.js create mode 100644 plugins/woocommerce-admin/client/settings/index.js diff --git a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js index 467a7c2f9d5..99962652727 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js @@ -300,7 +300,8 @@ export default compose( const { getCurrentUserData } = select( 'wc-api' ); const userData = getCurrentUserData(); - userPrefColumns = userData[ columnPrefsKey ]; + userPrefColumns = + userData && userData[ columnPrefsKey ] ? userData[ columnPrefsKey ] : userPrefColumns; } if ( isRequesting || ( query.search && ! ( query[ endpoint ] && query[ endpoint ].length ) ) ) { diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/config.js b/plugins/woocommerce-admin/client/analytics/report/customers/config.js index 27daf8421ad..09e9888e610 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/config.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/config.js @@ -5,6 +5,7 @@ import { __, _x } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; import { applyFilters } from '@wordpress/hooks'; +import { COUNTRIES as countries } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -116,9 +117,6 @@ export const advancedFilters = applyFilters( CUSTOMERS_REPORT_ADVANCED_FILTERS_F component: 'Search', type: 'countries', getLabels: async value => { - const countries = - ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || []; - const allLabels = countries.map( country => ( { id: country.code, label: decodeEntities( country.name ), diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/table.js b/plugins/woocommerce-admin/client/analytics/report/customers/table.js index 53dd429aaf2..90e45b1ae1f 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/table.js @@ -13,6 +13,7 @@ import { defaultTableDateFormat } from '@woocommerce/date'; import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'; import { Date, Link } from '@woocommerce/components'; import { numberFormat } from '@woocommerce/number'; +import { COUNTRIES as countries } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -102,9 +103,7 @@ export default class CustomersReportTable extends Component { } getCountryName( code ) { - const countries = ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || []; - const country = countries.find( c => c.code === code ); - return country ? country.name : null; + return typeof countries[ code ] !== 'undefined' ? countries[ code ] : null; } getRowsContent( customers ) { diff --git a/plugins/woocommerce-admin/client/analytics/report/index.js b/plugins/woocommerce-admin/client/analytics/report/index.js index 96ba6d3e18e..d7733df98b9 100644 --- a/plugins/woocommerce-admin/client/analytics/report/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/index.js @@ -14,6 +14,7 @@ import { find } from 'lodash'; */ import { useFilters } from '@woocommerce/components'; import { getQuery, getSearchWords } from '@woocommerce/navigation'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -33,6 +34,7 @@ import { searchItemsByString } from 'wc-api/items/utils'; import withSelect from 'wc-api/with-select'; export const REPORTS_FILTER = 'woocommerce_admin_reports_list'; +const manageStock = getSetting( 'manageStock', 'no' ); export const getReports = () => { const reports = [ @@ -71,7 +73,7 @@ export const getReports = () => { title: __( 'Downloads', 'woocommerce-admin' ), component: DownloadsReport, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { report: 'stock', title: __( 'Stock', 'woocommerce-admin' ), diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/config.js b/plugins/woocommerce-admin/client/analytics/report/orders/config.js index 110b2651f65..9bf1174d119 100644 --- a/plugins/woocommerce-admin/client/analytics/report/orders/config.js +++ b/plugins/woocommerce-admin/client/analytics/report/orders/config.js @@ -4,14 +4,13 @@ */ import { __, _x } from '@wordpress/i18n'; import { applyFilters } from '@wordpress/hooks'; +import { ORDER_STATUSES } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies */ import { getCouponLabels, getProductLabels } from 'lib/async-requests'; -const { orderStatuses } = wcSettings; - const ORDERS_REPORT_CHARTS_FILTER = 'woocommerce_admin_orders_report_charts'; const ORDERS_REPORT_FILTERS_FILTER = 'woocommerce_admin_orders_report_filters'; const ORDERS_REPORT_ADVANCED_FILTERS_FILTER = 'woocommerce_admin_orders_report_advanced_filters'; @@ -87,9 +86,9 @@ export const advancedFilters = applyFilters( ORDERS_REPORT_ADVANCED_FILTERS_FILT ], input: { component: 'SelectControl', - options: Object.keys( orderStatuses ).map( key => ( { + options: Object.keys( ORDER_STATUSES ).map( key => ( { value: key, - label: orderStatuses[ key ], + label: ORDER_STATUSES[ key ], } ) ), }, }, diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js b/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js index c404de9f154..b68635f5b6a 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js @@ -13,6 +13,7 @@ import { Link } from '@woocommerce/components'; import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { numberFormat } from '@woocommerce/number'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -20,6 +21,9 @@ import { numberFormat } from '@woocommerce/number'; import ReportTable from 'analytics/components/report-table'; import { isLowStock } from './utils'; +const manageStock = getSetting( 'manageStock', 'no' ); +const stockStatuses = getSetting( 'stockStatuses', {} ); + export default class VariationsReportTable extends Component { constructor() { super(); @@ -64,13 +68,13 @@ export default class VariationsReportTable extends Component { isSortable: true, isNumeric: true, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { label: __( 'Status', 'woocommerce-admin' ), key: 'stock_status', } : null, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { label: __( 'Stock', 'woocommerce-admin' ), key: 'stock', @@ -81,7 +85,6 @@ export default class VariationsReportTable extends Component { } getRowsContent( data = [] ) { - const { stockStatuses } = wcSettings; const { query } = this.props; const persistedQuery = getPersistedQuery( query ); @@ -125,7 +128,7 @@ export default class VariationsReportTable extends Component { ), value: orders_count, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { display: isLowStock( stock_status, stock_quantity, low_stock_amount ) ? ( @@ -137,7 +140,7 @@ export default class VariationsReportTable extends Component { value: stockStatuses[ stock_status ], } : null, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { display: stock_quantity, value: stock_quantity, diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table.js b/plugins/woocommerce-admin/client/analytics/report/products/table.js index 46d16522c7b..907b45854e4 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/table.js @@ -14,6 +14,7 @@ import { formatCurrency, getCurrencyFormatDecimal, renderCurrency } from '@wooco import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { Link, Tag } from '@woocommerce/components'; import { numberFormat } from '@woocommerce/number'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -24,6 +25,9 @@ import ReportTable from 'analytics/components/report-table'; import withSelect from 'wc-api/with-select'; import './style.scss'; +const manageStock = getSetting( 'manageStock', 'no' ); +const stockStatuses = getSetting( 'stockStatuses', {} ); + class ProductsReportTable extends Component { constructor() { super(); @@ -78,13 +82,13 @@ class ProductsReportTable extends Component { key: 'variations', isSortable: true, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { label: __( 'Status', 'woocommerce-admin' ), key: 'stock_status', } : null, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { label: __( 'Stock', 'woocommerce-admin' ), key: 'stock', @@ -95,7 +99,6 @@ class ProductsReportTable extends Component { } getRowsContent( data = [] ) { - const { stockStatuses } = wcSettings; const { query } = this.props; const persistedQuery = getPersistedQuery( query ); @@ -194,13 +197,13 @@ class ProductsReportTable extends Component { display: numberFormat( variations.length ), value: variations.length, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { display: manage_stock ? stockStatus : __( 'N/A', 'woocommerce-admin' ), value: manage_stock ? stockStatuses[ stock_status ] : null, } : null, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { display: manage_stock ? numberFormat( stock_quantity ) diff --git a/plugins/woocommerce-admin/client/analytics/report/stock/index.js b/plugins/woocommerce-admin/client/analytics/report/stock/index.js index f7af4783d7c..4ddd31b02a0 100644 --- a/plugins/woocommerce-admin/client/analytics/report/stock/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/stock/index.js @@ -15,7 +15,6 @@ import ReportFilters from 'analytics/components/report-filters'; export default class StockReport extends Component { render() { const { query, path } = this.props; - return ( { const { diff --git a/plugins/woocommerce-admin/client/analytics/settings/config.js b/plugins/woocommerce-admin/client/analytics/settings/config.js index 381f62252b0..4d255f8ded2 100644 --- a/plugins/woocommerce-admin/client/analytics/settings/config.js +++ b/plugins/woocommerce-admin/client/analytics/settings/config.js @@ -5,6 +5,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { applyFilters } from '@wordpress/hooks'; import interpolateComponents from 'interpolate-components'; +import { getSetting, ORDER_STATUSES } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -13,7 +14,7 @@ import { DEFAULT_ACTIONABLE_STATUSES } from 'wc-api/constants'; import DefaultDate from './default-date'; const SETTINGS_FILTER = 'woocommerce_admin_analytics_settings'; -const DEFAUTL_DATE_RANGE = 'period=month&compare=previous_year'; +const DEFAULT_DATE_RANGE = 'period=month&compare=previous_year'; const defaultOrderStatuses = [ 'completed', @@ -25,27 +26,33 @@ const defaultOrderStatuses = [ 'on-hold', ]; -const actionableOrderStatuses = Array.isArray( - wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses -) - ? wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses +const { + woocommerce_actionable_order_statuses, + woocommerce_excluded_report_order_statuses, + woocommerce_default_date_range, +} = getSetting( 'wcAdminSettings', { + woocommerce_actionable_order_statuses: [], + woocommerce_excluded_report_order_statuses: [], + woocommerce_default_date_range: DEFAULT_DATE_RANGE, +} ); + +const actionableOrderStatuses = Array.isArray( woocommerce_actionable_order_statuses ) + ? woocommerce_actionable_order_statuses : []; -const excludedOrderStatuses = Array.isArray( - wcSettings.wcAdminSettings.woocommerce_excluded_report_order_statuses -) - ? wcSettings.wcAdminSettings.woocommerce_excluded_report_order_statuses +const excludedOrderStatuses = Array.isArray( woocommerce_excluded_report_order_statuses ) + ? woocommerce_excluded_report_order_statuses : []; -const orderStatuses = Object.keys( wcSettings.orderStatuses ) +const orderStatuses = Object.keys( ORDER_STATUSES ) .filter( status => status !== 'refunded' ) .map( key => { return { value: key, - label: wcSettings.orderStatuses[ key ], + label: ORDER_STATUSES[ key ], description: sprintf( __( 'Exclude the %s status from reports', 'woocommerce-admin' ), - wcSettings.orderStatuses[ key ] + ORDER_STATUSES[ key ] ), }; } ); @@ -112,7 +119,7 @@ export const analyticsSettings = applyFilters( SETTINGS_FILTER, [ 'the default date range.', 'woocommerce-admin' ), - initialValue: wcSettings.wcAdminSettings.woocommerce_default_date_range || DEFAUTL_DATE_RANGE, - defaultValue: DEFAUTL_DATE_RANGE, + initialValue: woocommerce_default_date_range, + defaultValue: DEFAULT_DATE_RANGE, }, ] ); diff --git a/plugins/woocommerce-admin/client/analytics/settings/index.js b/plugins/woocommerce-admin/client/analytics/settings/index.js index dc30437dcbb..295f17d5d90 100644 --- a/plugins/woocommerce-admin/client/analytics/settings/index.js +++ b/plugins/woocommerce-admin/client/analytics/settings/index.js @@ -116,6 +116,9 @@ class Settings extends Component { * @param {object} state - State */ persistChanges( state ) { + // @todo Should remove global state from the file. This creates + // potential hard to debug side-effects. + wcSettings.wcAdminSettings = wcSettings.wcAdminSettings || {}; analyticsSettings.forEach( setting => { const updatedValue = state.settings[ setting.name ]; wcSettings.wcAdminSettings[ setting.name ] = updatedValue; diff --git a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.js b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.js index db177d525ec..470af546f36 100644 --- a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.js +++ b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; import { SelectControl, TextControl } from 'newspack-components'; import { useMemo } from 'react'; +import { COUNTRIES as countries } from '@woocommerce/wc-admin-settings'; /** * Form validation. @@ -38,8 +39,6 @@ export function validateStoreAddress( values ) { * @return {Object} Select options, { value: 'US:GA', label: 'United States - Georgia' } */ export function getCountryStateOptions() { - const countries = ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || []; - const countryStateOptions = countries.reduce( ( acc, country ) => { if ( ! country.states.length ) { acc.push( { diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js b/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js index 7621a83ebce..b56bad3ead0 100644 --- a/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js +++ b/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js @@ -13,6 +13,7 @@ import { withDispatch } from '@wordpress/data'; * WooCommerce dependencies */ import { EllipsisMenu, MenuItem, MenuTitle, SectionHeader } from '@woocommerce/components'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -161,7 +162,7 @@ export default compose( 'wc-api' ); const userData = getCurrentUserData(); - const allLeaderboards = wcSettings.dataEndpoints.leaderboards; + const { allLeaderboards } = getSetting( 'dataEndpoints', { leaderboards: {} } ); return { allLeaderboards, diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/business-details.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/business-details.js index 4dba2133e3a..f32e3f3bf38 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/business-details.js +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/business-details.js @@ -14,6 +14,7 @@ import { keys, pickBy } from 'lodash'; * WooCommerce dependencies */ import { numberFormat } from '@woocommerce/number'; +import { getSetting, CURRENCY as currency } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -23,6 +24,8 @@ import withSelect from 'wc-api/with-select'; import { recordEvent } from 'lib/tracks'; import { formatCurrency } from '@woocommerce/currency'; +const wcAdminAssetUrl = getSetting( 'wcAdminAssetUrl', '' ); + class BusinessDetails extends Component { constructor() { super(); @@ -50,7 +53,7 @@ class BusinessDetails extends Component { recordEvent( 'storeprofiler_store_business_details_continue', { product_number: product_count, already_selling: 'no' !== selling_venues, - currency: wcSettings.currency.code, + currency: currency.code, revenue, used_platform: other_platform, install_facebook: facebook, @@ -179,7 +182,7 @@ class BusinessDetails extends Component { { extensionBenefits.map( benefit => (
- +
{ benefit.title } diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/industry.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/industry.js index add37eceac1..8a3a3a5f8ed 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/industry.js +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/industry.js @@ -9,6 +9,11 @@ import { includes, filter } from 'lodash'; import { compose } from '@wordpress/compose'; import { withDispatch } from '@wordpress/data'; +/** + * WooCommerce Dependencies + */ +import { getSetting } from '@woocommerce/wc-admin-settings'; + /** * Internal dependencies */ @@ -16,6 +21,8 @@ import { H, Card } from '@woocommerce/components'; import withSelect from 'wc-api/with-select'; import { recordEvent } from 'lib/tracks'; +const onboarding = getSetting( 'onboarding', {} ); + class Industry extends Component { constructor() { super(); @@ -77,7 +84,7 @@ class Industry extends Component { } render() { - const { industries } = wcSettings.onboarding; + const { industries } = onboarding; const { error } = this.state; return ( diff --git a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js index ffd94a33610..79936e134cf 100644 --- a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js +++ b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js @@ -16,6 +16,7 @@ import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@wooco import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { calculateDelta, formatValue } from '@woocommerce/number'; import { formatCurrency } from '@woocommerce/currency'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -33,11 +34,14 @@ import withSelect from 'wc-api/with-select'; import './style.scss'; import { recordEvent } from 'lib/tracks'; +const { performanceIndicators: indicators } = getSetting( 'dataEndpoints', { + performanceIndicators: '', +} ); + class StorePerformance extends Component { renderMenu() { const { hiddenBlocks, - indicators, isFirst, isLast, onMove, @@ -193,8 +197,6 @@ export default compose( const datesFromQuery = getCurrentDates( query ); const endPrimary = datesFromQuery.primary.before; const endSecondary = datesFromQuery.secondary.before; - - const indicators = wcSettings.dataEndpoints.performanceIndicators; const userIndicators = indicators.filter( indicator => ! hiddenBlocks.includes( indicator.stat ) ); diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/index.js b/plugins/woocommerce-admin/client/dashboard/task-list/index.js index d72ceabfa23..0698303c3d0 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/index.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/index.js @@ -12,6 +12,7 @@ import { compose } from '@wordpress/compose'; */ import { Card, List } from '@woocommerce/components'; import { updateQueryString } from '@woocommerce/navigation'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal depdencies @@ -25,6 +26,13 @@ import Tax from './tasks/tax'; import Payments from './tasks/payments'; import withSelect from 'wc-api/with-select'; +const { customLogo, hasHomepage, hasProducts, shippingZonesCount } = getSetting( 'onboarding', { + customLogo: '', + hasHomePage: false, + hasProducts: false, + shippingZonesCount: 0, +} ); + class TaskDashboard extends Component { componentDidMount() { document.body.classList.add( 'woocommerce-onboarding' ); @@ -37,7 +45,6 @@ class TaskDashboard extends Component { } getTasks() { - const { customLogo, hasHomepage, hasProducts, shippingZonesCount } = wcSettings.onboarding; const { profileItems, query } = this.props; return [ diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js index f87e0122a3a..800cd8f7d21 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js @@ -8,7 +8,6 @@ import { Button, TextControl } from 'newspack-components'; import classnames from 'classnames'; import { Component, Fragment } from '@wordpress/element'; import { FormToggle } from '@wordpress/components'; -import { get } from 'lodash'; import PropTypes from 'prop-types'; /** @@ -16,6 +15,9 @@ import PropTypes from 'prop-types'; */ import { Flag, Form } from '@woocommerce/components'; import { getCurrencyFormatString } from '@woocommerce/currency'; +import { CURRENCY } from '@woocommerce/wc-admin-settings'; + +const { symbol, symbolPosition } = CURRENCY; class ShippingRates extends Component { constructor() { @@ -81,26 +83,15 @@ class ShippingRates extends Component { } renderInputPrefix() { - const symbolPosition = get( wcSettings, [ 'currency', 'position' ] ); if ( 0 === symbolPosition.indexOf( 'right' ) ) { return; } - - return ( - - { get( wcSettings, [ 'currency', 'symbol' ], '$' ) } - - ); + return { symbol }; } renderInputSuffix( rate ) { - const symbolPosition = get( wcSettings, [ 'currency', 'position' ] ); if ( 0 === symbolPosition.indexOf( 'right' ) ) { - return ( - - { get( wcSettings, [ 'currency', 'symbol' ], '$' ) } - - ); + return { symbol }; } return parseFloat( rate ) === parseFloat( 0 ) ? ( diff --git a/plugins/woocommerce-admin/client/devdocs/example.js b/plugins/woocommerce-admin/client/devdocs/example.js index 345794c3731..b225314562a 100755 --- a/plugins/woocommerce-admin/client/devdocs/example.js +++ b/plugins/woocommerce-admin/client/devdocs/example.js @@ -20,6 +20,7 @@ class Example extends Component { try { exampleComponent = require( `components/src/${ this.props.filePath }/docs/example` ); } catch ( e ) { + // eslint-disable-next-line no-console console.error( e ); } diff --git a/plugins/woocommerce-admin/client/header/activity-panel/index.js b/plugins/woocommerce-admin/client/header/activity-panel/index.js index 2019357d836..45854755688 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/index.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/index.js @@ -9,6 +9,7 @@ import { Component } from '@wordpress/element'; import Gridicon from 'gridicons'; import { IconButton, NavigableMenu } from '@wordpress/components'; import { partial, uniqueId, find } from 'lodash'; +import { getSetting } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -30,6 +31,9 @@ import ReviewsPanel from './panels/reviews'; import withSelect from 'wc-api/with-select'; import WordPressNotices from './wordpress-notices'; +const manageStock = getSetting( 'manageStock', 'no' ); +const reviewsEnabled = getSetting( 'reviewsEnabled', 'no' ); + class ActivityPanel extends Component { constructor() { super( ...arguments ); @@ -128,7 +132,7 @@ class ActivityPanel extends Component { icon: , unread: hasUnreadOrders, }, - 'yes' === wcSettings.manageStock + 'yes' === manageStock ? { name: 'stock', title: __( 'Stock', 'woocommerce-admin' ), @@ -136,7 +140,7 @@ class ActivityPanel extends Component { unread: hasUnreadStock, } : null, - 'yes' === wcSettings.reviewsEnabled + 'yes' === reviewsEnabled ? { name: 'reviews', title: __( 'Reviews', 'woocommerce-admin' ), diff --git a/plugins/woocommerce-admin/client/header/activity-panel/panels/inbox.js b/plugins/woocommerce-admin/client/header/activity-panel/panels/inbox.js index e3e9c70172b..d522678e145 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/panels/inbox.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/panels/inbox.js @@ -9,6 +9,11 @@ import { compose } from '@wordpress/compose'; import Gridicon from 'gridicons'; import { withDispatch } from '@wordpress/data'; +/** + * WooCommerce dependencies + */ +import { ADMIN_URL as adminUrl } from '@woocommerce/wc-admin-settings'; + /** * Internal dependencies */ @@ -37,7 +42,7 @@ class InboxPanel extends Component { const { triggerNoteAction } = this.props; const href = event.target.href || ''; - if ( href.length && ! href.startsWith( wcSettings.adminUrl ) ) { + if ( href.length && ! href.startsWith( adminUrl ) ) { event.preventDefault(); window.open( href, '_blank' ); } diff --git a/plugins/woocommerce-admin/client/header/activity-panel/panels/orders.js b/plugins/woocommerce-admin/client/header/activity-panel/panels/orders.js index 5adb0040ca5..6d6798148e6 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/panels/orders.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/panels/orders.js @@ -266,6 +266,7 @@ export default compose( getReportItemsError, isReportItemsRequesting, } = select( 'wc-api' ); + wcSettings.wcAdminSettings = wcSettings.wcAdminSettings || {}; const orderStatuses = wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses || DEFAULT_ACTIONABLE_STATUSES; diff --git a/plugins/woocommerce-admin/client/header/activity-panel/unread-indicators.js b/plugins/woocommerce-admin/client/header/activity-panel/unread-indicators.js index 80a1dfc2338..cf01e946639 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/unread-indicators.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/unread-indicators.js @@ -8,6 +8,9 @@ import { DEFAULT_ACTIONABLE_STATUSES } from 'wc-api/constants'; export function getUnreadNotes( select ) { const { getCurrentUserData, getNotes, getNotesError, isGetNotesRequesting } = select( 'wc-api' ); const userData = getCurrentUserData(); + if ( ! userData ) { + return null; + } const notesQuery = { page: 1, per_page: 1, @@ -33,6 +36,7 @@ export function getUnreadNotes( select ) { export function getUnreadOrders( select ) { const { getItems, getItemsTotalCount, getItemsError, isGetItemsRequesting } = select( 'wc-api' ); + wcSettings.wcAdminSettings = wcSettings.wcAdminSettings || {}; const orderStatuses = wcSettings.wcAdminSettings.woocommerce_actionable_order_statuses || DEFAULT_ACTIONABLE_STATUSES; diff --git a/plugins/woocommerce-admin/client/settings/fallbacks.js b/plugins/woocommerce-admin/client/settings/fallbacks.js new file mode 100644 index 00000000000..eced7ebfc34 --- /dev/null +++ b/plugins/woocommerce-admin/client/settings/fallbacks.js @@ -0,0 +1,92 @@ +/** @format */ + +const defaults = { + adminUrl: '', + countries: [], + currency: { + code: 'USD', + precision: 2, + symbol: '$', + symbolPosition: 'left', + decimalSeparator: '.', + priceFormat: '%1$s%2$s', + thousandSeparator: ',', + }, + defaultDateRange: 'period=month&compare=previous_year', + locale: { + siteLocale: 'en_US', + userLocale: 'en_US', + weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], + }, + orderStatuses: [], + siteTitle: '', + wcAssetUrl: '', +}; + +const globalSharedSettings = typeof wcSettings === 'object' ? wcSettings : {}; + +// Use defaults or global settings, depending on what is set. +const allSettings = { + ...defaults, + ...globalSharedSettings, +}; + +allSettings.currency = { + ...defaults.currency, + ...allSettings.currency, +}; + +allSettings.locale = { + ...defaults.locale, + ...allSettings.locale, +}; + +// for anything you want exposed as non-mutable outside of its use in a module, +// import the constant. Otherwise use getSetting/setSetting for the value +// reference. +export const ADMIN_URL = allSettings.adminUrl; +export const COUNTRIES = allSettings.countries; +export const CURRENCY = allSettings.currency; +export const LOCALE = allSettings.locale; +export const ORDER_STATUSES = allSettings.orderStatuses; +export const SITE_TITLE = allSettings.siteTitle; +export const WC_ASSET_URL = allSettings.wcAssetUrl; +export const DEFAULT_DATE_RANGE = allSettings.defaultDateRange; + +/** + * Retrieves a setting value from the setting state. + * + * @export + * @param {string} name The identifier for the setting. + * @param {mixed} [fallback=false] The value to use as a fallback + * if the setting is not in the + * state. + * @param {function} [filter=( val ) => val] A callback for filtering the + * value before it's returned. + * Receives both the found value + * (if it exists for the key) and + * the provided fallback arg. + * + * @returns {mixed} The value present in the settings state for the given + * name. + */ +export function getSetting( name, fallback = false, filter = val => val ) { + const value = allSettings.hasOwnProperty( name ) ? allSettings[ name ] : fallback; + return filter( value, fallback ); +} + +/** + * Sets a value to a property on the settings state. + * + * @export + * @param {string} name The setting property key for the + * setting being mutated. + * @param {mixed} value The value to set. + * @param {function} [filter=( val ) => val] Allows for providing a callback + * to sanitize the setting (eg. + * ensure it's a number) + */ +export function setSetting( name, value, filter = val => val ) { + value = filter( value ); + allSettings[ name ] = filter( value ); +} diff --git a/plugins/woocommerce-admin/client/settings/index.js b/plugins/woocommerce-admin/client/settings/index.js new file mode 100644 index 00000000000..5f6fd83daae --- /dev/null +++ b/plugins/woocommerce-admin/client/settings/index.js @@ -0,0 +1,28 @@ +/** + * External dependencies + * + * @format + */ + +import * as SHARED from '@woocommerce/settings'; + +/** + * Internal dependencies + */ +import * as FALLBACKS from './fallbacks'; + +// If `getSetting` is not set, then it was not available so let's do +// defaults. +const SOURCE = ! SHARED || typeof SHARED.getSetting === 'undefined' ? FALLBACKS : SHARED; + +export const ADMIN_URL = SOURCE.ADMIN_URL; +export const COUNTRIES = SOURCE.COUNTRIES; +export const CURRENCY = SOURCE.CURRENCY; +export const LOCALE = SOURCE.LOCALE; +export const ORDER_STATUSES = SOURCE.ORDER_STATUSES; +export const SITE_TITLE = SOURCE.SITE_TITLE; +export const WC_ASSET_URL = SOURCE.WC_ASSET_URL; +export const DEFAULT_DATE_RANGE = SOURCE.DEFAULT_DATE_RANGE; + +export const getSetting = SOURCE.getSetting; +export const setSetting = SOURCE.setSetting; diff --git a/plugins/woocommerce-admin/packages/components/src/advanced-filters/docs/example.js b/plugins/woocommerce-admin/packages/components/src/advanced-filters/docs/example.js index ec408d5b1d8..4a194e13044 100644 --- a/plugins/woocommerce-admin/packages/components/src/advanced-filters/docs/example.js +++ b/plugins/woocommerce-admin/packages/components/src/advanced-filters/docs/example.js @@ -3,7 +3,7 @@ * Internal dependencies */ import { AdvancedFilters } from '@woocommerce/components'; -const { orderStatuses } = wcSettings; +const { ORDER_STATUSES } = '@woocommerce/wc-admin-settings'; const path = ( new URL( document.location ) ).searchParams.get( 'path' ) || '/devdocs'; const query = { @@ -33,9 +33,9 @@ const advancedFilters = { ], input: { component: 'SelectControl', - options: Object.keys( orderStatuses ).map( key => ( { + options: Object.keys( ORDER_STATUSES ).map( key => ( { value: key, - label: orderStatuses[ key ], + label: ORDER_STATUSES[ key ], } ) ), }, }, diff --git a/plugins/woocommerce-admin/packages/components/src/advanced-filters/index.js b/plugins/woocommerce-admin/packages/components/src/advanced-filters/index.js index 70b6b919b36..94be0830244 100644 --- a/plugins/woocommerce-admin/packages/components/src/advanced-filters/index.js +++ b/plugins/woocommerce-admin/packages/components/src/advanced-filters/index.js @@ -21,6 +21,7 @@ import { getQueryFromActiveFilters, getHistory, } from '@woocommerce/navigation'; +import { LOCALE } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -37,6 +38,8 @@ const matches = [ { value: 'any', label: __( 'Any', 'woocommerce-admin' ) }, ]; +const { siteLocale } = LOCALE; + /** * Displays a configurable set of filters which can modify query parameters. */ @@ -186,7 +189,6 @@ class AdvancedFilters extends Component { } isEnglish() { - const { siteLocale } = wcSettings; return /en-/.test( siteLocale ); } diff --git a/plugins/woocommerce-admin/packages/components/src/advanced-filters/number-filter.js b/plugins/woocommerce-admin/packages/components/src/advanced-filters/number-filter.js index c4002fda2a6..84d0cf54431 100644 --- a/plugins/woocommerce-admin/packages/components/src/advanced-filters/number-filter.js +++ b/plugins/woocommerce-admin/packages/components/src/advanced-filters/number-filter.js @@ -19,6 +19,7 @@ import { textContent } from './utils'; * WooCommerce dependencies */ import { formatCurrency } from '@woocommerce/currency'; +import { CURRENCY } from '@woocommerce/wc-admin-settings'; class NumberFilter extends Component { getBetweenString() { @@ -71,8 +72,7 @@ class NumberFilter extends Component { getFormControl( { type, value, label, onChange } ) { if ( 'currency' === type ) { - const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ] ); - const symbolPosition = get( wcSettings, [ 'currency', 'position' ] ); + const { symbol: currencySymbol, symbolPosition } = CURRENCY; return ( 0 === symbolPosition.indexOf( 'right' ) diff --git a/plugins/woocommerce-admin/packages/components/src/chart/index.js b/plugins/woocommerce-admin/packages/components/src/chart/index.js index fdafbd68907..66d95585e6f 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/index.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/index.js @@ -6,7 +6,7 @@ import { __, sprintf } from '@wordpress/i18n'; import classNames from 'classnames'; import { Component, createRef, Fragment } from '@wordpress/element'; import { formatDefaultLocale as d3FormatDefaultLocale } from 'd3-format'; -import { get, isEqual, partial, without } from 'lodash'; +import { isEqual, partial, without } from 'lodash'; import Gridicon from 'gridicons'; import { IconButton, NavigableMenu, SelectControl } from '@wordpress/components'; import { interpolateViridis as d3InterpolateViridis } from 'd3-scale-chromatic'; @@ -18,6 +18,7 @@ import { withViewportMatch } from '@wordpress/viewport'; * WooCommerce dependencies */ import { getIdsFromQuery, updateQueryString } from '@woocommerce/navigation'; +import { CURRENCY } from '@woocommerce/wc-admin-settings'; /** * Internal dependencies @@ -42,12 +43,16 @@ function getD3CurrencyFormat( symbol, position ) { } } -const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ], '' ); -const symbolPosition = get( wcSettings, [ 'currency', 'position' ], 'left' ); +const { + symbol: currencySymbol, + symbolPosition, + decimalSeparator: decimal, + thousandSeparator: thousands, +} = CURRENCY; d3FormatDefaultLocale( { - decimal: get( wcSettings, [ 'currency', 'decimal_separator' ], '.' ), - thousands: get( wcSettings, [ 'currency', 'thousand_separator' ], ',' ), + decimal, + thousands, grouping: [ 3 ], currency: getD3CurrencyFormat( currencySymbol, symbolPosition ), } ); diff --git a/plugins/woocommerce-admin/packages/components/src/filters/docs/example.js b/plugins/woocommerce-admin/packages/components/src/filters/docs/example.js index 3d8f27e28fe..a176f778ade 100644 --- a/plugins/woocommerce-admin/packages/components/src/filters/docs/example.js +++ b/plugins/woocommerce-admin/packages/components/src/filters/docs/example.js @@ -9,7 +9,7 @@ import { ReportFilters, Section, } from '@woocommerce/components'; -const { orderStatuses } = wcSettings; +const { ORDER_STATUSES } = '@woocommerce/wc-admin-settings'; const path = ''; const query = {}; @@ -49,9 +49,9 @@ const advancedFilters = { ], input: { component: 'SelectControl', - options: Object.keys( orderStatuses ).map( key => ( { + options: Object.keys( ORDER_STATUSES ).map( key => ( { value: key, - label: orderStatuses[ key ], + label: ORDER_STATUSES[ key ], } ) ), }, }, diff --git a/plugins/woocommerce-admin/packages/currency/src/index.js b/plugins/woocommerce-admin/packages/currency/src/index.js index a2b59a8673e..62b4efc765b 100644 --- a/plugins/woocommerce-admin/packages/currency/src/index.js +++ b/plugins/woocommerce-admin/packages/currency/src/index.js @@ -2,13 +2,14 @@ /** * External dependencies */ -import { get, isNaN } from 'lodash'; +import { isNaN } from 'lodash'; import { sprintf } from '@wordpress/i18n'; /** * WooCommerce dependencies */ import { numberFormat } from '@woocommerce/number'; +import { CURRENCY as currency } from '@woocommerce/wc-admin-settings'; /** * Formats money with a given currency code. Uses site's currency settings for formatting. @@ -18,14 +19,13 @@ import { numberFormat } from '@woocommerce/number'; * @returns {?String} A formatted string. */ export function formatCurrency( number, currencySymbol ) { - // default to wcSettings (and then to $) if currency symbol is not passed in if ( ! currencySymbol ) { - currencySymbol = get( wcSettings, [ 'currency', 'symbol' ], '$' ); + currencySymbol = currency.symbol; } - const precision = get( wcSettings, [ 'currency', 'precision' ], 2 ); + const precision = currency.precision; const formattedNumber = numberFormat( number, precision ); - const priceFormat = get( wcSettings, [ 'currency', 'price_format' ], '%1$s%2$s' ); + const priceFormat = currency.priceFormat; if ( '' === formattedNumber ) { return formattedNumber; @@ -42,7 +42,7 @@ export function formatCurrency( number, currencySymbol ) { * @return {Number} The original number rounded to a decimal point */ export function getCurrencyFormatDecimal( number ) { - const { precision = 2 } = wcSettings.currency; + const { precision = 2 } = currency; if ( 'number' !== typeof number ) { number = parseFloat( number ); } @@ -60,7 +60,7 @@ export function getCurrencyFormatDecimal( number ) { * @return {String} The original number rounded to a decimal point */ export function getCurrencyFormatString( number ) { - const { precision = 2 } = wcSettings.currency; + const { precision = 2 } = currency; if ( 'number' !== typeof number ) { number = parseFloat( number ); } diff --git a/plugins/woocommerce-admin/packages/currency/test/index.js b/plugins/woocommerce-admin/packages/currency/test/index.js index 95ca86ef52c..5a48a3bfe27 100644 --- a/plugins/woocommerce-admin/packages/currency/test/index.js +++ b/plugins/woocommerce-admin/packages/currency/test/index.js @@ -2,21 +2,52 @@ /** * Internal dependencies */ -import { formatCurrency, getCurrencyFormatDecimal, getCurrencyFormatString } from '../src'; +import { + formatCurrency, + getCurrencyFormatDecimal, + getCurrencyFormatString, +} from '../src'; + +/** + * WooCommerce dependencies + * Note: setCurrencyProp doesn't exist on the module alias, it's used for mocking + * values. + */ +import { setCurrencyProp, resetMock } from '@woocommerce/wc-admin-settings'; + +beforeEach( () => { + resetMock(); +} ); + +jest.mock( '@woocommerce/wc-admin-settings', () => { + let mockedCurrency = jest.requireActual( '@woocommerce/wc-admin-settings' ).CURRENCY; + const originalCurrency = { + ...mockedCurrency, + }; + const reset = () => { + mockedCurrency = Object.assign( mockedCurrency, originalCurrency ); + }; + return { + setCurrencyProp: ( prop, value ) => { + mockedCurrency[ prop ] = value; + }, + resetMock: reset, + CURRENCY: mockedCurrency, + }; +} ); describe( 'formatCurrency', () => { - it( 'should default to wcSettings or USD when currency not passed in', () => { + it( 'should use defaults (USD) when currency not passed in', () => { expect( formatCurrency( 9.99 ) ).toBe( '$9.99' ); expect( formatCurrency( 30 ) ).toBe( '$30.00' ); } ); it( 'should uses store currency settings, not locale-based', () => { - global.wcSettings.currency.code = 'JPY'; - global.wcSettings.currency.precision = 3; - global.wcSettings.currency.decimal_separator = ','; - global.wcSettings.currency.thousand_separator = '.'; - global.wcSettings.currency.price_format = '%2$s%1$s'; - + setCurrencyProp( 'code', 'JPY' ); + setCurrencyProp( 'precision', 3 ); + setCurrencyProp( 'priceFormat', '%2$s%1$s' ); + setCurrencyProp( 'thousandSeparator', '.' ); + setCurrencyProp( 'decimalSeparator', ',' ); expect( formatCurrency( 9.49258, '¥' ) ).toBe( '9,493¥' ); expect( formatCurrency( 3000, '¥' ) ).toBe( '3.000,000¥' ); expect( formatCurrency( 3.0002, '¥' ) ).toBe( '3,000¥' ); @@ -31,31 +62,23 @@ describe( 'formatCurrency', () => { describe( 'getCurrencyFormatDecimal', () => { it( 'should round a number to 2 decimal places in USD', () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatDecimal( 9.49258 ) ).toBe( 9.49 ); expect( getCurrencyFormatDecimal( 30 ) ).toBe( 30 ); expect( getCurrencyFormatDecimal( 3.0002 ) ).toBe( 3 ); } ); it( 'should round a number to 0 decimal places in JPY', () => { - global.wcSettings.currency.precision = 0; + setCurrencyProp( 'precision', 0 ); expect( getCurrencyFormatDecimal( 1239.88 ) ).toBe( 1240 ); expect( getCurrencyFormatDecimal( 1500 ) ).toBe( 1500 ); expect( getCurrencyFormatDecimal( 33715.02 ) ).toBe( 33715 ); } ); it( 'should correctly convert and round a string', () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatDecimal( '19.80' ) ).toBe( 19.8 ); } ); - it( 'should default to a precision of 2 if none set', () => { - delete global.wcSettings.currency.precision; - expect( getCurrencyFormatDecimal( 59.282 ) ).toBe( 59.28 ); - } ); - it( "should return 0 when given an input that isn't a number", () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatDecimal( 'abc' ) ).toBe( 0 ); expect( getCurrencyFormatDecimal( false ) ).toBe( 0 ); expect( getCurrencyFormatDecimal( null ) ).toBe( 0 ); @@ -64,31 +87,23 @@ describe( 'getCurrencyFormatDecimal', () => { describe( 'getCurrencyFormatString', () => { it( 'should round a number to 2 decimal places in USD', () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatString( 9.49258 ) ).toBe( '9.49' ); expect( getCurrencyFormatString( 30 ) ).toBe( '30.00' ); expect( getCurrencyFormatString( 3.0002 ) ).toBe( '3.00' ); } ); it( 'should round a number to 0 decimal places in JPY', () => { - global.wcSettings.currency.precision = 0; + setCurrencyProp( 'precision', 0 ); expect( getCurrencyFormatString( 1239.88 ) ).toBe( '1240' ); expect( getCurrencyFormatString( 1500 ) ).toBe( '1500' ); expect( getCurrencyFormatString( 33715.02 ) ).toBe( '33715' ); } ); it( 'should correctly convert and round a string', () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatString( '19.80' ) ).toBe( '19.80' ); } ); - it( 'should default to a precision of 2 if none set', () => { - delete global.wcSettings.currency.precision; - expect( getCurrencyFormatString( '59.282' ) ).toBe( '59.28' ); - } ); - it( "should return empty string when given an input that isn't a number", () => { - global.wcSettings.currency.precision = 2; expect( getCurrencyFormatString( 'abc' ) ).toBe( '' ); expect( getCurrencyFormatString( false ) ).toBe( '' ); expect( getCurrencyFormatString( null ) ).toBe( '' ); diff --git a/plugins/woocommerce-admin/packages/date/src/index.js b/plugins/woocommerce-admin/packages/date/src/index.js index 8001480b5e6..5e652f1d94f 100644 --- a/plugins/woocommerce-admin/packages/date/src/index.js +++ b/plugins/woocommerce-admin/packages/date/src/index.js @@ -7,12 +7,17 @@ import { find } from 'lodash'; import { __ } from '@wordpress/i18n'; import { parse } from 'qs'; +/** + * WooCommerce dependencies + */ +import { getSetting } from '@woocommerce/wc-admin-settings'; + export const isoDateFormat = 'YYYY-MM-DD'; /** * DateValue Object * - * @typedef {Object} DateValue - Describes the date range supplied by the date picker. + * @typedef {Object} DateValue - Describes the date range supplied by the date picker. * @property {string} label - The translated value of the period. * @property {string} range - The human readable value of a date range. * @property {moment.Moment} after - Start of the date range. @@ -273,7 +278,7 @@ export const getDateParamsFromQuery = ( { period, compare, after, before } ) => before: before ? moment( before ) : null, }; } - + wcSettings.wcAdminSettings = wcSettings.wcAdminSettings || {}; const defaultDateRange = wcSettings.wcAdminSettings.woocommerce_default_date_range || 'period=month&compare=previous_year'; @@ -519,7 +524,7 @@ export function getDateFormatsForInterval( interval, ticks = 0 ) { * of moment style js formats. */ export function loadLocaleData() { - const { userLocale, weekdaysShort } = wcSettings.l10n; + const { userLocale, weekdaysShort } = getSetting( 'locale' ); // Don't update if the wp locale hasn't been set yet, like in unit tests, for instance. if ( 'en' !== moment.locale() ) { moment.updateLocale( userLocale, { diff --git a/plugins/woocommerce-admin/packages/date/test/index.js b/plugins/woocommerce-admin/packages/date/test/index.js index b7948b20d3f..48bba549a0d 100644 --- a/plugins/woocommerce-admin/packages/date/test/index.js +++ b/plugins/woocommerce-admin/packages/date/test/index.js @@ -4,6 +4,14 @@ */ import moment from 'moment'; +/** + * WooCommerce settings + */ +import { + setSetting, + getSetting, +} from '@woocommerce/wc-admin-settings'; + /** * Internal dependencies */ @@ -506,25 +514,27 @@ describe( 'getRangeLabel', () => { } ); describe( 'loadLocaleData', () => { + const originalLocale = getSetting( 'locale' ); beforeEach( () => { // Reset to default settings - wcSettings.l10n = { - userLocale: 'en_US', - weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], - }; + setSetting( 'locale', originalLocale ); } ); it( 'should load locale data on user locale', () => { - wcSettings.l10n = { - userLocale: 'fr_FR', - weekdaysShort: [ 'dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam' ], - }; + setSetting( + 'locale', + { + userLocale: 'fr_FR', + weekdaysShort: [ 'dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam' ], + } + ); // initialize locale. Gutenberg normaly does this, but not in test environment. moment.locale( 'fr_FR', {} ); loadLocaleData(); - expect( moment.localeData().weekdaysMin() ).toEqual( wcSettings.l10n.weekdaysShort ); + expect( moment.localeData().weekdaysMin() ) + .toEqual( getSetting( 'locale' ).weekdaysShort ); } ); } ); diff --git a/plugins/woocommerce-admin/packages/number/src/index.js b/plugins/woocommerce-admin/packages/number/src/index.js index e376224a6f8..b6247a81813 100644 --- a/plugins/woocommerce-admin/packages/number/src/index.js +++ b/plugins/woocommerce-admin/packages/number/src/index.js @@ -2,7 +2,13 @@ /** * External dependencies */ -import { get, isFinite } from 'lodash'; +import { isFinite } from 'lodash'; + +/** + * WooCommerce dependencies + */ +import { CURRENCY } from '@woocommerce/wc-admin-settings'; + const number_format = require( 'locutus/php/strings/number_format' ); /** @@ -22,8 +28,10 @@ export function numberFormat( number, precision = null ) { return ''; } - const decimalSeparator = get( wcSettings, [ 'currency', 'decimal_separator' ], '.' ); - const thousandSeparator = get( wcSettings, [ 'currency', 'thousand_separator' ], ',' ); + const { + decimalSeparator, + thousandSeparator, + } = CURRENCY; precision = parseInt( precision ); if ( isNaN( precision ) ) { diff --git a/plugins/woocommerce-admin/packages/number/src/test/index.js b/plugins/woocommerce-admin/packages/number/src/test/index.js index 83df3f32f06..ce99d4efe3f 100644 --- a/plugins/woocommerce-admin/packages/number/src/test/index.js +++ b/plugins/woocommerce-admin/packages/number/src/test/index.js @@ -4,7 +4,34 @@ */ import { numberFormat } from '../index'; +/** + * WooCommerce dependencies + * Note: setCurrencyProp doesn't exist on the module alias, it's used for mocking + * values. + */ +import { setCurrencyProp, resetMock } from '@woocommerce/wc-admin-settings'; + +jest.mock( '@woocommerce/wc-admin-settings', () => { + let mockedCurrency = jest.requireActual( '@woocommerce/wc-admin-settings' ).CURRENCY; + const originalCurrency = { + ...mockedCurrency, + }; + const reset = () => { + mockedCurrency = Object.assign( mockedCurrency, originalCurrency ); + }; + return { + setCurrencyProp: ( prop, value ) => { + mockedCurrency[ prop ] = value; + }, + resetMock: reset, + CURRENCY: mockedCurrency, + }; +} ); + describe( 'numberFormat', () => { + beforeEach( () => { + resetMock(); + } ); it( 'should default to precision=null decimal=. thousands=,', () => { expect( numberFormat( 1000 ) ).toBe( '1,000' ); } ); @@ -30,10 +57,8 @@ describe( 'numberFormat', () => { } ); it( 'uses store currency settings, not locale', () => { - global.wcSettings.siteLocale = 'en-US'; - global.wcSettings.currency.decimal_separator = ','; - global.wcSettings.currency.thousand_separator = '.'; - + setCurrencyProp( 'decimalSeparator', ',' ); + setCurrencyProp( 'thousandSeparator', '.' ); expect( numberFormat( '12345.6789', 3 ) ).toBe( '12.345,679' ); } ); } ); diff --git a/plugins/woocommerce-admin/src/Features/ActivityPanels.php b/plugins/woocommerce-admin/src/Features/ActivityPanels.php index 2e4ddaad8fc..c065d32e0be 100644 --- a/plugins/woocommerce-admin/src/Features/ActivityPanels.php +++ b/plugins/woocommerce-admin/src/Features/ActivityPanels.php @@ -37,7 +37,10 @@ class ActivityPanels { */ public function __construct() { add_filter( 'wc_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) ); - add_action( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 ); // Run after Automattic\WooCommerce\Admin\Loader. + // Run after Automattic\WooCommerce\Admin\Loader. + add_filter( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 ); + // new settings injection + add_filter( 'woocommerce_shared_settings', array( $this, 'component_settings' ), 20 ); add_action( 'woocommerce_updated', array( $this, 'woocommerce_updated_note' ) ); } diff --git a/plugins/woocommerce-admin/src/Features/Onboarding.php b/plugins/woocommerce-admin/src/Features/Onboarding.php index 9150c057a22..708129bdf8c 100644 --- a/plugins/woocommerce-admin/src/Features/Onboarding.php +++ b/plugins/woocommerce-admin/src/Features/Onboarding.php @@ -57,8 +57,11 @@ class Onboarding { if ( $this->should_show_tasks() ) { OnboardingTasks::get_instance(); } - - add_action( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 ); // Run after Automattic\WooCommerce\Admin\Loader. + // old settings injection + // Run after Automattic\WooCommerce\Admin\Loader. + add_filter( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 ); + // new settings injection + add_filter( 'woocommerce_shared_settings', array( $this, 'component_settings' ), 20 ); add_filter( 'woocommerce_component_settings_preload_endpoints', array( $this, 'add_preload_endpoints' ) ); add_action( 'woocommerce_theme_installed', array( $this, 'delete_themes_transient' ) ); add_action( 'after_switch_theme', array( $this, 'delete_themes_transient' ) ); diff --git a/plugins/woocommerce-admin/src/Features/OnboardingTasks.php b/plugins/woocommerce-admin/src/Features/OnboardingTasks.php index 6dde4b0700a..07dca7cca44 100644 --- a/plugins/woocommerce-admin/src/Features/OnboardingTasks.php +++ b/plugins/woocommerce-admin/src/Features/OnboardingTasks.php @@ -41,7 +41,11 @@ class OnboardingTasks { */ public function __construct() { add_action( 'admin_enqueue_scripts', array( $this, 'add_media_scripts' ) ); - add_action( 'woocommerce_components_settings', array( $this, 'component_settings' ), 30 ); // Run after Onboarding. + // old settings injection + // Run after Onboarding. + add_filter( 'woocommerce_components_settings', array( $this, 'component_settings' ), 30 ); + // new settings injection + add_filter( 'woocommerce_shared_settings', array( $this, 'component_settings' ), 30 ); add_action( 'admin_init', array( $this, 'set_active_task' ), 20 ); add_action( 'current_screen', array( $this, 'check_active_task_completion' ), 1000 ); } diff --git a/plugins/woocommerce-admin/src/Loader.php b/plugins/woocommerce-admin/src/Loader.php index 12e1e6e5ed3..abecdac8dd0 100644 --- a/plugins/woocommerce-admin/src/Loader.php +++ b/plugins/woocommerce-admin/src/Loader.php @@ -8,6 +8,8 @@ namespace Automattic\WooCommerce\Admin; +use \_WP_Dependency; + /** * Loader Class. */ @@ -47,9 +49,13 @@ class Loader { */ public function __construct() { add_action( 'init', array( __CLASS__, 'load_features' ) ); - add_action( 'admin_enqueue_scripts', array( __CLASS__, 'register_scripts' ) ); +; add_action( 'admin_enqueue_scripts', array( __CLASS__, 'register_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'inject_wc_settings_dependencies' ), 14 ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_scripts' ), 15 ); - add_action( 'woocommerce_components_settings', array( __CLASS__, 'add_component_settings' ) ); + // old settings injection + add_filter( 'woocommerce_components_settings', array( __CLASS__, 'add_component_settings' ) ); + // new settings injection + add_filter( 'woocommerce_shared_settings', array( __CLASS__, 'add_component_settings' ) ); add_filter( 'admin_body_class', array( __CLASS__, 'add_admin_body_classes' ) ); add_action( 'admin_menu', array( __CLASS__, 'register_page_handler' ) ); add_filter( 'admin_title', array( __CLASS__, 'update_admin_title' ) ); @@ -518,6 +524,23 @@ class Loader { * @return array Array of component settings. */ public static function add_component_settings( $settings ) { + if ( ! function_exists( 'wc_blocks_container' ) ) { + global $wp_locale; + // inject data not available via older versions of wc_blocks/woo. + $settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() ); + $settings['currency'] = self::get_currency_settings(); + $settings['locale'] = [ + 'siteLocale' => isset( $settings['siteLocale'] ) + ? $settings['siteLocale'] + : get_locale(), + 'userLocale' => isset( $settings['l10n']['userLocale'] ) + ? $settings['l10n']['userLocale'] + : get_user_locale(), + 'weekdaysShort' => isset( $settings['l10n']['weekdaysShort'] ) + ? $settings['l10n']['weekdaysShort'] + : array_values( $wp_locale->weekday_abbrev ) + ]; + } $preload_data_endpoints = apply_filters( 'woocommerce_component_settings_preload_endpoints', array( '/wc/v3' ) ); if ( ! empty( $preload_data_endpoints ) ) { $preload_data = array_reduce( @@ -530,10 +553,7 @@ class Loader { foreach ( self::get_user_data_fields() as $user_field ) { $current_user_data[ $user_field ] = json_decode( get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true ) ); } - - $settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() ); $settings['currentUserData'] = $current_user_data; - $settings['currency'] = self::get_currency_settings(); $settings['reviewsEnabled'] = get_option( 'woocommerce_enable_reviews' ); $settings['manageStock'] = get_option( 'woocommerce_manage_stock' ); $settings['commentModeration'] = get_option( 'comment_moderation' ); @@ -543,6 +563,9 @@ class Loader { $settings['wcAdminAssetUrl'] = plugins_url( 'images/', plugin_dir_path( dirname( __DIR__ ) ) . 'woocommerce-admin.php' ); if ( ! empty( $preload_data_endpoints ) ) { + $settings['dataEndpoints'] = isset( $settings['dataEndpoints'] ) + ? $settings['dataEndpoints'] + : []; foreach ( $preload_data_endpoints as $key => $endpoint ) { // Handle error case: rest_do_request() doesn't guarantee success. if ( empty( $preload_data[ $endpoint ] ) ) { @@ -553,11 +576,9 @@ class Loader { } } $settings = self::get_custom_settings( $settings ); - if ( self::is_embed_page() ) { $settings['embedBreadcrumbs'] = self::get_embed_breadcrumbs(); } - return $settings; } @@ -664,13 +685,13 @@ class Loader { return apply_filters( 'wc_currency_settings', array( - 'code' => $code, - 'precision' => wc_get_price_decimals(), - 'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $code ) ), - 'position' => get_option( 'woocommerce_currency_pos' ), - 'decimal_separator' => wc_get_price_decimal_separator(), - 'thousand_separator' => wc_get_price_thousand_separator(), - 'price_format' => html_entity_decode( get_woocommerce_price_format() ), + 'code' => $code, + 'precision' => wc_get_price_decimals(), + 'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $code ) ), + 'symbolPosition' => get_option( 'woocommerce_currency_pos' ), + 'decimalSeparator' => wc_get_price_decimal_separator(), + 'thousandSeparator' => wc_get_price_thousand_separator(), + 'priceFormat' => html_entity_decode( get_woocommerce_price_format() ), ) ); } @@ -737,4 +758,26 @@ class Loader { public static function get_user_data_fields() { return apply_filters( 'wc_admin_get_user_data_fields', array() ); } + + /** + * Injects wp-shared-settings as a dependency if it's present. + */ + public static function inject_wc_settings_dependencies() { + if ( wp_script_is( 'wc-settings', 'registered' ) ) { + $handles_for_injection = [ + 'wc-csv', + 'wc-currency', + 'wc-navigation', + 'wc-number', + 'wc-date', + 'wc-components', + ]; + foreach( $handles_for_injection as $handle ) { + $script = wp_scripts()->query( $handle, 'registered' ); + if ( $script instanceof _WP_Dependency ) { + $script->deps[] = 'wc-settings'; + } + } + } + } } diff --git a/plugins/woocommerce-admin/tests/js/jest.config.json b/plugins/woocommerce-admin/tests/js/jest.config.json index 7cf4c219ac1..d4b3609153e 100644 --- a/plugins/woocommerce-admin/tests/js/jest.config.json +++ b/plugins/woocommerce-admin/tests/js/jest.config.json @@ -10,6 +10,7 @@ "moduleDirectories": ["node_modules", "/client", "/packages"], "moduleNameMapper": { "tinymce": "/tests/js/mocks/tinymce", + "@woocommerce/(settings|wc-admin-settings)": "/client/settings/index.js", "@woocommerce/(.*)": "/packages/$1/src" }, "setupFiles": [ diff --git a/plugins/woocommerce-admin/tests/js/setup-globals.js b/plugins/woocommerce-admin/tests/js/setup-globals.js index a81b9d055bf..c85156ff73b 100644 --- a/plugins/woocommerce-admin/tests/js/setup-globals.js +++ b/plugins/woocommerce-admin/tests/js/setup-globals.js @@ -30,6 +30,43 @@ const wooCommercePackages = [ 'number', ]; +// aliases +global.wcSettings = { + adminUrl: 'https://vagrant.local/wp/wp-admin/', + countries: [], + currency: { + code: 'USD', + precision: 2, + symbol: '$', + symbolPosition: 'left', + decimalSeparator: '.', + priceFormat: '%1$s%2$s', + thousandSeparator: ',', + }, + defaultDateRange: 'period=month&compare=previous_year', + date: { + dow: 0, + }, + locale: { + siteLocale: 'en_US', + userLocale: 'en_US', + weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], + }, + orderStatuses: { + pending: 'Pending payment', + processing: 'Processing', + 'on-hold': 'On hold', + completed: 'Completed', + cancelled: 'Cancelled', + refunded: 'Refunded', + failed: 'Failed', + }, + wcAdminSettings: { + woocommerce_actionable_order_statuses: [], + woocommerce_excluded_report_order_statuses: [], + }, +}; + wordPressPackages.forEach( lib => { Object.defineProperty( global.wp, lib, { get: () => require( `@wordpress/${ lib }` ), @@ -42,32 +79,6 @@ wooCommercePackages.forEach( lib => { } ); } ); -global.wcSettings = { - adminUrl: 'https://vagrant.local/wp/wp-admin/', - locale: 'en-US', - currency: { code: 'USD', precision: 2, symbol: '$' }, - date: { - dow: 0, - }, - orderStatuses: { - pending: 'Pending payment', - processing: 'Processing', - 'on-hold': 'On hold', - completed: 'Completed', - cancelled: 'Cancelled', - refunded: 'Refunded', - failed: 'Failed', - }, - l10n: { - userLocale: 'en_US', - weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], - }, - wcAdminSettings: { - woocommerce_actionable_order_statuses: [], - woocommerce_excluded_report_order_statuses: [], - }, -}; - const config = require( '../../config/development.json' ); window.wcAdminFeatures = config && config.features ? config.features : {}; diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 49657e53c48..cb9d11ebc51 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -34,6 +34,7 @@ const externals = { '@wordpress/html-entities': { this: [ 'wp', 'htmlEntities' ] }, '@wordpress/i18n': { this: [ 'wp', 'i18n' ] }, '@wordpress/keycodes': { this: [ 'wp', 'keycodes' ] }, + '@woocommerce/settings': { this: [ 'wc', 'wcSettings' ] }, tinymce: 'tinymce', moment: 'moment', react: 'React', @@ -150,6 +151,10 @@ const webpackConfig = { 'gutenberg-components': path.resolve( __dirname, 'node_modules/@wordpress/components/src' ), // @todo - remove once https://github.com/WordPress/gutenberg/pull/16196 is released. 'react-spring': 'react-spring/web.cjs', + '@woocommerce/wc-admin-settings': path.resolve( + __dirname, + 'client/settings/index.js' + ), }, }, plugins: [