Merge branch 'master' into fix/1035
# Conflicts: # client/analytics/report/customers/table.js # includes/api/class-wc-admin-rest-reports-orders-stats-controller.php # tests/reports/class-wc-tests-reports-orders-stats.php
This commit is contained in:
commit
507ee13825
|
@ -15,6 +15,11 @@
|
|||
"wpApiSettings": true,
|
||||
"wcSettings": true,
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect",
|
||||
},
|
||||
},
|
||||
"rules": {
|
||||
"camelcase": 0,
|
||||
"indent": 0,
|
||||
|
|
|
@ -14,7 +14,7 @@ And as such will require data layer logic for products to fully build the table
|
|||
"_links": {
|
||||
"product": [
|
||||
{
|
||||
"href": "https://example.com/wp-json/wc/v3/products/20"
|
||||
"href": "https://example.com/wp-json/wc/v4/products/20"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export default [
|
|||
_links: {
|
||||
product: [
|
||||
{
|
||||
href: 'https://example.com/wp-json/wc/v3/products/20',
|
||||
href: 'https://example.com/wp-json/wc/v4/products/20',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ export default [
|
|||
_links: {
|
||||
product: [
|
||||
{
|
||||
href: 'https://example.com/wp-json/wc/v3/products/22',
|
||||
href: 'https://example.com/wp-json/wc/v4/products/22',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -60,7 +60,7 @@ export default [
|
|||
_links: {
|
||||
product: [
|
||||
{
|
||||
href: 'https://example.com/wp-json/wc/v3/products/23',
|
||||
href: 'https://example.com/wp-json/wc/v4/products/23',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -74,7 +74,7 @@ export default [
|
|||
_links: {
|
||||
product: [
|
||||
{
|
||||
href: 'https://example.com/wp-json/wc/v3/products/24',
|
||||
href: 'https://example.com/wp-json/wc/v4/products/24',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -88,7 +88,7 @@ export default [
|
|||
_links: {
|
||||
product: [
|
||||
{
|
||||
href: 'https://example.com/wp-json/wc/v3/products/25',
|
||||
href: 'https://example.com/wp-json/wc/v4/products/25',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -69,7 +69,7 @@ Leaderboard.propTypes = {
|
|||
/**
|
||||
* The endpoint to use in API calls to populate the table rows and summary.
|
||||
* For example, if `taxes` is provided, data will be fetched from the report
|
||||
* `taxes` endpoint (ie: `/wc/v3/reports/taxes` and `/wc/v3/reports/taxes/stats`).
|
||||
* `taxes` endpoint (ie: `/wc/v4/reports/taxes` and `/wc/v4/reports/taxes/stats`).
|
||||
* If the provided endpoint doesn't exist, an error will be shown to the user
|
||||
* with `ReportError`.
|
||||
*/
|
||||
|
|
|
@ -17,6 +17,7 @@ import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'
|
|||
* Internal dependencies
|
||||
*/
|
||||
import LeaderboardWithSelect, { Leaderboard } from '../';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import mockData from '../__mocks__/top-selling-products-mock-data';
|
||||
|
||||
|
@ -102,7 +103,7 @@ describe( 'Leaderboard', () => {
|
|||
);
|
||||
const leaderboard = leaderboardWrapper.root.findByType( Leaderboard );
|
||||
|
||||
const endpoint = '/wc/v3/reports/products';
|
||||
const endpoint = NAMESPACE + 'reports/products';
|
||||
const query = { orderby: 'items_sold', per_page: 5, extended_info: 1 };
|
||||
|
||||
expect( getReportStatsMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
|
||||
|
|
|
@ -19,7 +19,7 @@ import { SummaryList, SummaryListPlaceholder, SummaryNumber } from '@woocommerce
|
|||
*/
|
||||
import { getSummaryNumbers } from 'store/reports/utils';
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
import { calculateDelta, formatValue } from './utils';
|
||||
import { calculateDelta, formatValue } from 'lib/number';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
/**
|
||||
|
@ -42,33 +42,35 @@ export class ReportSummary extends Component {
|
|||
const secondaryTotals = totals.secondary || {};
|
||||
const { compare } = getDateParamsFromQuery( query );
|
||||
|
||||
const summaryNumbers = charts.map( chart => {
|
||||
const { key, label, type } = chart;
|
||||
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
||||
const href = getNewPath( { chart: key } );
|
||||
const prevValue = formatValue( type, secondaryTotals[ key ] );
|
||||
const isSelected = selectedChart.key === key;
|
||||
const value = formatValue( type, primaryTotals[ key ] );
|
||||
const renderSummaryNumbers = ( { onToggle } ) =>
|
||||
charts.map( chart => {
|
||||
const { key, label, type } = chart;
|
||||
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
||||
const href = getNewPath( { chart: key } );
|
||||
const prevValue = formatValue( type, secondaryTotals[ key ] );
|
||||
const isSelected = selectedChart.key === key;
|
||||
const value = formatValue( type, primaryTotals[ key ] );
|
||||
|
||||
return (
|
||||
<SummaryNumber
|
||||
key={ key }
|
||||
delta={ delta }
|
||||
href={ href }
|
||||
label={ label }
|
||||
prevLabel={
|
||||
'previous_period' === compare
|
||||
? __( 'Previous Period:', 'wc-admin' )
|
||||
: __( 'Previous Year:', 'wc-admin' )
|
||||
}
|
||||
prevValue={ prevValue }
|
||||
selected={ isSelected }
|
||||
value={ value }
|
||||
/>
|
||||
);
|
||||
} );
|
||||
return (
|
||||
<SummaryNumber
|
||||
key={ key }
|
||||
delta={ delta }
|
||||
href={ href }
|
||||
label={ label }
|
||||
prevLabel={
|
||||
'previous_period' === compare
|
||||
? __( 'Previous Period:', 'wc-admin' )
|
||||
: __( 'Previous Year:', 'wc-admin' )
|
||||
}
|
||||
prevValue={ prevValue }
|
||||
selected={ isSelected }
|
||||
value={ value }
|
||||
onLinkClickCallback={ onToggle }
|
||||
/>
|
||||
);
|
||||
} );
|
||||
|
||||
return <SummaryList>{ summaryNumbers }</SummaryList>;
|
||||
return <SummaryList>{ renderSummaryNumbers }</SummaryList>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +82,7 @@ ReportSummary.propTypes = {
|
|||
/**
|
||||
* The endpoint to use in API calls to populate the Summary Numbers.
|
||||
* For example, if `taxes` is provided, data will be fetched from the report
|
||||
* `taxes` endpoint (ie: `/wc/v3/reports/taxes/stats`). If the provided endpoint
|
||||
* `taxes` endpoint (ie: `/wc/v4/reports/taxes/stats`). If the provided endpoint
|
||||
* doesn't exist, an error will be shown to the user with `ReportError`.
|
||||
*/
|
||||
endpoint: PropTypes.string.isRequired,
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isFinite } from 'lodash';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export function formatValue( type, value ) {
|
||||
if ( ! isFinite( value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( type ) {
|
||||
case 'average':
|
||||
return Math.round( value );
|
||||
case 'currency':
|
||||
return formatCurrency( value );
|
||||
case 'number':
|
||||
return numberFormat( value );
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateDelta( primaryValue, secondaryValue ) {
|
||||
if ( ! isFinite( primaryValue ) || ! isFinite( secondaryValue ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( secondaryValue === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round( ( primaryValue - secondaryValue ) / secondaryValue * 100 );
|
||||
}
|
|
@ -124,7 +124,7 @@ ReportTable.propTypes = {
|
|||
/**
|
||||
* The endpoint to use in API calls to populate the table rows and summary.
|
||||
* For example, if `taxes` is provided, data will be fetched from the report
|
||||
* `taxes` endpoint (ie: `/wc/v3/reports/taxes` and `/wc/v3/reports/taxes/stats`).
|
||||
* `taxes` endpoint (ie: `/wc/v4/reports/taxes` and `/wc/v4/reports/taxes/stats`).
|
||||
* If the provided endpoint doesn't exist, an error will be shown to the user
|
||||
* with `ReportError`.
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Spinner } from '@wordpress/components';
|
|||
* WooCommerce dependencies
|
||||
*/
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
|
||||
export default class CategoryBreadcrumbs extends Component {
|
||||
getCategoryAncestorIds( category, categories ) {
|
||||
|
@ -48,14 +49,18 @@ export default class CategoryBreadcrumbs extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { categories, category } = this.props;
|
||||
const { categories, category, query } = this.props;
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
return category ? (
|
||||
<div className="woocommerce-table__breadcrumbs">
|
||||
{ this.getCategoryAncestors( category, categories ) }
|
||||
<Link
|
||||
href={ 'term.php?taxonomy=product_cat&post_type=product&tag_ID=' + category.id }
|
||||
type="wp-admin"
|
||||
href={ getNewPath( persistedQuery, 'categories', {
|
||||
filter: 'single_category',
|
||||
categories: category.id,
|
||||
} ) }
|
||||
type="wc-admin"
|
||||
>
|
||||
{ category.name }
|
||||
</Link>
|
||||
|
|
|
@ -36,6 +36,31 @@ export const filters = [
|
|||
showFilters: () => true,
|
||||
filters: [
|
||||
{ label: __( 'All Categories', 'wc-admin' ), value: 'all' },
|
||||
{
|
||||
label: __( 'Single Category', 'wc-admin' ),
|
||||
value: 'select_category',
|
||||
chartMode: 'item-comparison',
|
||||
subFilters: [
|
||||
{
|
||||
component: 'Search',
|
||||
value: 'single_category',
|
||||
chartMode: 'item-comparison',
|
||||
path: [ 'select_category' ],
|
||||
settings: {
|
||||
type: 'categories',
|
||||
param: 'categories',
|
||||
getLabels: getRequestByIdString( NAMESPACE + 'products/categories', category => ( {
|
||||
id: category.id,
|
||||
label: category.name,
|
||||
} ) ),
|
||||
labels: {
|
||||
placeholder: __( 'Type to search for a category', 'wc-admin' ),
|
||||
button: __( 'Single Category', 'wc-admin' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: __( 'Comparison', 'wc-admin' ),
|
||||
value: 'compare-categories',
|
||||
|
|
|
@ -11,6 +11,8 @@ import { map } from 'lodash';
|
|||
* WooCommerce dependencies
|
||||
*/
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -66,14 +68,18 @@ class CategoriesReportTable extends Component {
|
|||
}
|
||||
|
||||
getRowsContent( categoryStats ) {
|
||||
const { query } = this.props;
|
||||
return map( categoryStats, categoryStat => {
|
||||
const { category_id, items_sold, net_revenue, products_count, orders_count } = categoryStat;
|
||||
const categories = this.props.categories;
|
||||
const { categories, query } = this.props;
|
||||
const category = categories[ category_id ];
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
return [
|
||||
{
|
||||
display: <CategoryBreacrumbs category={ category } categories={ categories } />,
|
||||
display: (
|
||||
<CategoryBreacrumbs query={ query } category={ category } categories={ categories } />
|
||||
),
|
||||
value: category && category.name,
|
||||
},
|
||||
{
|
||||
|
@ -85,7 +91,17 @@ class CategoriesReportTable extends Component {
|
|||
value: getCurrencyFormatDecimal( net_revenue ),
|
||||
},
|
||||
{
|
||||
display: numberFormat( products_count ),
|
||||
display: category && (
|
||||
<Link
|
||||
href={ getNewPath( persistedQuery, 'categories', {
|
||||
filter: 'single_category',
|
||||
categories: category.id,
|
||||
} ) }
|
||||
type="wc-admin"
|
||||
>
|
||||
{ numberFormat( products_count ) }
|
||||
</Link>
|
||||
),
|
||||
value: products_count,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -31,6 +31,31 @@ export const filters = [
|
|||
showFilters: () => true,
|
||||
filters: [
|
||||
{ label: __( 'All Coupons', 'wc-admin' ), value: 'all' },
|
||||
{
|
||||
label: __( 'Single Coupon', 'wc-admin' ),
|
||||
value: 'select_coupon',
|
||||
chartMode: 'item-comparison',
|
||||
subFilters: [
|
||||
{
|
||||
component: 'Search',
|
||||
value: 'single_coupon',
|
||||
chartMode: 'item-comparison',
|
||||
path: [ 'select_coupon' ],
|
||||
settings: {
|
||||
type: 'coupons',
|
||||
param: 'coupons',
|
||||
getLabels: getRequestByIdString( NAMESPACE + 'coupons', coupon => ( {
|
||||
id: coupon.id,
|
||||
label: coupon.code,
|
||||
} ) ),
|
||||
labels: {
|
||||
placeholder: __( 'Type to search for a coupon', 'wc-admin' ),
|
||||
button: __( 'Single Coupon', 'wc-admin' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: __( 'Comparison', 'wc-admin' ),
|
||||
value: 'compare-coupons',
|
||||
|
@ -44,6 +69,7 @@ export const filters = [
|
|||
labels: {
|
||||
title: __( 'Compare Coupon Codes', 'wc-admin' ),
|
||||
update: __( 'Compare', 'wc-admin' ),
|
||||
helpText: __( 'Select at least two coupon codes to compare', 'wc-admin' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -281,6 +281,36 @@ export const advancedFilters = {
|
|||
component: 'Date',
|
||||
},
|
||||
},
|
||||
last_active: {
|
||||
labels: {
|
||||
add: __( 'Last active', 'wc-admin' ),
|
||||
remove: __( 'Remove last active filter', 'wc-admin' ),
|
||||
rule: __( 'Select a last active filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Last active {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select registered date', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'before',
|
||||
/* translators: Sentence fragment, logical, "Before" refers to customers registered before a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Before', 'date', 'wc-admin' ),
|
||||
},
|
||||
{
|
||||
value: 'after',
|
||||
/* translators: Sentence fragment, logical, "after" refers to customers registered after a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'After', 'date', 'wc-admin' ),
|
||||
},
|
||||
{
|
||||
value: 'between',
|
||||
/* translators: Sentence fragment, logical, "Between" refers to average order value of a customer, between two given amounts. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Between', 'date', 'wc-admin' ),
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Date',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/*eslint-enable max-len*/
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { Tooltip } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
|
@ -90,6 +91,12 @@ 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;
|
||||
}
|
||||
|
||||
getRowsContent( customers ) {
|
||||
return customers.map( customer => {
|
||||
const {
|
||||
|
@ -106,6 +113,7 @@ export default class CustomersReportTable extends Component {
|
|||
city,
|
||||
country,
|
||||
} = customer;
|
||||
const countryName = this.getCountryName( country );
|
||||
|
||||
const customerNameLink = user_id ? (
|
||||
<Link href={ 'user-edit.php?user_id=' + user_id } type="wp-admin">
|
||||
|
@ -121,6 +129,15 @@ export default class CustomersReportTable extends Component {
|
|||
'—'
|
||||
);
|
||||
|
||||
const countryDisplay = (
|
||||
<Fragment>
|
||||
<Tooltip text={ countryName }>
|
||||
<span aria-hidden="true">{ country }</span>
|
||||
</Tooltip>
|
||||
<span className="screen-reader-text">{ countryName }</span>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
display: customerNameLink,
|
||||
|
@ -155,7 +172,7 @@ export default class CustomersReportTable extends Component {
|
|||
value: date_last_active,
|
||||
},
|
||||
{
|
||||
display: country,
|
||||
display: countryDisplay,
|
||||
value: country,
|
||||
},
|
||||
{
|
||||
|
@ -176,11 +193,6 @@ export default class CustomersReportTable extends Component {
|
|||
return (
|
||||
<ReportTable
|
||||
endpoint="customers"
|
||||
extendItemsMethodNames={ {
|
||||
load: 'getCustomers',
|
||||
getError: 'getCustomersError',
|
||||
isRequesting: 'isGetCustomersRequesting',
|
||||
} }
|
||||
getHeadersContent={ this.getHeadersContent }
|
||||
getRowsContent={ this.getRowsContent }
|
||||
itemIdField="id"
|
||||
|
|
|
@ -2,30 +2,26 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { __, _n, _x, sprintf } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { map } from 'lodash';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
||||
import { Date, Link, OrderStatus, ViewMoreList } from '@woocommerce/components';
|
||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { QUERY_DEFAULTS } from 'store/constants';
|
||||
import { getFilterQuery } from 'store/reports/utils';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { formatTableOrders } from './utils';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import './style.scss';
|
||||
|
||||
class OrdersReportTable extends Component {
|
||||
export default class OrdersReportTable extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
@ -96,45 +92,52 @@ class OrdersReportTable extends Component {
|
|||
}
|
||||
|
||||
getRowsContent( tableData ) {
|
||||
const { query } = this.props;
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
return map( tableData, row => {
|
||||
const {
|
||||
date,
|
||||
id,
|
||||
status,
|
||||
customer_id,
|
||||
line_items,
|
||||
items_sold,
|
||||
coupon_lines,
|
||||
currency,
|
||||
net_revenue,
|
||||
customer_type,
|
||||
date_created,
|
||||
extended_info,
|
||||
net_total,
|
||||
num_items_sold,
|
||||
order_id,
|
||||
status,
|
||||
} = row;
|
||||
const { coupons, products } = extended_info;
|
||||
|
||||
const products = line_items
|
||||
const formattedProducts = products
|
||||
.sort( ( itemA, itemB ) => itemB.quantity - itemA.quantity )
|
||||
.map( item => ( {
|
||||
label: item.name,
|
||||
href: 'post.php?post=' + item.product_id + '&action=edit',
|
||||
quantity: item.quantity,
|
||||
href: getNewPath( persistedQuery, 'products', {
|
||||
filter: 'single_product',
|
||||
products: item.id,
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
const coupons = coupon_lines.map( coupon => ( {
|
||||
const formattedCoupons = coupons.map( coupon => ( {
|
||||
label: coupon.code,
|
||||
// @TODO It should link to the coupons report
|
||||
href: 'edit.php?s=' + coupon.code + '&post_type=shop_coupon',
|
||||
href: getNewPath( persistedQuery, 'coupons', {
|
||||
filter: 'single_coupon',
|
||||
coupons: coupon.id,
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
return [
|
||||
{
|
||||
display: <Date date={ date } visibleFormat={ defaultTableDateFormat } />,
|
||||
value: date,
|
||||
display: <Date date={ date_created } visibleFormat={ defaultTableDateFormat } />,
|
||||
value: date_created,
|
||||
},
|
||||
{
|
||||
display: (
|
||||
<Link href={ 'post.php?post=' + id + '&action=edit' } type="wp-admin">
|
||||
{ id }
|
||||
<Link href={ 'post.php?post=' + order_id + '&action=edit' } type="wp-admin">
|
||||
{ order_id }
|
||||
</Link>
|
||||
),
|
||||
value: id,
|
||||
value: order_id,
|
||||
},
|
||||
{
|
||||
display: (
|
||||
|
@ -143,32 +146,36 @@ class OrdersReportTable extends Component {
|
|||
value: status,
|
||||
},
|
||||
{
|
||||
// @TODO This should display customer type (new/returning) once it's
|
||||
// implemented in the API.
|
||||
display: customer_id,
|
||||
value: customer_id,
|
||||
display:
|
||||
customer_type === 'new'
|
||||
? _x( 'New', 'customer type', 'wc-admin' )
|
||||
: _x( 'Returning', 'customer type', 'wc-admin' ),
|
||||
value: customer_type,
|
||||
},
|
||||
{
|
||||
display: this.renderList(
|
||||
products.length ? [ products[ 0 ] ] : [],
|
||||
products.map( product => ( {
|
||||
formattedProducts.length ? [ formattedProducts[ 0 ] ] : [],
|
||||
formattedProducts.map( product => ( {
|
||||
label: sprintf( __( '%s× %s', 'wc-admin' ), product.quantity, product.label ),
|
||||
href: product.href,
|
||||
} ) )
|
||||
),
|
||||
value: products.map( product => product.label ).join( ' ' ),
|
||||
value: formattedProducts.map( product => product.label ).join( ' ' ),
|
||||
},
|
||||
{
|
||||
display: numberFormat( items_sold ),
|
||||
value: items_sold,
|
||||
display: numberFormat( num_items_sold ),
|
||||
value: num_items_sold,
|
||||
},
|
||||
{
|
||||
display: this.renderList( coupons.length ? [ coupons[ 0 ] ] : [], coupons ),
|
||||
value: coupons.map( item => item.code ).join( ' ' ),
|
||||
display: this.renderList(
|
||||
formattedCoupons.length ? [ formattedCoupons[ 0 ] ] : [],
|
||||
formattedCoupons
|
||||
),
|
||||
value: formattedCoupons.map( item => item.code ).join( ' ' ),
|
||||
},
|
||||
{
|
||||
display: formatCurrency( net_revenue, currency ),
|
||||
value: net_revenue,
|
||||
display: formatCurrency( net_total, currency ),
|
||||
value: net_total,
|
||||
},
|
||||
];
|
||||
} );
|
||||
|
@ -217,7 +224,7 @@ class OrdersReportTable extends Component {
|
|||
|
||||
renderLinks( items = [] ) {
|
||||
return items.map( ( item, i ) => (
|
||||
<Link href={ item.href } key={ i } type="wp-admin">
|
||||
<Link href={ item.href } key={ i } type="wc-admin">
|
||||
{ item.label }
|
||||
</Link>
|
||||
) );
|
||||
|
@ -233,7 +240,7 @@ class OrdersReportTable extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { query, tableData } = this.props;
|
||||
const { query } = this.props;
|
||||
|
||||
return (
|
||||
<ReportTable
|
||||
|
@ -242,49 +249,12 @@ class OrdersReportTable extends Component {
|
|||
getRowsContent={ this.getRowsContent }
|
||||
getSummary={ this.getSummary }
|
||||
query={ query }
|
||||
tableData={ tableData }
|
||||
tableQuery={ {
|
||||
extended_info: true,
|
||||
} }
|
||||
title={ __( 'Orders', 'wc-admin' ) }
|
||||
columnPrefsKey="orders_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { query } = props;
|
||||
const datesFromQuery = getCurrentDates( query );
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
const { getOrders, getOrdersTotalCount, getOrdersError, isGetOrdersRequesting } = select(
|
||||
'wc-api'
|
||||
);
|
||||
|
||||
const tableQuery = {
|
||||
orderby: query.orderby || 'date',
|
||||
order: query.order || 'asc',
|
||||
page: query.page || 1,
|
||||
per_page: query.per_page || QUERY_DEFAULTS.pageSize,
|
||||
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
|
||||
before: appendTimestamp( datesFromQuery.primary.before, 'end' ),
|
||||
status: [ 'processing', 'on-hold', 'completed' ],
|
||||
...filterQuery,
|
||||
};
|
||||
const orders = getOrders( tableQuery );
|
||||
const ordersTotalCount = getOrdersTotalCount( tableQuery );
|
||||
const isError = Boolean( getOrdersError( tableQuery ) );
|
||||
const isRequesting = isGetOrdersRequesting( tableQuery );
|
||||
|
||||
return {
|
||||
tableData: {
|
||||
items: {
|
||||
data: formatTableOrders( orders ),
|
||||
totalResults: ordersTotalCount,
|
||||
},
|
||||
isError,
|
||||
isRequesting,
|
||||
query: tableQuery,
|
||||
},
|
||||
};
|
||||
} )
|
||||
)( OrdersReportTable );
|
||||
|
|
|
@ -60,6 +60,31 @@ const filterConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: __( 'Single Product Category', 'wc-admin' ),
|
||||
value: 'select_category',
|
||||
chartMode: 'item-comparison',
|
||||
subFilters: [
|
||||
{
|
||||
component: 'Search',
|
||||
value: 'single_category',
|
||||
chartMode: 'item-comparison',
|
||||
path: [ 'select_category' ],
|
||||
settings: {
|
||||
type: 'categories',
|
||||
param: 'categories',
|
||||
getLabels: getRequestByIdString( NAMESPACE + 'products/categories', category => ( {
|
||||
id: category.id,
|
||||
label: category.name,
|
||||
} ) ),
|
||||
labels: {
|
||||
placeholder: __( 'Type to search for a product category', 'wc-admin' ),
|
||||
button: __( 'Single Product Category', 'wc-admin' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: __( 'Product Comparison', 'wc-admin' ),
|
||||
value: 'compare-products',
|
||||
|
|
|
@ -172,6 +172,7 @@ class ProductsReportTable extends Component {
|
|||
category={ category }
|
||||
categories={ categories }
|
||||
key={ category.id }
|
||||
query={ query }
|
||||
/>
|
||||
) ) }
|
||||
/>
|
||||
|
|
|
@ -222,7 +222,7 @@ export default compose(
|
|||
const tableQuery = {
|
||||
interval: 'day',
|
||||
orderby: query.orderby || 'date',
|
||||
order: query.order || 'asc',
|
||||
order: query.order || 'desc',
|
||||
page: query.page || 1,
|
||||
per_page: query.per_page || QUERY_DEFAULTS.pageSize,
|
||||
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
|
||||
|
|
|
@ -43,12 +43,13 @@ export default class StockReportTable extends Component {
|
|||
{
|
||||
label: __( 'Status', 'wc-admin' ),
|
||||
key: 'stock_status',
|
||||
isSortable: true,
|
||||
defaultSort: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Stock', 'wc-admin' ),
|
||||
key: 'stock_quantity',
|
||||
isSortable: true,
|
||||
defaultSort: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -136,8 +137,8 @@ export default class StockReportTable extends Component {
|
|||
// getSummary={ this.getSummary }
|
||||
query={ query }
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'stock_quantity',
|
||||
order: query.order || 'desc',
|
||||
orderby: query.orderby || 'stock_status',
|
||||
order: query.order || 'asc',
|
||||
type: query.type || 'all',
|
||||
} }
|
||||
title={ __( 'Stock', 'wc-admin' ) }
|
||||
|
|
|
@ -13,13 +13,13 @@ import { NAMESPACE } from 'store/constants';
|
|||
|
||||
export const charts = [
|
||||
{
|
||||
key: 'order_tax',
|
||||
label: __( 'Order Tax', 'wc-admin' ),
|
||||
key: 'total_tax',
|
||||
label: __( 'Total Tax', 'wc-admin' ),
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'total_tax',
|
||||
label: __( 'Total Tax', 'wc-admin' ),
|
||||
key: 'order_tax',
|
||||
label: __( 'Order Tax', 'wc-admin' ),
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
.woocommerce-chart__footer {
|
||||
position: relative;
|
||||
&:after {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class Dashboard extends Component {
|
|||
<Fragment>
|
||||
<Header sections={ [ __( 'Dashboard', 'wc-admin' ) ] } />
|
||||
<ReportFilters query={ query } path={ path } />
|
||||
<StorePerformance />
|
||||
<StorePerformance query={ query } />
|
||||
<Leaderboards query={ query } />
|
||||
<DashboardCharts query={ query } path={ path } />
|
||||
</Fragment>
|
||||
|
|
|
@ -59,9 +59,8 @@ export class TopSellingCategories extends Component {
|
|||
return map( data, row => {
|
||||
const { category_id, items_sold, net_revenue, extended_info } = row;
|
||||
const name = get( extended_info, [ 'name' ] );
|
||||
// TODO Update this to use a single_category filter, once it exists.
|
||||
const categoryUrl = getNewPath( persistedQuery, 'analytics/categories', {
|
||||
filter: 'compare-categories',
|
||||
filter: 'single_category',
|
||||
categories: category_id,
|
||||
} );
|
||||
const categoryLink = (
|
||||
|
|
|
@ -2,9 +2,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { ToggleControl } from '@wordpress/components';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import moment from 'moment';
|
||||
import { find } from 'lodash';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@woocommerce/date';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -16,99 +26,204 @@ import {
|
|||
MenuTitle,
|
||||
SectionHeader,
|
||||
SummaryList,
|
||||
SummaryListPlaceholder,
|
||||
SummaryNumber,
|
||||
} from '@woocommerce/components';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import './style.scss';
|
||||
import { calculateDelta, formatValue } from 'lib/number';
|
||||
|
||||
class StorePerformance extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = {
|
||||
showCustomers: true,
|
||||
showProducts: true,
|
||||
showOrders: true,
|
||||
userPrefs: props.userPrefs || [],
|
||||
};
|
||||
|
||||
this.toggle = this.toggle.bind( this );
|
||||
}
|
||||
|
||||
toggle( type ) {
|
||||
toggle( statKey ) {
|
||||
return () => {
|
||||
this.setState( state => ( { [ type ]: ! state[ type ] } ) );
|
||||
this.setState( state => {
|
||||
const prefs = [ ...state.userPrefs ];
|
||||
let newPrefs = [];
|
||||
if ( ! prefs.includes( statKey ) ) {
|
||||
prefs.push( statKey );
|
||||
newPrefs = prefs;
|
||||
} else {
|
||||
newPrefs = prefs.filter( pref => pref !== statKey );
|
||||
}
|
||||
this.props.updateCurrentUserData( {
|
||||
dashboard_performance_indicators: newPrefs,
|
||||
} );
|
||||
return {
|
||||
userPrefs: newPrefs,
|
||||
};
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
const { indicators } = this.props;
|
||||
return (
|
||||
<EllipsisMenu label={ __( 'Choose which analytics to display', 'wc-admin' ) }>
|
||||
<MenuTitle>{ __( 'Display Stats:', 'wc-admin' ) }</MenuTitle>
|
||||
<MenuItem onInvoke={ this.toggle( 'showCustomers' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Show Customers', 'wc-admin' ) }
|
||||
checked={ this.state.showCustomers }
|
||||
onChange={ this.toggle( 'showCustomers' ) }
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem onInvoke={ this.toggle( 'showProducts' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Show Products', 'wc-admin' ) }
|
||||
checked={ this.state.showProducts }
|
||||
onChange={ this.toggle( 'showProducts' ) }
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem onInvoke={ this.toggle( 'showOrders' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Show Orders', 'wc-admin' ) }
|
||||
checked={ this.state.showOrders }
|
||||
onChange={ this.toggle( 'showOrders' ) }
|
||||
/>
|
||||
</MenuItem>
|
||||
{ indicators.map( ( indicator, i ) => {
|
||||
const checked = ! this.state.userPrefs.includes( indicator.stat );
|
||||
return (
|
||||
<MenuItem onInvoke={ this.toggle( indicator.stat ) } key={ i }>
|
||||
<ToggleControl
|
||||
label={ sprintf( __( 'Show %s', 'wc-admin' ), indicator.label ) }
|
||||
checked={ checked }
|
||||
onChange={ this.toggle( indicator.stat ) }
|
||||
/>
|
||||
</MenuItem>
|
||||
);
|
||||
} ) }
|
||||
</EllipsisMenu>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const totalOrders = 10;
|
||||
const totalProducts = 1000;
|
||||
const { showCustomers, showProducts, showOrders } = this.state;
|
||||
renderList() {
|
||||
const {
|
||||
query,
|
||||
primaryRequesting,
|
||||
secondaryRequesting,
|
||||
primaryError,
|
||||
secondaryError,
|
||||
primaryData,
|
||||
secondaryData,
|
||||
userIndicators,
|
||||
} = this.props;
|
||||
if ( primaryRequesting || secondaryRequesting ) {
|
||||
return <SummaryListPlaceholder numberOfItems={ userIndicators.length } />;
|
||||
}
|
||||
|
||||
if ( primaryError || secondaryError ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
const { compare } = getDateParamsFromQuery( query );
|
||||
const prevLabel =
|
||||
'previous_period' === compare
|
||||
? __( 'Previous Period:', 'wc-admin' )
|
||||
: __( 'Previous Year:', 'wc-admin' );
|
||||
return (
|
||||
<SummaryList>
|
||||
{ () =>
|
||||
userIndicators.map( ( indicator, i ) => {
|
||||
const primaryItem = find( primaryData.data, data => data.stat === indicator.stat );
|
||||
const secondaryItem = find( secondaryData.data, data => data.stat === indicator.stat );
|
||||
|
||||
if ( ! primaryItem || ! secondaryItem ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const href =
|
||||
( primaryItem._links &&
|
||||
primaryItem._links.report[ 0 ] &&
|
||||
primaryItem._links.report[ 0 ].href ) ||
|
||||
'';
|
||||
const reportUrl =
|
||||
( href && getNewPath( persistedQuery, href, { chart: primaryItem.chart } ) ) || '';
|
||||
const delta = calculateDelta( primaryItem.value, secondaryItem.value );
|
||||
const primaryValue = formatValue( primaryItem.format, primaryItem.value );
|
||||
const secondaryValue = formatValue( secondaryItem.format, secondaryItem.value );
|
||||
|
||||
return (
|
||||
<SummaryNumber
|
||||
key={ i }
|
||||
href={ reportUrl }
|
||||
label={ indicator.label }
|
||||
value={ primaryValue }
|
||||
prevLabel={ prevLabel }
|
||||
prevValue={ secondaryValue }
|
||||
delta={ delta }
|
||||
/>
|
||||
);
|
||||
} )
|
||||
}
|
||||
</SummaryList>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<SectionHeader title={ __( 'Store Performance', 'wc-admin' ) } menu={ this.renderMenu() } />
|
||||
<Card className="woocommerce-dashboard__store-performance">
|
||||
<SummaryList>
|
||||
{ showCustomers && (
|
||||
<SummaryNumber
|
||||
label={ __( 'New Customers', 'wc-admin' ) }
|
||||
value={ '2' }
|
||||
prevLabel={ __( 'Previous Week:', 'wc-admin' ) }
|
||||
prevValue={ 3 }
|
||||
delta={ -33 }
|
||||
/>
|
||||
) }
|
||||
{ showProducts && (
|
||||
<SummaryNumber
|
||||
label={ __( 'Total Products', 'wc-admin' ) }
|
||||
value={ totalProducts }
|
||||
prevLabel={ __( 'Previous Week:', 'wc-admin' ) }
|
||||
prevValue={ totalProducts }
|
||||
delta={ 0 }
|
||||
/>
|
||||
) }
|
||||
{ showOrders && (
|
||||
<SummaryNumber
|
||||
label={ __( 'Total Orders', 'wc-admin' ) }
|
||||
value={ totalOrders }
|
||||
prevLabel={ __( 'Previous Week:', 'wc-admin' ) }
|
||||
prevValue={ totalOrders }
|
||||
delta={ 0 }
|
||||
/>
|
||||
) }
|
||||
</SummaryList>
|
||||
</Card>
|
||||
<Card className="woocommerce-dashboard__store-performance">{ this.renderList() }</Card>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { query } = props;
|
||||
const {
|
||||
getCurrentUserData,
|
||||
getReportItems,
|
||||
getReportItemsError,
|
||||
isReportItemsRequesting,
|
||||
} = select( 'wc-api' );
|
||||
const userData = getCurrentUserData();
|
||||
let userPrefs = userData.dashboard_performance_indicators;
|
||||
|
||||
export default StorePerformance;
|
||||
// Set default values for user preferences if none is set.
|
||||
// These columns are HIDDEN by default.
|
||||
if ( ! userPrefs ) {
|
||||
userPrefs = [ 'taxes/order_tax', 'taxes/shipping_tax', 'downloads/download_count' ];
|
||||
}
|
||||
|
||||
const datesFromQuery = getCurrentDates( query );
|
||||
const endPrimary = datesFromQuery.primary.before;
|
||||
const endSecondary = datesFromQuery.secondary.before;
|
||||
|
||||
const indicators = wcSettings.dataEndpoints.performanceIndicators;
|
||||
const userIndicators = indicators.filter( indicator => ! userPrefs.includes( indicator.stat ) );
|
||||
const statKeys = userIndicators.map( indicator => indicator.stat ).join( ',' );
|
||||
|
||||
const primaryQuery = {
|
||||
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
|
||||
before: appendTimestamp( endPrimary, endPrimary.isSame( moment(), 'day' ) ? 'now' : 'end' ),
|
||||
stats: statKeys,
|
||||
};
|
||||
|
||||
const secondaryQuery = {
|
||||
after: appendTimestamp( datesFromQuery.secondary.after, 'start' ),
|
||||
before: appendTimestamp(
|
||||
endSecondary,
|
||||
endSecondary.isSame( moment(), 'day' ) ? 'now' : 'end'
|
||||
),
|
||||
stats: statKeys,
|
||||
};
|
||||
|
||||
const primaryData = getReportItems( 'performance-indicators', primaryQuery );
|
||||
const primaryError = getReportItemsError( 'performance-indicators', primaryQuery ) || null;
|
||||
const primaryRequesting = isReportItemsRequesting( 'performance-indicators', primaryQuery );
|
||||
|
||||
const secondaryData = getReportItems( 'performance-indicators', secondaryQuery );
|
||||
const secondaryError = getReportItemsError( 'performance-indicators', secondaryQuery ) || null;
|
||||
const secondaryRequesting = isReportItemsRequesting( 'performance-indicators', secondaryQuery );
|
||||
|
||||
return {
|
||||
userPrefs,
|
||||
userIndicators,
|
||||
indicators,
|
||||
primaryData,
|
||||
primaryError,
|
||||
primaryRequesting,
|
||||
secondaryData,
|
||||
secondaryError,
|
||||
secondaryRequesting,
|
||||
};
|
||||
} ),
|
||||
withDispatch( dispatch => {
|
||||
const { updateCurrentUserData } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
updateCurrentUserData,
|
||||
};
|
||||
} )
|
||||
)( StorePerformance );
|
||||
|
|
|
@ -133,11 +133,11 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
</span>
|
||||
{ refundValue ? (
|
||||
<span>
|
||||
<s>{ formatCurrency( total, order.currency ) }</s>{' '}
|
||||
{ formatCurrency( remainingTotal, order.currency ) }
|
||||
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
|
||||
{ formatCurrency( remainingTotal, order.currency_symbol ) }
|
||||
</span>
|
||||
) : (
|
||||
<span>{ formatCurrency( total, order.currency ) }</span>
|
||||
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
|
||||
) }
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
@include breakpoint( '>960px' ) {
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-layout .woocommerce-layout__main {
|
||||
|
|
|
@ -1,20 +1,63 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get, isFinite } from 'lodash';
|
||||
const number_format = require( 'locutus/php/strings/number_format' );
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
/**
|
||||
* Formats a number using site's current locale
|
||||
*
|
||||
* @format
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
|
||||
* @param {Number|String} number number to format
|
||||
* @returns {?String} A formatted string.
|
||||
* @see http://locutus.io/php/strings/number_format/
|
||||
* @param {Number|String} number number to format
|
||||
* @param {int|null} [precision=null] optional decimal precision
|
||||
* @returns {?String} A formatted string.
|
||||
*/
|
||||
|
||||
export function numberFormat( number ) {
|
||||
const locale = wcSettings.siteLocale || 'en-US'; // Default so we don't break.
|
||||
|
||||
export function numberFormat( number, precision = null ) {
|
||||
if ( 'number' !== typeof number ) {
|
||||
number = parseFloat( number );
|
||||
}
|
||||
|
||||
if ( isNaN( number ) ) {
|
||||
return '';
|
||||
}
|
||||
return new Intl.NumberFormat( locale ).format( number );
|
||||
|
||||
const decimalSeparator = get( wcSettings, [ 'currency', 'decimal_separator' ], '.' );
|
||||
const thousandSeparator = get( wcSettings, [ 'currency', 'thousand_separator' ], ',' );
|
||||
precision = parseInt( precision );
|
||||
|
||||
if ( isNaN( precision ) ) {
|
||||
const [ , decimals ] = number.toString().split( '.' );
|
||||
precision = decimals ? decimals.length : 0;
|
||||
}
|
||||
|
||||
return number_format( number, precision, decimalSeparator, thousandSeparator );
|
||||
}
|
||||
|
||||
export function formatValue( type, value ) {
|
||||
if ( ! isFinite( value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( type ) {
|
||||
case 'average':
|
||||
return Math.round( value );
|
||||
case 'currency':
|
||||
return formatCurrency( value );
|
||||
case 'number':
|
||||
return numberFormat( value );
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateDelta( primaryValue, secondaryValue ) {
|
||||
if ( ! isFinite( primaryValue ) || ! isFinite( secondaryValue ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( secondaryValue === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round( ( primaryValue - secondaryValue ) / secondaryValue * 100 );
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { numberFormat } from '../index';
|
||||
|
||||
describe( 'numberFormat', () => {
|
||||
it( 'should default to en-US formatting', () => {
|
||||
it( 'should default to precision=null decimal=. thousands=,', () => {
|
||||
expect( numberFormat( 1000 ) ).toBe( '1,000' );
|
||||
} );
|
||||
|
||||
|
@ -16,4 +16,20 @@ describe( 'numberFormat', () => {
|
|||
it( 'should accept a string', () => {
|
||||
expect( numberFormat( '10000' ) ).toBe( '10,000' );
|
||||
} );
|
||||
|
||||
it( 'maintains all decimals if no precision specified', () => {
|
||||
expect( numberFormat( '10000.123456' ) ).toBe( '10,000.123456' );
|
||||
} );
|
||||
|
||||
it( 'maintains all decimals if invalid precision specified', () => {
|
||||
expect( numberFormat( '10000.123456', 'not a number' ) ).toBe( '10,000.123456' );
|
||||
} );
|
||||
|
||||
it( 'uses store currency settings, not locale', () => {
|
||||
global.wcSettings.siteLocale = 'en-US';
|
||||
global.wcSettings.currency.decimal_separator = ',';
|
||||
global.wcSettings.currency.thousand_separator = '.';
|
||||
|
||||
expect( numberFormat( '12345.6789', 3 ) ).toBe( '12.345,679' );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* @format
|
||||
*/
|
||||
|
||||
export const NAMESPACE = '/wc/v3/';
|
||||
export const NAMESPACE = '/wc/v4/';
|
||||
export const SWAGGERNAMESPACE = 'https://virtserver.swaggerhub.com/peterfabian/wc-v3-api/1.0.0/';
|
||||
export const ERROR = 'ERROR';
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { dispatch } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getNotes } = resolvers;
|
||||
|
@ -29,10 +30,10 @@ describe( 'getNotes', () => {
|
|||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v3/admin/notes' ) {
|
||||
if ( options.path === NAMESPACE + 'admin/notes' ) {
|
||||
return Promise.resolve( NOTES_1 );
|
||||
}
|
||||
if ( options.path === '/wc/v3/admin/notes?page=2' ) {
|
||||
if ( options.path === NAMESPACE + 'admin/notes?page=2' ) {
|
||||
return Promise.resolve( NOTES_2 );
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -11,6 +11,7 @@ import { dispatch } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getOrders } = resolvers;
|
||||
|
@ -29,10 +30,10 @@ describe( 'getOrders', () => {
|
|||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v3/orders' ) {
|
||||
if ( options.path === NAMESPACE + 'orders' ) {
|
||||
return Promise.resolve( ORDERS_1 );
|
||||
}
|
||||
if ( options.path === '/wc/v3/orders?orderby=id' ) {
|
||||
if ( options.path === NAMESPACE + 'orders?orderby=id' ) {
|
||||
return Promise.resolve( ORDERS_2 );
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -31,7 +31,7 @@ describe( 'getReportItems', () => {
|
|||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === `/wc/v3/reports/${ endpoint }` ) {
|
||||
if ( options.path === `/wc/v4/reports/${ endpoint }` ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: () => ITEMS_1_COUNT,
|
||||
|
@ -39,7 +39,7 @@ describe( 'getReportItems', () => {
|
|||
json: () => Promise.resolve( ITEMS_1 ),
|
||||
} );
|
||||
}
|
||||
if ( options.path === `/wc/v3/reports/${ endpoint }?orderby=id` ) {
|
||||
if ( options.path === `/wc/v4/reports/${ endpoint }?orderby=id` ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: () => ITEMS_2_COUNT,
|
||||
|
|
|
@ -55,7 +55,7 @@ describe( 'getReportStats', () => {
|
|||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v3/reports/revenue/stats' ) {
|
||||
if ( options.path === '/wc/v4/reports/revenue/stats' ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: header => REPORT_1_TOTALS[ header ],
|
||||
|
@ -63,7 +63,7 @@ describe( 'getReportStats', () => {
|
|||
json: () => Promise.resolve( REPORT_1 ),
|
||||
} );
|
||||
}
|
||||
if ( options.path === '/wc/v3/reports/products/stats?interval=week' ) {
|
||||
if ( options.path === '/wc/v4/reports/products/stats?interval=week' ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: header => REPORT_2_TOTALS[ header ],
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
getSummaryNumbers,
|
||||
getFilterQuery,
|
||||
getReportTableData,
|
||||
timeStampFilterDates,
|
||||
} from '../utils';
|
||||
import * as ordersConfig from 'analytics/report/orders/config';
|
||||
|
||||
|
@ -527,3 +528,71 @@ describe( 'getReportTableData()', () => {
|
|||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'timeStampFilterDates', () => {
|
||||
const advancedFilters = {
|
||||
filters: {
|
||||
city: {
|
||||
input: { component: 'Search' },
|
||||
},
|
||||
my_date: {
|
||||
input: { component: 'Date' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it( 'should not change activeFilters not using the Date component', () => {
|
||||
const activeFilter = {
|
||||
key: 'name',
|
||||
rule: 'is',
|
||||
value: 'New York',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
|
||||
expect( timeStampedActiveFilter ).toMatchObject( activeFilter );
|
||||
} );
|
||||
|
||||
it( 'should append timestamps to activeFilters using the Date component', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'after',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T00:00:00+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should append start of day for "after" rule', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'after',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T00:00:00+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should append end of day for "before" rule', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'before',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T23:59:59+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should handle "between" values', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'before',
|
||||
value: [ '2018-04-04', '2018-04-10' ],
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( Array.isArray( timeStampedActiveFilter.value ) ).toBe( true );
|
||||
expect( timeStampedActiveFilter.value ).toHaveLength( 2 );
|
||||
expect( timeStampedActiveFilter.value[ 0 ] ).toContain( 'T00:00:00+00:00' );
|
||||
expect( timeStampedActiveFilter.value[ 1 ] ).toContain( 'T23:59:59+00:00' );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { find, forEach, isNull } from 'lodash';
|
||||
import { find, forEach, isNull, get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
|
@ -46,6 +46,42 @@ export function getFilterQuery( endpoint, query ) {
|
|||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timestamp to advanced filter parameters involving date. The api
|
||||
* expects a timestamp for these values similar to `before` and `after`.
|
||||
*
|
||||
* @param {object} config - advancedFilters config object.
|
||||
* @param {object} activeFilter - an active filter.
|
||||
* @returns {object} - an active filter with timestamp added to date values.
|
||||
*/
|
||||
export function timeStampFilterDates( config, activeFilter ) {
|
||||
const advancedFilterConfig = config.filters[ activeFilter.key ];
|
||||
if ( 'Date' !== get( advancedFilterConfig, [ 'input', 'component' ] ) ) {
|
||||
return activeFilter;
|
||||
}
|
||||
|
||||
const { rule, value } = activeFilter;
|
||||
const timeOfDayMap = {
|
||||
after: 'start',
|
||||
before: 'end',
|
||||
};
|
||||
// If the value is an array, it signifies "between" values which must have a timestamp
|
||||
// appended to each value.
|
||||
if ( Array.isArray( value ) ) {
|
||||
const [ after, before ] = value;
|
||||
return Object.assign( {}, activeFilter, {
|
||||
value: [
|
||||
appendTimestamp( moment( after ), timeOfDayMap.after ),
|
||||
appendTimestamp( moment( before ), timeOfDayMap.before ),
|
||||
],
|
||||
} );
|
||||
}
|
||||
|
||||
return Object.assign( {}, activeFilter, {
|
||||
value: appendTimestamp( moment( value ), timeOfDayMap[ rule ] ),
|
||||
} );
|
||||
}
|
||||
|
||||
export function getQueryFromConfig( config, advancedFilters, query ) {
|
||||
const queryValue = query[ config.param ];
|
||||
|
||||
|
@ -60,7 +96,7 @@ export function getQueryFromConfig( config, advancedFilters, query ) {
|
|||
return {};
|
||||
}
|
||||
|
||||
return activeFilters.reduce(
|
||||
return activeFilters.map( filter => timeStampFilterDates( advancedFilters, filter ) ).reduce(
|
||||
( result, activeFilter ) => {
|
||||
const { key, rule, value } = activeFilter;
|
||||
result[ getUrlKey( key, rule ) ] = value;
|
||||
|
@ -284,7 +320,7 @@ export function getReportTableQuery( endpoint, urlQuery, query ) {
|
|||
|
||||
return {
|
||||
orderby: urlQuery.orderby || 'date',
|
||||
order: urlQuery.order || 'asc',
|
||||
order: urlQuery.order || 'desc',
|
||||
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
|
||||
before: appendTimestamp( datesFromQuery.primary.before, 'end' ),
|
||||
page: urlQuery.page || 1,
|
||||
|
|
|
@ -14,13 +14,14 @@ import { stringifyQuery } from '@woocommerce/navigation';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { isResourcePrefix, getResourceIdentifier, getResourceName } from '../utils';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'category-query' ) );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
const query = getResourceIdentifier( resourceName );
|
||||
const url = `/wc/v3/products/categories${ stringifyQuery( query ) }`;
|
||||
const url = NAMESPACE + `/products/categories${ stringifyQuery( query ) }`;
|
||||
|
||||
try {
|
||||
const categories = await fetch( {
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { SECOND, MINUTE } from '@fresh-data/framework';
|
||||
import { MINUTE } from '@fresh-data/framework';
|
||||
|
||||
export const NAMESPACE = '/wc/v3';
|
||||
export const NAMESPACE = '/wc/v4';
|
||||
|
||||
export const DEFAULT_REQUIREMENT = {
|
||||
timeout: 5 * SECOND,
|
||||
timeout: 1 * MINUTE,
|
||||
freshness: 5 * MINUTE,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import operations from './operations';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
operations,
|
||||
selectors,
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isResourcePrefix, getResourceIdentifier, getResourceName } from '../utils';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'customers-query' ) );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
const query = getResourceIdentifier( resourceName );
|
||||
const url = `${ NAMESPACE }/customers${ stringifyQuery( query ) }`;
|
||||
|
||||
try {
|
||||
const customers = await fetch( { path: url } );
|
||||
const ids = customers.map( customer => customer.id );
|
||||
const customerResources = customers.reduce( ( resources, customer ) => {
|
||||
resources[ getResourceName( 'customer', customer.id ) ] = { data: customer };
|
||||
return resources;
|
||||
}, {} );
|
||||
|
||||
return {
|
||||
[ resourceName ]: {
|
||||
data: ids,
|
||||
},
|
||||
...customerResources,
|
||||
};
|
||||
} catch ( error ) {
|
||||
return { [ resourceName ]: { error } };
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getCustomers = ( getResource, requireResource ) => (
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
const resourceName = getResourceName( 'customers-query', query );
|
||||
const ids = requireResource( requirement, resourceName ).data || [];
|
||||
return ids.map( id => getResource( getResourceName( 'customer', id ) ).data || {} );
|
||||
};
|
||||
|
||||
const getCustomersError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'customers-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isGetCustomersRequesting = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'customers-query', query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
||||
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getCustomers,
|
||||
getCustomersError,
|
||||
isGetCustomersRequesting,
|
||||
};
|
|
@ -26,6 +26,7 @@ const typeEndpointMap = {
|
|||
'report-items-query-downloads': 'downloads',
|
||||
'report-items-query-customers': 'customers',
|
||||
'report-items-query-stock': 'stock',
|
||||
'report-items-query-performance-indicators': 'performance-indicators',
|
||||
};
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
|
|
|
@ -40,6 +40,7 @@ function updateCurrentUserData( resourceNames, data, fetch ) {
|
|||
'revenue_report_columns',
|
||||
'taxes_report_columns',
|
||||
'variations_report_columns',
|
||||
'dashboard_performance_indicators',
|
||||
'dashboard_charts',
|
||||
'dashboard_chart_type',
|
||||
'dashboard_chart_interval',
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import categories from './categories';
|
||||
import customers from './customers';
|
||||
import notes from './notes';
|
||||
import orders from './orders';
|
||||
import reportItems from './reports/items';
|
||||
|
@ -19,7 +18,6 @@ function createWcApiSpec() {
|
|||
},
|
||||
selectors: {
|
||||
...categories.selectors,
|
||||
...customers.selectors,
|
||||
...notes.selectors,
|
||||
...orders.selectors,
|
||||
...reportItems.selectors,
|
||||
|
@ -31,7 +29,6 @@ function createWcApiSpec() {
|
|||
read( resourceNames ) {
|
||||
return [
|
||||
...categories.operations.read( resourceNames ),
|
||||
...customers.operations.read( resourceNames ),
|
||||
...notes.operations.read( resourceNames ),
|
||||
...orders.operations.read( resourceNames ),
|
||||
...reportItems.operations.read( resourceNames ),
|
||||
|
|
|
@ -22,7 +22,7 @@ Properties of all the charts available for that report.
|
|||
|
||||
The endpoint to use in API calls to populate the Summary Numbers.
|
||||
For example, if `taxes` is provided, data will be fetched from the report
|
||||
`taxes` endpoint (ie: `/wc/v3/reports/taxes/stats`). If the provided endpoint
|
||||
`taxes` endpoint (ie: `/wc/v4/reports/taxes/stats`). If the provided endpoint
|
||||
doesn't exist, an error will be shown to the user with `ReportError`.
|
||||
|
||||
### `query`
|
||||
|
|
|
@ -20,7 +20,7 @@ The key for user preferences settings for column visibility.
|
|||
|
||||
The endpoint to use in API calls to populate the table rows and summary.
|
||||
For example, if `taxes` is provided, data will be fetched from the report
|
||||
`taxes` endpoint (ie: `/wc/v3/reports/taxes` and `/wc/v3/reports/taxes/stats`).
|
||||
`taxes` endpoint (ie: `/wc/v4/reports/taxes` and `/wc/v4/reports/taxes/stats`).
|
||||
If the provided endpoint doesn't exist, an error will be shown to the user
|
||||
with `ReportError`.
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Props
|
|||
### `date`
|
||||
|
||||
- **Required**
|
||||
- Type: String
|
||||
- Type: One of type: string, object
|
||||
- Default: null
|
||||
|
||||
Date to use in the component.
|
||||
|
|
|
@ -139,6 +139,13 @@ The string to use as a query parameter when searching row items.
|
|||
|
||||
Url query parameter search function operates on
|
||||
|
||||
### `showMenu`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: `true`
|
||||
|
||||
Boolean to determine whether or not ellipsis menu is shown.
|
||||
|
||||
### `summary`
|
||||
|
||||
- Type: Array
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Admin_Notes_Controller extends WC_REST_CRUD_Controller {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Coupons Controller
|
||||
*
|
||||
* Handles requests to /coupons/*
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Coupons controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Coupons_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Coupons_Controller extends WC_REST_Coupons_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
}
|
|
@ -19,6 +19,13 @@ class WC_Admin_REST_Customers_Controller extends WC_REST_Customers_Controller {
|
|||
|
||||
// TODO Add support for guests here. See https://wp.me/p7bje6-1dM.
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Searches emails by partial search instead of a strict match.
|
||||
* See "search parameters" under https://codex.wordpress.org/Class_Reference/WP_User_Query.
|
||||
|
|
|
@ -17,6 +17,13 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class WC_Admin_REST_Data_Controller extends WC_REST_Data_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Return the list of data resources.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Data countries controller.
|
||||
*
|
||||
* Handles requests to the /data/countries endpoint.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST API Data countries controller class.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Data_Countries_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Data_Countries_Controller extends WC_REST_Data_Countries_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
}
|
|
@ -21,7 +21,7 @@ class WC_Admin_REST_Data_Download_Ips_Controller extends WC_REST_Data_Controller
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -15,7 +15,14 @@ defined( 'ABSPATH' ) || exit;
|
|||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Orders_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Orders_Stats_Controller extends WC_REST_Orders_Controller {
|
||||
class WC_Admin_REST_Orders_Controller extends WC_REST_Orders_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Product Categories Controller
|
||||
*
|
||||
* Handles requests to /products/categories.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product categories controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Product_Categories_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Product_Categories_Controller extends WC_REST_Product_Categories_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
}
|
|
@ -16,6 +16,12 @@ defined( 'ABSPATH' ) || exit;
|
|||
* @extends WC_REST_Product_Reviews_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Product_Reviews_Controller extends WC_REST_Product_Reviews_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
|
|
|
@ -17,6 +17,13 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class WC_Admin_REST_Products_Controller extends WC_REST_Products_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Adds properties that can be embed via ?_embed=1.
|
||||
*
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Categories_Controller extends WC_Admin_REST_Reports_
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -64,6 +64,7 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
*
|
||||
|
@ -135,7 +136,36 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the list of allowed reports, so that data can be loaded from third party extensions in addition to WooCommerce core.
|
||||
* Array items should be in format of array( 'slug' => 'downloads/stats', 'description' => '',
|
||||
* 'url' => '', and 'path' => '/wc-ext/v1/...'.
|
||||
*
|
||||
* @param array $endpoints The list of allowed reports..
|
||||
*/
|
||||
$reports = apply_filters( 'woocommerce_admin_reports', $reports );
|
||||
|
||||
foreach ( $reports as $report ) {
|
||||
if ( empty( $report['slug'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $report['path'] ) ) {
|
||||
$report['path'] = '/' . $this->namespace . '/reports/' . $report['slug'];
|
||||
}
|
||||
|
||||
// Allows a different admin page to be loaded here,
|
||||
// or allows an empty url if no report exists for a set of performance indicators.
|
||||
if ( ! isset( $report['url'] ) ) {
|
||||
if ( '/stats' === substr( $report['slug'], -6 ) ) {
|
||||
$url_slug = substr( $report['slug'], 0, -6 );
|
||||
} else {
|
||||
$url_slug = $report['slug'];
|
||||
}
|
||||
|
||||
$report['url'] = '/analytics/' . $url_slug;
|
||||
}
|
||||
|
||||
$item = $this->prepare_item_for_response( (object) $report, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
@ -154,6 +184,7 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
$data = array(
|
||||
'slug' => $report->slug,
|
||||
'description' => $report->description,
|
||||
'path' => $report->path,
|
||||
);
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
|
@ -165,7 +196,10 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
$response->add_links(
|
||||
array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $report->slug ) ),
|
||||
'href' => rest_url( $report->path ),
|
||||
),
|
||||
'report' => array(
|
||||
'href' => $report->url,
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
|
@ -208,6 +242,12 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
|||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'path' => array(
|
||||
'description' => __( 'API path.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Coupons_Controller extends WC_REST_Reports_Controlle
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -139,6 +139,8 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con
|
|||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'coupons_count' => array(
|
||||
'description' => __( 'Amount of coupons.', 'wc-admin' ),
|
||||
|
@ -147,10 +149,11 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con
|
|||
'readonly' => true,
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Amount of orders.', 'wc-admin' ),
|
||||
'description' => __( 'Amount of discounted orders.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Downloads_Files_Controller extends WC_REST_Reports_C
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -143,6 +143,7 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
|
|||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_Admin_REST_Report
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -147,18 +147,22 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_Admin_REST_Report
|
|||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'avg_order_value' => array(
|
||||
'description' => __( 'Average order value.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
),
|
||||
'avg_order_value' => array(
|
||||
'description' => __( 'Average order value.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'avg_items_per_order' => array(
|
||||
'description' => __( 'Average items per order', 'wc-admin' ),
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -31,6 +31,67 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
*/
|
||||
protected $rest_base = 'reports/performance-indicators';
|
||||
|
||||
/**
|
||||
* Contains a list of endpoints by report slug.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $endpoints = array();
|
||||
|
||||
/**
|
||||
* Contains a list of allowed stats.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowed_stats = array();
|
||||
|
||||
/**
|
||||
* Contains a list of stat labels.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $labels = array();
|
||||
|
||||
/**
|
||||
* Contains a list of endpoints by url.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $urls = array();
|
||||
|
||||
/**
|
||||
* Register the routes for reports.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/allowed',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_allowed_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_allowed_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
|
@ -46,14 +107,17 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all allowed stats that can be returned from this endpoint.
|
||||
* Get information such as allowed stats, stat labels, and endpoint data from stats reports.
|
||||
*
|
||||
* @return array
|
||||
* @return WP_Error|True
|
||||
*/
|
||||
public function get_allowed_stats() {
|
||||
global $wp_rest_server;
|
||||
private function get_indicator_data() {
|
||||
// Data already retrieved.
|
||||
if ( ! empty( $this->endpoints ) && ! empty( $this->labels ) && ! empty( $this->allowed_stats ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$request = new WP_REST_Request( 'GET', '/wc/v3/reports' );
|
||||
$request = new WP_REST_Request( 'GET', '/wc/v4/reports' );
|
||||
$response = rest_do_request( $request );
|
||||
$endpoints = $response->get_data();
|
||||
$allowed_stats = array();
|
||||
|
@ -63,9 +127,10 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
|
||||
foreach ( $endpoints as $endpoint ) {
|
||||
if ( '/stats' === substr( $endpoint['slug'], -6 ) ) {
|
||||
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/reports/' . $endpoint['slug'] );
|
||||
$request = new WP_REST_Request( 'OPTIONS', $endpoint['path'] );
|
||||
$response = rest_do_request( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$prefix = substr( $endpoint['slug'], 0, -6 );
|
||||
|
||||
if ( empty( $data['schema']['properties']['totals']['properties'] ) ) {
|
||||
|
@ -73,17 +138,113 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
}
|
||||
|
||||
foreach ( $data['schema']['properties']['totals']['properties'] as $property_key => $schema_info ) {
|
||||
$allowed_stats[] = $prefix . '/' . $property_key;
|
||||
if ( empty( $schema_info['indicator'] ) || ! $schema_info['indicator'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stat = $prefix . '/' . $property_key;
|
||||
$allowed_stats[] = $stat;
|
||||
|
||||
$this->labels[ $stat ] = trim( preg_replace( '/\W+/', ' ', $schema_info['description'] ) );
|
||||
$this->formats[ $stat ] = isset( $schema_info['format'] ) ? $schema_info['format'] : 'number';
|
||||
}
|
||||
|
||||
$this->endpoints[ $prefix ] = $endpoint['path'];
|
||||
$this->urls[ $prefix ] = $endpoint['_links']['report'][0]['href'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->allowed_stats = $allowed_stats;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of allowed performance indicators.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_allowed_items( $request ) {
|
||||
$indicator_data = $this->get_indicator_data();
|
||||
if ( is_wp_error( $indicator_data ) ) {
|
||||
return $indicator_data;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
foreach ( $this->allowed_stats as $stat ) {
|
||||
$pieces = $this->get_stats_parts( $stat );
|
||||
$report = $pieces[0];
|
||||
$chart = $pieces[1];
|
||||
$data[] = (object) array(
|
||||
'stat' => $stat,
|
||||
'chart' => $chart,
|
||||
'label' => $this->labels[ $stat ],
|
||||
);
|
||||
}
|
||||
|
||||
usort( $data, array( $this, 'sort' ) );
|
||||
|
||||
$objects = array();
|
||||
foreach ( $data as $item ) {
|
||||
$prepared = $this->prepare_item_for_response( $item, $request );
|
||||
$objects[] = $this->prepare_response_for_collection( $prepared );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $objects );
|
||||
$response->header( 'X-WP-Total', count( $data ) );
|
||||
$response->header( 'X-WP-TotalPages', 1 );
|
||||
|
||||
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list of stats. Sorted by custom arrangement.
|
||||
*
|
||||
* @see https://github.com/woocommerce/wc-admin/issues/1282
|
||||
* @param object $a First item.
|
||||
* @param object $b Second item.
|
||||
* @return order
|
||||
*/
|
||||
public function sort( $a, $b ) {
|
||||
/**
|
||||
* Filter the list of allowed stats that can be returned via the performance indiciator endpoint.
|
||||
* Custom ordering for store performance indicators.
|
||||
*
|
||||
* @param array $allowed_stats The list of allowed stats.
|
||||
* @see https://github.com/woocommerce/wc-admin/issues/1282
|
||||
* @param array $indicators A list of ordered indicators.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_performance_indicators_allowed_stats', $allowed_stats );
|
||||
$stat_order = apply_filters(
|
||||
'woocommerce_rest_report_sort_performance_indicators',
|
||||
array(
|
||||
'revenue/gross_revenue',
|
||||
'revenue/net_revenue',
|
||||
'orders/orders_count',
|
||||
'orders/avg_order_value',
|
||||
'products/items_sold',
|
||||
'revenue/refunds',
|
||||
'coupons/orders_count',
|
||||
'coupons/amount',
|
||||
'taxes/total_tax',
|
||||
'taxes/order_tax',
|
||||
'taxes/shipping_tax',
|
||||
'revenue/shipping',
|
||||
'downloads/download_count',
|
||||
)
|
||||
);
|
||||
|
||||
$a = array_search( $a->stat, $stat_order );
|
||||
$b = array_search( $b->stat, $stat_order );
|
||||
|
||||
if ( false === $a && false === $b ) {
|
||||
return 0;
|
||||
} elseif ( false === $a ) {
|
||||
return 1;
|
||||
} elseif ( false === $b ) {
|
||||
return -1;
|
||||
} else {
|
||||
return $a - $b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,10 +254,9 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$allowed_stats = $this->get_allowed_stats();
|
||||
|
||||
if ( is_wp_error( $allowed_stats ) ) {
|
||||
return $allowed_stats;
|
||||
$indicator_data = $this->get_indicator_data();
|
||||
if ( is_wp_error( $indicator_data ) ) {
|
||||
return $indicator_data;
|
||||
}
|
||||
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
|
@ -105,51 +265,50 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
}
|
||||
|
||||
$stats = array();
|
||||
foreach ( $query_args['stats'] as $stat_request ) {
|
||||
foreach ( $query_args['stats'] as $stat ) {
|
||||
$is_error = false;
|
||||
|
||||
$pieces = explode( '/', $stat_request );
|
||||
$endpoint = $pieces[0];
|
||||
$stat = $pieces[1];
|
||||
$pieces = $this->get_stats_parts( $stat );
|
||||
$report = $pieces[0];
|
||||
$chart = $pieces[1];
|
||||
|
||||
if ( ! in_array( $stat_request, $allowed_stats ) ) {
|
||||
if ( ! in_array( $stat, $this->allowed_stats ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the list of allowed endpoints, so that data can be loaded from extensions rather than core.
|
||||
* These should be in the format of slug => path. Example: `bookings` => `/wc-bookings/v1/reports/bookings/stats`.
|
||||
*
|
||||
* @param array $endpoints The list of allowed endpoints.
|
||||
*/
|
||||
$stats_endpoints = apply_filters( 'woocommerce_admin_performance_indicators_stats_endpoints', array() );
|
||||
if ( ! empty( $stats_endpoints [ $endpoint ] ) ) {
|
||||
$request_url = $stats_endpoints [ $endpoint ];
|
||||
} else {
|
||||
$request_url = '/wc/v3/reports/' . $endpoint . '/stats';
|
||||
}
|
||||
|
||||
$request = new WP_REST_Request( 'GET', $request_url );
|
||||
$request_url = $this->endpoints[ $report ];
|
||||
$request = new WP_REST_Request( 'GET', $request_url );
|
||||
$request->set_param( 'before', $query_args['before'] );
|
||||
$request->set_param( 'after', $query_args['after'] );
|
||||
|
||||
$response = rest_do_request( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
if ( 200 !== $response->get_status() || empty( $data['totals'][ $stat ] ) ) {
|
||||
$data = $response->get_data();
|
||||
$format = $this->formats[ $stat ];
|
||||
$label = $this->labels[ $stat ];
|
||||
|
||||
if ( 200 !== $response->get_status() || ! isset( $data['totals'][ $chart ] ) ) {
|
||||
$stats[] = (object) array(
|
||||
'stat' => $stat_request,
|
||||
'value' => null,
|
||||
'stat' => $stat,
|
||||
'chart' => $chart,
|
||||
'label' => $label,
|
||||
'format' => $format,
|
||||
'value' => null,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$stats[] = (object) array(
|
||||
'stat' => $stat_request,
|
||||
'value' => $data['totals'][ $stat ],
|
||||
'stat' => $stat,
|
||||
'chart' => $chart,
|
||||
'label' => $label,
|
||||
'format' => $format,
|
||||
'value' => $data['totals'][ $chart ],
|
||||
);
|
||||
}
|
||||
|
||||
usort( $stats, array( $this, 'sort' ) );
|
||||
|
||||
$objects = array();
|
||||
foreach ( $stats as $stat ) {
|
||||
$data = $this->prepare_item_for_response( $stat, $request );
|
||||
|
@ -201,25 +360,52 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $object ) {
|
||||
$pieces = explode( '/', $object->stat );
|
||||
$pieces = $this->get_stats_parts( $object->stat );
|
||||
$endpoint = $pieces[0];
|
||||
$stat = $pieces[1];
|
||||
$url = $this->urls[ $endpoint ];
|
||||
|
||||
$links = array(
|
||||
'api' => array(
|
||||
'href' => rest_url( $this->endpoints[ $endpoint ] ),
|
||||
),
|
||||
'report' => array(
|
||||
'href' => rest_url( sprintf( '/%s/reports/%s/stats', $this->namespace, $endpoint ) ),
|
||||
'href' => ! empty( $url ) ? $url : '',
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the endpoint part of a stat request (prefix) and the actual stat total we want.
|
||||
* To allow extensions to namespace (example: fue/emails/sent), we break on the last forward slash.
|
||||
*
|
||||
* @param string $full_stat A stat request string like orders/avg_order_value or fue/emails/sent.
|
||||
* @return array Containing the prefix (endpoint) and suffix (stat).
|
||||
*/
|
||||
private function get_stats_parts( $full_stat ) {
|
||||
$endpoint = substr( $full_stat, 0, strrpos( $full_stat, '/' ) );
|
||||
$stat = substr( $full_stat, ( strrpos( $full_stat, '/' ) + 1 ) );
|
||||
return array(
|
||||
$endpoint,
|
||||
$stat,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$indicator_data = $this->get_indicator_data();
|
||||
if ( is_wp_error( $indicator_data ) ) {
|
||||
$allowed_stats = array();
|
||||
} else {
|
||||
$allowed_stats = $this->allowed_stats;
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'report_performance_indicator',
|
||||
|
@ -230,6 +416,26 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'enum' => $allowed_stats,
|
||||
),
|
||||
'chart' => array(
|
||||
'description' => __( 'The specific chart this stat referrers to.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'label' => array(
|
||||
'description' => __( 'Human readable label for the stat.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'format' => array(
|
||||
'description' => __( 'Format of the stat.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'enum' => array( 'number', 'currency' ),
|
||||
),
|
||||
'value' => array(
|
||||
'description' => __( 'Value of the stat. Returns null if the stat does not exist or cannot be loaded.', 'wc-admin' ),
|
||||
|
@ -243,17 +449,29 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
|
|||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema for the list of allowed performance indicators.
|
||||
*
|
||||
* @return array $schema
|
||||
*/
|
||||
public function get_public_allowed_item_schema() {
|
||||
$schema = $this->get_public_item_schema();
|
||||
unset( $schema['properties']['value'] );
|
||||
unset( $schema['properties']['format'] );
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$allowed_stats = $this->get_allowed_stats();
|
||||
if ( is_wp_error( $allowed_stats ) ) {
|
||||
$indicator_data = $this->get_indicator_data();
|
||||
if ( is_wp_error( $indicator_data ) ) {
|
||||
$allowed_stats = __( 'There was an issue loading the report endpoints', 'wc-admin' );
|
||||
} else {
|
||||
$allowed_stats = implode( ', ', $allowed_stats );
|
||||
$allowed_stats = implode( ', ', $this->allowed_stats );
|
||||
}
|
||||
|
||||
$params = array();
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -152,12 +152,14 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co
|
|||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
),
|
||||
'net_revenue' => array(
|
||||
'description' => __( 'Net revenue.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Number of orders.', 'wc-admin' ),
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -137,51 +137,61 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con
|
|||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'net_revenue' => array(
|
||||
'description' => __( 'Net revenue.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'coupons' => array(
|
||||
'description' => __( 'Total of coupons.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'shipping' => array(
|
||||
'description' => __( 'Total of shipping.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'taxes' => array(
|
||||
'description' => __( 'Total of taxes.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'refunds' => array(
|
||||
'description' => __( 'Total of refunds.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||
'description' => __( 'Amount of orders.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'num_items_sold' => array(
|
||||
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||
'description' => __( 'Items sold.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'products' => array(
|
||||
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||
'description' => __( 'Products sold.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -51,6 +51,23 @@ class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller
|
|||
|
||||
if ( 'date' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'date ID';
|
||||
} elseif ( 'stock_status' === $args['orderby'] ) {
|
||||
$args['meta_query'] = array( // WPCS: slow query ok.
|
||||
'relation' => 'AND',
|
||||
'_stock_status' => array(
|
||||
'key' => '_stock_status',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
'_stock' => array(
|
||||
'key' => '_stock',
|
||||
'compare' => 'EXISTS',
|
||||
'type' => 'NUMERIC',
|
||||
),
|
||||
);
|
||||
$args['orderby'] = array(
|
||||
'_stock_status' => $args['order'],
|
||||
'_stock' => 'desc' === $args['order'] ? 'asc' : 'desc',
|
||||
);
|
||||
} elseif ( 'stock_quantity' === $args['orderby'] ) {
|
||||
$args['meta_key'] = '_stock'; // WPCS: slow query ok.
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
|
@ -353,8 +370,9 @@ class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller
|
|||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'stock_quantity',
|
||||
'default' => 'stock_status',
|
||||
'enum' => array(
|
||||
'stock_status',
|
||||
'stock_quantity',
|
||||
'date',
|
||||
'id',
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Taxes_Controller extends WC_REST_Reports_Controller
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
@ -167,18 +167,24 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr
|
|||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'order_tax' => array(
|
||||
'description' => __( 'Order tax.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'shipping_tax' => array(
|
||||
'description' => __( 'Shipping tax.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Amount of orders.', 'wc-admin' ),
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
|
@ -22,7 +22,7 @@ class WC_Admin_REST_System_Status_Tools_Controller extends WC_REST_System_Status
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* A list of available tools for use in the system status section.
|
||||
|
@ -55,7 +55,7 @@ class WC_Admin_REST_System_Status_Tools_Controller extends WC_REST_System_Status
|
|||
|
||||
switch ( $tool ) {
|
||||
case 'rebuild_stats':
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::queue_order_stats_repopulate_database();
|
||||
WC_Admin_Api_Init::regenerate_report_data();
|
||||
$message = __( 'Rebuilding reports data in the background . . .', 'wc-admin' );
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -12,6 +12,38 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class WC_Admin_Api_Init {
|
||||
|
||||
/**
|
||||
* Action hook for reducing a range of batches down to single actions.
|
||||
*/
|
||||
const QUEUE_BATCH_ACTION = 'wc-admin_queue_batches';
|
||||
|
||||
/**
|
||||
* Action hook for queuing an action after another is complete.
|
||||
*/
|
||||
const QUEUE_DEPEDENT_ACTION = 'wc-admin_queue_dependent_action';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of customers.
|
||||
*/
|
||||
const CUSTOMERS_BATCH_ACTION = 'wc-admin_process_customers_batch';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of orders.
|
||||
*/
|
||||
const ORDERS_BATCH_ACTION = 'wc-admin_process_orders_batch';
|
||||
|
||||
/**
|
||||
* Action hook for initializing the orders lookup batch creation.
|
||||
*/
|
||||
const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init';
|
||||
|
||||
/**
|
||||
* Queue instance.
|
||||
*
|
||||
* @var WC_Queue_Interface
|
||||
*/
|
||||
protected static $queue = null;
|
||||
|
||||
/**
|
||||
* Boostrap REST API.
|
||||
*/
|
||||
|
@ -30,9 +62,41 @@ class WC_Admin_Api_Init {
|
|||
// Initialize Orders data store class's static vars.
|
||||
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 );
|
||||
// Initialize Customers Report data store sync hooks.
|
||||
// Note: we need to hook into 'wp' before `wc_current_user_is_active`.
|
||||
// Note: we need to hook in before `wc_current_user_is_active`.
|
||||
// See: https://github.com/woocommerce/woocommerce/blob/942615101ba00c939c107c3a4820c3d466864872/includes/wc-user-functions.php#L749.
|
||||
add_action( 'wp', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ), 9 );
|
||||
add_action( 'wp_loaded', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ) );
|
||||
|
||||
// Initialize scheduled action handlers.
|
||||
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 );
|
||||
add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 2 );
|
||||
add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) );
|
||||
|
||||
// Add currency symbol to orders endpoint response.
|
||||
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue instance.
|
||||
*
|
||||
* @return WC_Queue_Interface
|
||||
*/
|
||||
public static function queue() {
|
||||
if ( is_null( self::$queue ) ) {
|
||||
self::$queue = WC()->queue();
|
||||
}
|
||||
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set queue instance.
|
||||
*
|
||||
* @param WC_Queue_Interface $queue Queue instance.
|
||||
*/
|
||||
public static function set_queue( $queue ) {
|
||||
self::$queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,11 +161,14 @@ class WC_Admin_Api_Init {
|
|||
*/
|
||||
public function rest_api_init() {
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-admin-notes-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-coupons-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-customers-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-countries-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-download-ips-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-orders-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-orders-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-products-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-categories-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-reviews-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php';
|
||||
|
@ -127,11 +194,14 @@ class WC_Admin_Api_Init {
|
|||
'woocommerce_admin_rest_controllers',
|
||||
array(
|
||||
'WC_Admin_REST_Admin_Notes_Controller',
|
||||
'WC_Admin_REST_Coupons_Controller',
|
||||
'WC_Admin_REST_Customers_Controller',
|
||||
'WC_Admin_REST_Data_Controller',
|
||||
'WC_Admin_REST_Data_Countries_Controller',
|
||||
'WC_Admin_REST_Data_Download_Ips_Controller',
|
||||
'WC_Admin_REST_Orders_Stats_Controller',
|
||||
'WC_Admin_REST_Orders_Controller',
|
||||
'WC_Admin_REST_Products_Controller',
|
||||
'WC_Admin_REST_Product_Categories_Controller',
|
||||
'WC_Admin_REST_Product_Reviews_Controller',
|
||||
'WC_Admin_REST_Reports_Controller',
|
||||
'WC_Admin_REST_System_Status_Tools_Controller',
|
||||
|
@ -169,112 +239,134 @@ class WC_Admin_Api_Init {
|
|||
* @return array
|
||||
*/
|
||||
public static function filter_rest_endpoints( $endpoints ) {
|
||||
// Override GET /wc/v3/system_status/tools.
|
||||
if ( isset( $endpoints['/wc/v3/system_status/tools'] )
|
||||
&& isset( $endpoints['/wc/v3/system_status/tools'][1] )
|
||||
&& isset( $endpoints['/wc/v3/system_status/tools'][0] )
|
||||
&& $endpoints['/wc/v3/system_status/tools'][1]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
// Override GET /wc/v4/system_status/tools.
|
||||
if ( isset( $endpoints['/wc/v4/system_status/tools'] )
|
||||
&& isset( $endpoints['/wc/v4/system_status/tools'][1] )
|
||||
&& isset( $endpoints['/wc/v4/system_status/tools'][0] )
|
||||
&& $endpoints['/wc/v4/system_status/tools'][1]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/system_status/tools'][0] = $endpoints['/wc/v3/system_status/tools'][1];
|
||||
$endpoints['/wc/v4/system_status/tools'][0] = $endpoints['/wc/v4/system_status/tools'][1];
|
||||
}
|
||||
// // Override GET & PUT for /wc/v3/system_status/tools.
|
||||
if ( isset( $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'] )
|
||||
&& isset( $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][3] )
|
||||
&& isset( $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][2] )
|
||||
&& $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][2]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
&& $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][3]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
// // Override GET & PUT for /wc/v4/system_status/tools.
|
||||
if ( isset( $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'] )
|
||||
&& isset( $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][3] )
|
||||
&& isset( $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][2] )
|
||||
&& $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][2]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
&& $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][3]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][0] = $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][2];
|
||||
$endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][1] = $endpoints['/wc/v3/system_status/tools/(?P<id>[\w-]+)'][3];
|
||||
$endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][0] = $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][2];
|
||||
$endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][1] = $endpoints['/wc/v4/system_status/tools/(?P<id>[\w-]+)'][3];
|
||||
}
|
||||
|
||||
// Override GET /wc/v3/reports.
|
||||
if ( isset( $endpoints['/wc/v3/reports'] )
|
||||
&& isset( $endpoints['/wc/v3/reports'][1] )
|
||||
&& isset( $endpoints['/wc/v3/reports'][0] )
|
||||
&& $endpoints['/wc/v3/reports'][1]['callback'][0] instanceof WC_Admin_REST_Reports_Controller
|
||||
// Override GET /wc/v4/reports.
|
||||
if ( isset( $endpoints['/wc/v4/reports'] )
|
||||
&& isset( $endpoints['/wc/v4/reports'][1] )
|
||||
&& isset( $endpoints['/wc/v4/reports'][0] )
|
||||
&& $endpoints['/wc/v4/reports'][1]['callback'][0] instanceof WC_Admin_REST_Reports_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/reports'][0] = $endpoints['/wc/v3/reports'][1];
|
||||
$endpoints['/wc/v4/reports'][0] = $endpoints['/wc/v4/reports'][1];
|
||||
}
|
||||
|
||||
// Override /wc/v3/customers.
|
||||
if ( isset( $endpoints['/wc/v3/customers'] )
|
||||
&& isset( $endpoints['/wc/v3/customers'][3] )
|
||||
&& isset( $endpoints['/wc/v3/customers'][2] )
|
||||
&& $endpoints['/wc/v3/customers'][2]['callback'][0] instanceof WC_Admin_REST_Customers_Controller
|
||||
&& $endpoints['/wc/v3/customers'][3]['callback'][0] instanceof WC_Admin_REST_Customers_Controller
|
||||
// Override /wc/v4/coupons.
|
||||
if ( isset( $endpoints['/wc/v4/coupons'] )
|
||||
&& isset( $endpoints['/wc/v4/coupons'][3] )
|
||||
&& isset( $endpoints['/wc/v4/coupons'][2] )
|
||||
&& $endpoints['/wc/v4/coupons'][2]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
&& $endpoints['/wc/v4/coupons'][3]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/customers'][0] = $endpoints['/wc/v3/customers'][2];
|
||||
$endpoints['/wc/v3/customers'][1] = $endpoints['/wc/v3/customers'][3];
|
||||
$endpoints['/wc/v4/coupons'][0] = $endpoints['/wc/v4/coupons'][2];
|
||||
$endpoints['/wc/v4/coupons'][1] = $endpoints['/wc/v4/coupons'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v3/orders/$id.
|
||||
if ( isset( $endpoints['/wc/v3/orders/(?P<id>[\d]+)'] )
|
||||
&& isset( $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][5] )
|
||||
&& isset( $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][4] )
|
||||
&& isset( $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][3] )
|
||||
&& $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][3]['callback'][0] instanceof WC_Admin_REST_Orders_Stats_Controller
|
||||
&& $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][4]['callback'][0] instanceof WC_Admin_REST_Orders_Stats_Controller
|
||||
&& $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][5]['callback'][0] instanceof WC_Admin_REST_Orders_Stats_Controller
|
||||
// Override /wc/v4/customers.
|
||||
if ( isset( $endpoints['/wc/v4/customers'] )
|
||||
&& isset( $endpoints['/wc/v4/customers'][3] )
|
||||
&& isset( $endpoints['/wc/v4/customers'][2] )
|
||||
&& $endpoints['/wc/v4/customers'][2]['callback'][0] instanceof WC_Admin_REST_Customers_Controller
|
||||
&& $endpoints['/wc/v4/customers'][3]['callback'][0] instanceof WC_Admin_REST_Customers_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/orders/(?P<id>[\d]+)'][0] = $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][3];
|
||||
$endpoints['/wc/v3/orders/(?P<id>[\d]+)'][1] = $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][4];
|
||||
$endpoints['/wc/v3/orders/(?P<id>[\d]+)'][2] = $endpoints['/wc/v3/orders/(?P<id>[\d]+)'][5];
|
||||
$endpoints['/wc/v4/customers'][0] = $endpoints['/wc/v4/customers'][2];
|
||||
$endpoints['/wc/v4/customers'][1] = $endpoints['/wc/v4/customers'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v3orders.
|
||||
if ( isset( $endpoints['/wc/v3/orders'] )
|
||||
&& isset( $endpoints['/wc/v3/orders'][3] )
|
||||
&& isset( $endpoints['/wc/v3/orders'][2] )
|
||||
&& $endpoints['/wc/v3/orders'][2]['callback'][0] instanceof WC_Admin_REST_Orders_Stats_Controller
|
||||
&& $endpoints['/wc/v3/orders'][3]['callback'][0] instanceof WC_Admin_REST_Orders_Stats_Controller
|
||||
// Override /wc/v4/orders/$id.
|
||||
if ( isset( $endpoints['/wc/v4/orders/(?P<id>[\d]+)'] )
|
||||
&& isset( $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][5] )
|
||||
&& isset( $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][4] )
|
||||
&& isset( $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][3] )
|
||||
&& $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][3]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
&& $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][4]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
&& $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][5]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/orders'][0] = $endpoints['/wc/v3/orders'][2];
|
||||
$endpoints['/wc/v3/orders'][1] = $endpoints['/wc/v3/orders'][3];
|
||||
$endpoints['/wc/v4/orders/(?P<id>[\d]+)'][0] = $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][3];
|
||||
$endpoints['/wc/v4/orders/(?P<id>[\d]+)'][1] = $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][4];
|
||||
$endpoints['/wc/v4/orders/(?P<id>[\d]+)'][2] = $endpoints['/wc/v4/orders/(?P<id>[\d]+)'][5];
|
||||
}
|
||||
|
||||
// Override /wc/v3/data.
|
||||
if ( isset( $endpoints['/wc/v3/data'] )
|
||||
&& isset( $endpoints['/wc/v3/data'][1] )
|
||||
&& $endpoints['/wc/v3/data'][1]['callback'][0] instanceof WC_Admin_REST_Data_Controller
|
||||
// Override /wc/v4/orders.
|
||||
if ( isset( $endpoints['/wc/v4/orders'] )
|
||||
&& isset( $endpoints['/wc/v4/orders'][3] )
|
||||
&& isset( $endpoints['/wc/v4/orders'][2] )
|
||||
&& $endpoints['/wc/v4/orders'][2]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
&& $endpoints['/wc/v4/orders'][3]['callback'][0] instanceof WC_Admin_REST_Orders_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/data'][0] = $endpoints['/wc/v3/data'][1];
|
||||
$endpoints['/wc/v4/orders'][0] = $endpoints['/wc/v4/orders'][2];
|
||||
$endpoints['/wc/v4/orders'][1] = $endpoints['/wc/v4/orders'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v3/products.
|
||||
if ( isset( $endpoints['/wc/v3/products'] )
|
||||
&& isset( $endpoints['/wc/v3/products'][3] )
|
||||
&& isset( $endpoints['/wc/v3/products'][2] )
|
||||
&& $endpoints['/wc/v3/products'][2]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v3/products'][3]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
// Override /wc/v4/data.
|
||||
if ( isset( $endpoints['/wc/v4/data'] )
|
||||
&& isset( $endpoints['/wc/v4/data'][1] )
|
||||
&& $endpoints['/wc/v4/data'][1]['callback'][0] instanceof WC_Admin_REST_Data_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/products'][0] = $endpoints['/wc/v3/products'][2];
|
||||
$endpoints['/wc/v3/products'][1] = $endpoints['/wc/v3/products'][3];
|
||||
$endpoints['/wc/v4/data'][0] = $endpoints['/wc/v4/data'][1];
|
||||
}
|
||||
|
||||
// Override /wc/v3/products/$id.
|
||||
if ( isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'] )
|
||||
&& isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'][5] )
|
||||
&& isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'][4] )
|
||||
&& isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'][3] )
|
||||
&& $endpoints['/wc/v3/products/(?P<id>[\d]+)'][3]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v3/products/(?P<id>[\d]+)'][4]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v3/products/(?P<id>[\d]+)'][5]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
// Override /wc/v4/products.
|
||||
if ( isset( $endpoints['/wc/v4/products'] )
|
||||
&& isset( $endpoints['/wc/v4/products'][3] )
|
||||
&& isset( $endpoints['/wc/v4/products'][2] )
|
||||
&& $endpoints['/wc/v4/products'][2]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v4/products'][3]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/products/(?P<id>[\d]+)'][0] = $endpoints['/wc/v3/products/(?P<id>[\d]+)'][3];
|
||||
$endpoints['/wc/v3/products/(?P<id>[\d]+)'][1] = $endpoints['/wc/v3/products/(?P<id>[\d]+)'][4];
|
||||
$endpoints['/wc/v3/products/(?P<id>[\d]+)'][2] = $endpoints['/wc/v3/products/(?P<id>[\d]+)'][5];
|
||||
$endpoints['/wc/v4/products'][0] = $endpoints['/wc/v4/products'][2];
|
||||
$endpoints['/wc/v4/products'][1] = $endpoints['/wc/v4/products'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v3/products/reviews.
|
||||
if ( isset( $endpoints['/wc/v3/products/reviews'] )
|
||||
&& isset( $endpoints['/wc/v3/products/reviews'][3] )
|
||||
&& isset( $endpoints['/wc/v3/products/reviews'][2] )
|
||||
&& $endpoints['/wc/v3/products/reviews'][2]['callback'][0] instanceof WC_Admin_REST_Product_Reviews_Controller
|
||||
&& $endpoints['/wc/v3/products/reviews'][3]['callback'][0] instanceof WC_Admin_REST_Product_Reviews_Controller
|
||||
// Override /wc/v4/products/$id.
|
||||
if ( isset( $endpoints['/wc/v4/products/(?P<id>[\d]+)'] )
|
||||
&& isset( $endpoints['/wc/v4/products/(?P<id>[\d]+)'][5] )
|
||||
&& isset( $endpoints['/wc/v4/products/(?P<id>[\d]+)'][4] )
|
||||
&& isset( $endpoints['/wc/v4/products/(?P<id>[\d]+)'][3] )
|
||||
&& $endpoints['/wc/v4/products/(?P<id>[\d]+)'][3]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v4/products/(?P<id>[\d]+)'][4]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
&& $endpoints['/wc/v4/products/(?P<id>[\d]+)'][5]['callback'][0] instanceof WC_Admin_REST_Products_Controller
|
||||
) {
|
||||
$endpoints['/wc/v3/products/reviews'][0] = $endpoints['/wc/v3/products/reviews'][2];
|
||||
$endpoints['/wc/v3/products/reviews'][1] = $endpoints['/wc/v3/products/reviews'][3];
|
||||
$endpoints['/wc/v4/products/(?P<id>[\d]+)'][0] = $endpoints['/wc/v4/products/(?P<id>[\d]+)'][3];
|
||||
$endpoints['/wc/v4/products/(?P<id>[\d]+)'][1] = $endpoints['/wc/v4/products/(?P<id>[\d]+)'][4];
|
||||
$endpoints['/wc/v4/products/(?P<id>[\d]+)'][2] = $endpoints['/wc/v4/products/(?P<id>[\d]+)'][5];
|
||||
}
|
||||
|
||||
// Override /wc/v4/products/categories.
|
||||
if ( isset( $endpoints['/wc/v4/products/categories'] )
|
||||
&& isset( $endpoints['/wc/v4/products/categories'][3] )
|
||||
&& isset( $endpoints['/wc/v4/products/categories'][2] )
|
||||
&& $endpoints['/wc/v4/products/categories'][2]['callback'][0] instanceof WC_Admin_REST_Product_categories_Controller
|
||||
&& $endpoints['/wc/v4/products/categories'][3]['callback'][0] instanceof WC_Admin_REST_Product_categories_Controller
|
||||
) {
|
||||
$endpoints['/wc/v4/products/categories'][0] = $endpoints['/wc/v4/products/categories'][2];
|
||||
$endpoints['/wc/v4/products/categories'][1] = $endpoints['/wc/v4/products/categories'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v4/products/reviews.
|
||||
if ( isset( $endpoints['/wc/v4/products/reviews'] )
|
||||
&& isset( $endpoints['/wc/v4/products/reviews'][3] )
|
||||
&& isset( $endpoints['/wc/v4/products/reviews'][2] )
|
||||
&& $endpoints['/wc/v4/products/reviews'][2]['callback'][0] instanceof WC_Admin_REST_Product_Reviews_Controller
|
||||
&& $endpoints['/wc/v4/products/reviews'][3]['callback'][0] instanceof WC_Admin_REST_Product_Reviews_Controller
|
||||
) {
|
||||
$endpoints['/wc/v4/products/reviews'][0] = $endpoints['/wc/v4/products/reviews'][2];
|
||||
$endpoints['/wc/v4/products/reviews'][1] = $endpoints['/wc/v4/products/reviews'][3];
|
||||
}
|
||||
|
||||
return $endpoints;
|
||||
|
@ -286,9 +378,9 @@ class WC_Admin_Api_Init {
|
|||
public static function regenerate_report_data() {
|
||||
// Add registered customers to the lookup table before updating order stats
|
||||
// so that the orders can be associated with the `customer_id` column.
|
||||
self::customer_lookup_store_init();
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::queue_order_stats_repopulate_database();
|
||||
self::order_product_lookup_store_init();
|
||||
self::customer_lookup_batch_init();
|
||||
// Queue orders lookup to occur after customers lookup generation is done.
|
||||
self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,39 +414,54 @@ class WC_Admin_Api_Init {
|
|||
}
|
||||
|
||||
/**
|
||||
* Init orders product lookup store.
|
||||
*
|
||||
* @param WC_Background_Updater|null $updater Updater instance.
|
||||
* @return bool
|
||||
* Init order/product lookup tables update (in batches).
|
||||
*/
|
||||
public static function order_product_lookup_store_init( $updater = null ) {
|
||||
// TODO: this needs to be updated a bit, as it no longer runs as a part of WC_Install, there is no bg updater.
|
||||
global $wpdb;
|
||||
public static function orders_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => 1,
|
||||
'paginate' => true,
|
||||
)
|
||||
);
|
||||
$result = $order_query->get_orders();
|
||||
|
||||
$orders = get_transient( 'wc_update_350_all_orders' );
|
||||
if ( false === $orders ) {
|
||||
$orders = wc_get_orders(
|
||||
array(
|
||||
'limit' => -1,
|
||||
'return' => 'ids',
|
||||
)
|
||||
);
|
||||
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
|
||||
if ( 0 === $result->total ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process orders until close to running out of memory timeouts on large sites then requeue.
|
||||
foreach ( $orders as $order_id ) {
|
||||
$num_batches = ceil( $result->total / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::ORDERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of orders to update (stats and products).
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function orders_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => $batch_size,
|
||||
'page' => $batch_number,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
$order_ids = $order_query->get_orders();
|
||||
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
// TODO: schedule single order update if this fails?
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id );
|
||||
WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id );
|
||||
// Pop the order ID from the array for updating the transient later should we near memory exhaustion.
|
||||
unset( $orders[ $order_id ] );
|
||||
if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) {
|
||||
// Update the transient for the next run to avoid processing the same orders again.
|
||||
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id );
|
||||
WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,49 +472,144 @@ class WC_Admin_Api_Init {
|
|||
}
|
||||
|
||||
/**
|
||||
* Init customer lookup store.
|
||||
* Returns the batch size for regenerating reports.
|
||||
* Note: can differ per batch action.
|
||||
*
|
||||
* @param WC_Background_Updater|null $updater Updater instance.
|
||||
* @return bool
|
||||
* @param string $action Single batch action name.
|
||||
* @return int Batch size.
|
||||
*/
|
||||
public static function customer_lookup_store_init( $updater = null ) {
|
||||
// TODO: this needs to be updated a bit, as it no longer runs as a part of WC_Install, there is no bg updater.
|
||||
global $wpdb;
|
||||
public static function get_batch_size( $action ) {
|
||||
$batch_sizes = array(
|
||||
self::QUEUE_BATCH_ACTION => 100,
|
||||
self::CUSTOMERS_BATCH_ACTION => 25,
|
||||
self::ORDERS_BATCH_ACTION => 10,
|
||||
);
|
||||
$batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
|
||||
|
||||
// Backfill customer lookup table with registered customers.
|
||||
$customer_ids = get_transient( 'wc_update_350_all_customers' );
|
||||
/**
|
||||
* Filter the batch size for regenerating a report table.
|
||||
*
|
||||
* @param int $batch_size Batch size.
|
||||
* @param string $action Batch action name.
|
||||
*/
|
||||
return apply_filters( 'wc_admin_report_regenerate_batch_size', $batch_size, $action );
|
||||
}
|
||||
|
||||
if ( false === $customer_ids ) {
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'role' => 'customer',
|
||||
'number' => -1,
|
||||
)
|
||||
/**
|
||||
* Queue a large number of batch jobs, respecting the batch size limit.
|
||||
* Reduces a range of batches down to "single batch" jobs.
|
||||
*
|
||||
* @param int $range_start Starting batch number.
|
||||
* @param int $range_end Ending batch number.
|
||||
* @param string $single_batch_action Action to schedule for a single batch.
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_batches( $range_start, $range_end, $single_batch_action ) {
|
||||
$batch_size = self::get_batch_size( self::QUEUE_BATCH_ACTION );
|
||||
$range_size = 1 + ( $range_end - $range_start );
|
||||
$action_timestamp = time() + 5;
|
||||
|
||||
if ( $range_size > $batch_size ) {
|
||||
// If the current batch range is larger than a single batch,
|
||||
// split the range into $queue_batch_size chunks.
|
||||
$chunk_size = ceil( $range_size / $batch_size );
|
||||
|
||||
for ( $i = 0; $i < $batch_size; $i++ ) {
|
||||
$batch_start = $range_start + ( $i * $chunk_size );
|
||||
$batch_end = min( $range_end, $range_start + ( $chunk_size * ( $i + 1 ) ) - 1 );
|
||||
|
||||
self::queue()->schedule_single(
|
||||
$action_timestamp,
|
||||
self::QUEUE_BATCH_ACTION,
|
||||
array( $batch_start, $batch_end, $single_batch_action )
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, queue the single batches.
|
||||
for ( $i = $range_start; $i <= $range_end; $i++ ) {
|
||||
self::queue()->schedule_single( $action_timestamp, $single_batch_action, array( $i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue an action to run after another.
|
||||
*
|
||||
* @param string $action Action to run after prerequisite.
|
||||
* @param string $prerequisite_action Prerequisite action.
|
||||
*/
|
||||
public static function queue_dependent_action( $action, $prerequisite_action ) {
|
||||
$blocking_jobs = self::queue()->search(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'per_page' => 1,
|
||||
'claimed' => false,
|
||||
'search' => $prerequisite_action, // search is used instead of hook to find queued batch creation.
|
||||
)
|
||||
);
|
||||
|
||||
if ( $blocking_jobs ) {
|
||||
$blocking_job = current( $blocking_jobs );
|
||||
$after_blocking_job = $blocking_job->get_schedule()->next()->getTimestamp() + 5;
|
||||
|
||||
self::queue()->schedule_single(
|
||||
$after_blocking_job,
|
||||
self::QUEUE_DEPEDENT_ACTION,
|
||||
array( $action, $prerequisite_action )
|
||||
);
|
||||
} else {
|
||||
self::queue()->schedule_single( time() + 5, $action );
|
||||
}
|
||||
}
|
||||
|
||||
$customer_ids = $customer_query->get_results();
|
||||
/**
|
||||
* Init customer lookup table update (in batches).
|
||||
*/
|
||||
public static function customer_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
$total_customers = $customer_query->get_total();
|
||||
|
||||
set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS );
|
||||
if ( 0 === $total_customers ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process customers until close to running out of memory timeouts on large sites then requeue.
|
||||
$num_batches = ceil( $total_customers / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of customers to update.
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function customer_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
'number' => $batch_size,
|
||||
'paged' => $batch_number,
|
||||
)
|
||||
);
|
||||
|
||||
$customer_ids = $customer_query->get_results();
|
||||
|
||||
foreach ( $customer_ids as $customer_id ) {
|
||||
$result = WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
|
||||
|
||||
if ( $result ) {
|
||||
// Pop the customer ID from the array for updating the transient later should we near memory exhaustion.
|
||||
unset( $customer_ids[ $customer_id ] );
|
||||
}
|
||||
|
||||
if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) {
|
||||
// Update the transient for the next run to avoid processing the same orders again.
|
||||
set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
// TODO: schedule single customer update if this fails?
|
||||
WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -595,10 +797,25 @@ class WC_Admin_Api_Init {
|
|||
self::create_db_tables();
|
||||
|
||||
// Initialize report tables.
|
||||
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 );
|
||||
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'customer_lookup_store_init' ), 20 );
|
||||
add_action( 'woocommerce_after_register_post_type', array( __CLASS__, 'regenerate_report_data' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the currency symbol (in addition to currency code) to each Order
|
||||
* object in REST API responses. For use in formatCurrency().
|
||||
*
|
||||
* @param {WP_REST_Response} $response REST response object.
|
||||
* @returns {WP_REST_Response}
|
||||
*/
|
||||
public static function add_currency_symbol_to_order_response( $response ) {
|
||||
$response_data = $response->get_data();
|
||||
$currency_code = $response_data['currency'];
|
||||
$currency_symbol = get_woocommerce_currency_symbol( $currency_code );
|
||||
$response_data['currency_symbol'] = html_entity_decode( $currency_symbol );
|
||||
$response->set_data( $response_data );
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Api_Init();
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Order stats background process.
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'WC_Background_Process', false ) ) {
|
||||
include_once WC_ABSPATH . '/includes/abstracts/class-wc-background-process.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Order_Stats_Background_Process class.
|
||||
*
|
||||
* @todo use Action Scheduler instead of this.
|
||||
*/
|
||||
class WC_Admin_Order_Stats_Background_Process extends WC_Background_Process {
|
||||
|
||||
/**
|
||||
* Initiate new background process.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Uses unique prefix per blog so each blog has separate queue.
|
||||
$this->prefix = 'wp_' . get_current_blog_id();
|
||||
$this->action = 'wc_order_stats';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push to queue without scheduling duplicate recalculation events.
|
||||
* Overrides WC_Background_Process::push_to_queue.
|
||||
*
|
||||
* @param integer $data Timestamp of hour to generate stats.
|
||||
*/
|
||||
public function push_to_queue( $data ) {
|
||||
$data = absint( $data );
|
||||
if ( ! in_array( $data, $this->data, true ) ) {
|
||||
$this->data[] = $data;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch but only if there is data to update.
|
||||
* Overrides WC_Background_Process::dispatch.
|
||||
*/
|
||||
public function dispatch() {
|
||||
if ( ! $this->data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Code to execute for each item in the queue
|
||||
*
|
||||
* @param string $item Queue item to iterate over.
|
||||
* @return bool
|
||||
*/
|
||||
protected function task( $item ) {
|
||||
if ( ! $item ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = wc_get_order( $item );
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::update( $order );
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
protected $report_columns = array(
|
||||
'order_id' => 'order_id',
|
||||
'date_created' => 'date_created',
|
||||
'status' => 'status',
|
||||
'status' => 'REPLACE(status, "wc-", "") as status',
|
||||
'customer_id' => 'customer_id',
|
||||
'net_total' => 'net_total',
|
||||
'num_items_sold' => 'num_items_sold',
|
||||
|
@ -240,6 +240,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
$mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' );
|
||||
$products = $this->get_products_by_order_ids( array_keys( $mapped_orders ) );
|
||||
$mapped_products = $this->map_array_by_key( $products, 'product_id' );
|
||||
$coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) );
|
||||
$product_categories = $this->get_product_categories_by_product_ids( array_keys( $mapped_products ) );
|
||||
|
||||
$mapped_data = array();
|
||||
|
@ -250,8 +251,9 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
}
|
||||
|
||||
$mapped_data[ $product['order_id'] ]['products'][] = array(
|
||||
'id' => $product['product_id'],
|
||||
'name' => $product['product_name'],
|
||||
'id' => $product['product_id'],
|
||||
'name' => $product['product_name'],
|
||||
'quantity' => $product['product_quantity'],
|
||||
);
|
||||
$mapped_data[ $product['order_id'] ]['categories'] = array_unique(
|
||||
array_merge(
|
||||
|
@ -261,8 +263,24 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
);
|
||||
}
|
||||
|
||||
foreach ( $coupons as $coupon ) {
|
||||
if ( ! isset( $mapped_data[ $coupon['order_id'] ] ) ) {
|
||||
$mapped_data[ $product['order_id'] ]['coupons'] = array();
|
||||
}
|
||||
|
||||
$mapped_data[ $coupon['order_id'] ]['coupons'][] = array(
|
||||
'id' => $coupon['coupon_id'],
|
||||
'code' => wc_format_coupon_code( $coupon['coupon_code'] ),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $orders_data as $key => $order_data ) {
|
||||
$orders_data[ $key ]['extended_info'] = $mapped_data[ $order_data['order_id'] ];
|
||||
$defaults = array(
|
||||
'products' => array(),
|
||||
'categories' => array(),
|
||||
'coupons' => array(),
|
||||
);
|
||||
$orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_data['order_id'] ] ) ? array_merge( $defaults, $mapped_data[ $order_data['order_id'] ] ) : $defaults;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +300,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
}
|
||||
|
||||
/**
|
||||
* Get product Ids, names, and categories from order IDs.
|
||||
* Get product IDs, names, and quantity from order IDs.
|
||||
*
|
||||
* @param array $order_ids Array of order IDs.
|
||||
* @return array
|
||||
|
@ -293,7 +311,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
$included_order_ids = implode( ',', $order_ids );
|
||||
|
||||
$products = $wpdb->get_results(
|
||||
"SELECT order_id, ID as product_id, post_title as product_name
|
||||
"SELECT order_id, ID as product_id, post_title as product_name, product_qty as product_quantity
|
||||
FROM {$wpdb->prefix}posts
|
||||
JOIN {$order_product_lookup_table} ON {$order_product_lookup_table}.product_id = {$wpdb->prefix}posts.ID
|
||||
WHERE
|
||||
|
@ -305,6 +323,30 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon information from order IDs.
|
||||
*
|
||||
* @param array $order_ids Array of order IDs.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_coupons_by_order_ids( $order_ids ) {
|
||||
global $wpdb;
|
||||
$order_coupon_lookup_table = $wpdb->prefix . 'wc_order_coupon_lookup';
|
||||
$included_order_ids = implode( ',', $order_ids );
|
||||
|
||||
$coupons = $wpdb->get_results(
|
||||
"SELECT order_id, coupon_id, post_title as coupon_code
|
||||
FROM {$wpdb->prefix}posts
|
||||
JOIN {$order_coupon_lookup_table} ON {$order_coupon_lookup_table}.coupon_id = {$wpdb->prefix}posts.ID
|
||||
WHERE
|
||||
order_id IN ({$included_order_ids})
|
||||
",
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
return $coupons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product categories by array of product IDs
|
||||
*
|
||||
|
@ -335,7 +377,6 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
return $mapped_product_categories;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns string to be used as cache key for the data.
|
||||
*
|
||||
|
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Order_Stats_Background_Process', false ) ) {
|
||||
include_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-order-stats-background-process.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Reports_Orders_Stats_Data_Store.
|
||||
*
|
||||
|
@ -68,26 +64,11 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
'net_revenue' => '( SUM(net_total) - SUM(refund_total) ) AS net_revenue',
|
||||
'avg_items_per_order' => 'AVG(num_items_sold) AS avg_items_per_order',
|
||||
'avg_order_value' => '( SUM(net_total) - SUM(refund_total) ) / COUNT(*) AS avg_order_value',
|
||||
'num_returning_customers' => 'SUM(returning_customer = 1) AS num_returning_customers',
|
||||
'num_new_customers' => 'SUM(returning_customer = 0) AS num_new_customers',
|
||||
// Count returning customers as ( total_customers - new_customers ) to get an accurate number and count customers in with both new and old statuses as new.
|
||||
'num_returning_customers' => '( COUNT( DISTINCT( customer_id ) ) - COUNT( DISTINCT( CASE WHEN returning_customer = 0 THEN customer_id END ) ) ) AS num_returning_customers',
|
||||
'num_new_customers' => 'COUNT( DISTINCT( CASE WHEN returning_customer = 0 THEN customer_id END ) ) AS num_new_customers',
|
||||
);
|
||||
|
||||
/**
|
||||
* Background process to populate order stats.
|
||||
*
|
||||
* @var WC_Admin_Order_Stats_Background_Process
|
||||
*/
|
||||
protected static $background_process;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! self::$background_process ) {
|
||||
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up all the hooks for maintaining and populating table data.
|
||||
*/
|
||||
|
@ -97,10 +78,7 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
add_action( 'clean_post_cache', array( __CLASS__, 'sync_order' ) );
|
||||
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order' ) );
|
||||
add_action( 'woocommerce_refund_deleted', array( __CLASS__, 'sync_on_refund_delete' ), 10, 2 );
|
||||
|
||||
if ( ! self::$background_process ) {
|
||||
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
||||
}
|
||||
add_action( 'delete_post', array( __CLASS__, 'delete_order' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,31 +356,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a background process that will repopulate the entire orders stats database.
|
||||
*
|
||||
* @todo Make this work on large DBs.
|
||||
*/
|
||||
public static function queue_order_stats_repopulate_database() {
|
||||
|
||||
// This needs to be updated to work in batches instead of getting all orders, as
|
||||
// that will not work well on DBs with more than a few hundred orders.
|
||||
$order_ids = wc_get_orders(
|
||||
array(
|
||||
'limit' => -1,
|
||||
'type' => 'shop_order',
|
||||
'return' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $order_ids as $id ) {
|
||||
self::$background_process->push_to_queue( $id );
|
||||
}
|
||||
|
||||
self::$background_process->save();
|
||||
self::$background_process->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order information to the lookup table when orders are created or modified.
|
||||
*
|
||||
|
@ -500,6 +453,28 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
return $wpdb->replace( $table_name, $data, $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the order stats when an order is deleted.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public static function delete_order( $post_id ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
if ( 'shop_order' !== get_post_type( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE order_id = %d",
|
||||
$post_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculation methods.
|
||||
*/
|
||||
|
@ -528,23 +503,23 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
* @return bool
|
||||
*/
|
||||
protected static function is_returning_customer( $order ) {
|
||||
$customer_id = $order->get_user_id();
|
||||
global $wpdb;
|
||||
$customer_id = WC_Admin_Reports_Customers_Data_Store::get_customer_id_by_user_id( $order->get_user_id() );
|
||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
if ( 0 === $customer_id ) {
|
||||
if ( ! $customer_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$customer_orders = get_posts(
|
||||
array(
|
||||
'meta_key' => '_customer_user', // WPCS: slow query ok.
|
||||
'meta_value' => $customer_id, // WPCS: slow query ok.
|
||||
'post_type' => 'shop_order',
|
||||
'post_status' => array( 'wc-on-hold', 'wc-processing', 'wc-completed' ),
|
||||
'numberposts' => 2,
|
||||
$customer_orders = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM ${orders_stats_table} WHERE customer_id = %d AND date_created < %s",
|
||||
$customer_id,
|
||||
date( 'Y-m-d H:i:s', $order->get_date_created()->getTimestamp() )
|
||||
)
|
||||
);
|
||||
|
||||
return count( $customer_orders ) > 1;
|
||||
return $customer_orders >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -399,6 +399,7 @@ function wc_admin_get_user_data_fields() {
|
|||
'revenue_report_columns',
|
||||
'taxes_report_columns',
|
||||
'variations_report_columns',
|
||||
'dashboard_performance_indicators',
|
||||
'dashboard_charts',
|
||||
'dashboard_chart_type',
|
||||
'dashboard_chart_interval',
|
||||
|
|
|
@ -153,7 +153,8 @@ function wc_admin_print_script_settings() {
|
|||
}
|
||||
|
||||
$preload_data_endpoints = array(
|
||||
'countries' => '/wc/v3/data/countries',
|
||||
'countries' => '/wc/v4/data/countries',
|
||||
'performanceIndicators' => '/wc/v4/reports/performance-indicators/allowed',
|
||||
);
|
||||
|
||||
if ( function_exists( 'gutenberg_preload_api_request' ) ) {
|
||||
|
@ -169,7 +170,7 @@ function wc_admin_print_script_settings() {
|
|||
|
||||
$current_user_data = array();
|
||||
foreach ( wc_admin_get_user_data_fields() as $user_field ) {
|
||||
$current_user_data[ $user_field ] = get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true );
|
||||
$current_user_data[ $user_field ] = json_decode( get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,22 +180,22 @@ function wc_admin_print_script_settings() {
|
|||
|
||||
// Settings and variables can be passed here for access in the app.
|
||||
$settings = array(
|
||||
'adminUrl' => admin_url(),
|
||||
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
|
||||
'wcAdminAssetUrl' => plugins_url( 'images/', wc_admin_dir_path( 'wc-admin.php' ) ), // Temporary for plugin. See above.
|
||||
'embedBreadcrumbs' => wc_admin_get_embed_breadcrumbs(),
|
||||
'siteLocale' => esc_attr( get_bloginfo( 'language' ) ),
|
||||
'currency' => wc_admin_currency_settings(),
|
||||
'orderStatuses' => format_order_statuses( wc_get_order_statuses() ),
|
||||
'stockStatuses' => wc_get_product_stock_status_options(),
|
||||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'trackingEnabled' => $tracking_enabled,
|
||||
'dataEndpoints' => array(),
|
||||
'l10n' => array(
|
||||
'adminUrl' => admin_url(),
|
||||
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
|
||||
'wcAdminAssetUrl' => plugins_url( 'images/', wc_admin_dir_path( 'wc-admin.php' ) ), // Temporary for plugin. See above.
|
||||
'embedBreadcrumbs' => wc_admin_get_embed_breadcrumbs(),
|
||||
'siteLocale' => esc_attr( get_bloginfo( 'language' ) ),
|
||||
'currency' => wc_admin_currency_settings(),
|
||||
'orderStatuses' => format_order_statuses( wc_get_order_statuses() ),
|
||||
'stockStatuses' => wc_get_product_stock_status_options(),
|
||||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'trackingEnabled' => $tracking_enabled,
|
||||
'dataEndpoints' => array(),
|
||||
'l10n' => array(
|
||||
'userLocale' => get_user_locale(),
|
||||
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
|
||||
),
|
||||
'currentUserData' => $current_user_data,
|
||||
'currentUserData' => $current_user_data,
|
||||
);
|
||||
|
||||
foreach ( $preload_data_endpoints as $key => $endpoint ) {
|
||||
|
|
|
@ -269,10 +269,13 @@ function wc_admin_currency_settings() {
|
|||
return apply_filters(
|
||||
'wc_currency_settings',
|
||||
array(
|
||||
'code' => $code,
|
||||
'precision' => wc_get_price_decimals(),
|
||||
'symbol' => get_woocommerce_currency_symbol( $code ),
|
||||
'position' => get_option( 'woocommerce_currency_pos' ),
|
||||
'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() ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wc-admin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -6102,9 +6102,9 @@
|
|||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.1.tgz",
|
||||
"integrity": "sha512-L72mmmEayPJBejKIWe2pYtGis5r0tQ5NaJekdhyXgeMQTpJoBsH0NL4ElY2LfSoV15xeQWKQ+XTTOZdyero5Xg=="
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.2.tgz",
|
||||
"integrity": "sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g=="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -7238,9 +7238,9 @@
|
|||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.8.tgz",
|
||||
"integrity": "sha512-vetRFbN1SXSPfP3ClIiYnxTrXquSqakBEOoB5JESn0SVcSYzpu6ougjakpKnskGctYdlNpwf+riUHSkG7d4XUw=="
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.9.tgz",
|
||||
"integrity": "sha512-lt9f3A3RO1OCNaWdA+s/k7YVn0Typ5MbAKmX94PLCZbs8wLNccX3Bj4xXA7GLKOoDb/MeVoAoeIJarZD1JUnjg=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
|
@ -7811,9 +7811,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-jest": {
|
||||
"version": "22.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.1.2.tgz",
|
||||
"integrity": "sha512-jSPT4rVmNetkeCIyrvvOM0wJtgoUSbKHIUDoOGzIISsg51eWN/nISPNKVM+jXMMDI9oowbyapOnpKSXlsLiDpQ==",
|
||||
"version": "22.1.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.1.3.tgz",
|
||||
"integrity": "sha512-JTZTI6WQoNruAugNyCO8fXfTONVcDd5i6dMRFA5g3rUFn1UDDLILY1bTL6alvNXbW2U7Sc2OSpi8m08pInnq0A==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-jsx-a11y": {
|
||||
|
@ -8947,8 +8947,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -8966,13 +8965,11 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -8985,18 +8982,15 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -9099,8 +9093,7 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -9110,7 +9103,6 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -9123,20 +9115,17 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -9153,7 +9142,6 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -9226,8 +9214,7 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -9237,7 +9224,6 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -9313,8 +9299,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -9344,7 +9329,6 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -9362,7 +9346,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -9401,13 +9384,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -12929,6 +12910,11 @@
|
|||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"locutus": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.10.tgz",
|
||||
"integrity": "sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
|
@ -14131,6 +14117,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node-watch": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.0.tgz",
|
||||
"integrity": "sha512-XAgTL05z75ptd7JSVejH1a2Dm1zmXYhuDr9l230Qk6Z7/7GPcnAs/UyJJ4ggsXSvWil8iOzwQLW0zuGUvHpG8g==",
|
||||
"dev": true
|
||||
},
|
||||
"node-wp-i18n": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/node-wp-i18n/-/node-wp-i18n-1.2.2.tgz",
|
||||
|
@ -17033,23 +17025,17 @@
|
|||
}
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.16.1.tgz",
|
||||
"integrity": "sha512-ZUQm94F3AHozRaTo4Vz6yIgkSEZIL7p+BsWeGZ23rx+ZVRoqX+bvBA8br0xmCOU0DSR4qYGtV7Y5HxTsC4V78A==",
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.16.2.tgz",
|
||||
"integrity": "sha512-O/7qXi51DPjRVdbrpNzoBQH5dnAPQNbfoOFyRiUwreTMJfIHYOEBzwuH+c0+/BTSJ3CQyKs6ILSWXhESH6Op3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types": "0.11.6",
|
||||
"ast-types": "0.11.7",
|
||||
"esprima": "~4.0.0",
|
||||
"private": "~0.1.5",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ast-types": {
|
||||
"version": "0.11.6",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.6.tgz",
|
||||
"integrity": "sha512-nHiuV14upVGl7MWwFUYbzJ6YlfwWS084CU9EA8HajfYQjMSli5TQi3UTRygGF58LFWVkXxS1rbgRhROEqlQkXg==",
|
||||
"dev": true
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wc-admin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"main": "js/index.js",
|
||||
"author": "Automattic",
|
||||
"license": "GPL-2.0-or-later",
|
||||
|
@ -76,7 +76,7 @@
|
|||
"eslint": "5.12.0",
|
||||
"eslint-config-wpcalypso": "4.0.1",
|
||||
"eslint-loader": "2.1.1",
|
||||
"eslint-plugin-jest": "22.1.2",
|
||||
"eslint-plugin-jest": "22.1.3",
|
||||
"eslint-plugin-jsx-a11y": "6.1.2",
|
||||
"eslint-plugin-react": "7.12.3",
|
||||
"eslint-plugin-wpcalypso": "4.0.2",
|
||||
|
@ -87,7 +87,7 @@
|
|||
"lerna": "3.10.5",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"node-sass": "4.11.0",
|
||||
"node-watch": "^0.6.0",
|
||||
"node-watch": "0.6.0",
|
||||
"postcss-color-function": "4.0.1",
|
||||
"postcss-loader": "3.0.0",
|
||||
"prettier": "github:automattic/calypso-prettier#c56b4251",
|
||||
|
@ -95,7 +95,7 @@
|
|||
"raw-loader": "1.0.0",
|
||||
"react-docgen": "2.21.0",
|
||||
"readline-sync": "1.4.9",
|
||||
"recast": "0.16.1",
|
||||
"recast": "0.16.2",
|
||||
"replace": "1.0.1",
|
||||
"rimraf": "2.6.2",
|
||||
"rtlcss": "2.4.0",
|
||||
|
@ -121,7 +121,7 @@
|
|||
"@wordpress/viewport": "^2.0.7",
|
||||
"browser-filesaver": "^1.1.1",
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "2.6.1",
|
||||
"core-js": "2.6.2",
|
||||
"d3-array": "^2.0.0",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-format": "^1.3.2",
|
||||
|
@ -130,12 +130,13 @@
|
|||
"d3-selection": "^1.3.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"dompurify": "1.0.8",
|
||||
"dompurify": "1.0.9",
|
||||
"gfm-code-blocks": "1.0.0",
|
||||
"gridicons": "3.1.1",
|
||||
"history": "4.7.2",
|
||||
"html-to-react": "1.3.4",
|
||||
"interpolate-components": "1.1.1",
|
||||
"locutus": "^2.0.10",
|
||||
"lodash": "^4.17.11",
|
||||
"marked": "0.6.0",
|
||||
"prismjs": "^1.15.0",
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
# 1.4.0 ( unreleased )
|
||||
# 1.4.1 (unreleased)
|
||||
- Chart component: format numbers and prices using store currency settings.
|
||||
- Make `href`/linking optional in SummaryNumber.
|
||||
|
||||
# 1.4.0
|
||||
- Add download log ip address autocompleter to search component
|
||||
- Add order number autocompleter to search component
|
||||
- Add order number, username, and IP address filters to the downloads report.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/components",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"description": "UI components for WooCommerce.",
|
||||
"author": "Automattic",
|
||||
"license": "GPL-2.0-or-later",
|
||||
|
@ -22,9 +22,9 @@
|
|||
"react-native": "src/index",
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs2": "7.2.0",
|
||||
"@woocommerce/csv-export": "^1.0.1",
|
||||
"@woocommerce/csv-export": "^1.0.2",
|
||||
"@woocommerce/currency": "^1.0.0",
|
||||
"@woocommerce/date": "^1.0.3",
|
||||
"@woocommerce/date": "^1.0.5",
|
||||
"@woocommerce/navigation": "^1.1.0",
|
||||
"@wordpress/components": "7.0.5",
|
||||
"@wordpress/compose": "3.0.0",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"@wordpress/keycodes": "2.0.5",
|
||||
"@wordpress/viewport": "^2.0.7",
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "2.6.1",
|
||||
"core-js": "2.6.2",
|
||||
"d3-array": "^2.0.0",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-format": "^1.3.2",
|
||||
|
|
|
@ -60,8 +60,11 @@ class D3Chart extends Component {
|
|||
.append( 'g' )
|
||||
.attr( 'transform', `translate(${ margin.left },${ margin.top })` );
|
||||
|
||||
drawAxis( g, adjParams );
|
||||
type === 'line' && drawLines( g, data, adjParams );
|
||||
const xOffset = type === 'line' && adjParams.uniqueDates.length <= 1
|
||||
? adjParams.width / 2
|
||||
: 0;
|
||||
drawAxis( g, adjParams, xOffset );
|
||||
type === 'line' && drawLines( g, data, adjParams, xOffset );
|
||||
type === 'bar' && drawBars( g, data, adjParams );
|
||||
}
|
||||
|
||||
|
@ -140,6 +143,7 @@ class D3Chart extends Component {
|
|||
type,
|
||||
uniqueDates,
|
||||
uniqueKeys,
|
||||
valueType,
|
||||
xFormat: getFormatter( xFormat, d3TimeFormat ),
|
||||
x2Format: getFormatter( x2Format, d3TimeFormat ),
|
||||
xGroupScale: getXGroupScale( orderedKeys, xScale, compact ),
|
||||
|
@ -150,7 +154,6 @@ class D3Chart extends Component {
|
|||
yScale,
|
||||
yTickOffset: getYTickOffset( adjHeight, yMax ),
|
||||
yFormat: getFormatter( yFormat ),
|
||||
valueType,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ export const getYGrids = ( yMax ) => {
|
|||
return yGrids;
|
||||
};
|
||||
|
||||
export const drawAxis = ( node, params ) => {
|
||||
export const drawAxis = ( node, params, xOffset ) => {
|
||||
const xScale = params.type === 'line' ? params.xLineScale : params.xScale;
|
||||
const removeDuplicateDates = ( d, i, ticks, formatter ) => {
|
||||
const monthDate = moment( d ).toDate();
|
||||
|
@ -205,7 +205,7 @@ export const drawAxis = ( node, params ) => {
|
|||
.append( 'g' )
|
||||
.attr( 'class', 'axis' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(0, ${ params.height })` )
|
||||
.attr( 'transform', `translate(${ xOffset }, ${ params.height })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
.tickValues( ticks )
|
||||
|
@ -218,7 +218,7 @@ export const drawAxis = ( node, params ) => {
|
|||
.append( 'g' )
|
||||
.attr( 'class', 'axis axis-month' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(0, ${ params.height + 20 })` )
|
||||
.attr( 'transform', `translate(${ xOffset }, ${ params.height + 20 })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
.tickValues( ticks )
|
||||
|
@ -229,7 +229,7 @@ export const drawAxis = ( node, params ) => {
|
|||
node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'pipes' )
|
||||
.attr( 'transform', `translate(0, ${ params.height })` )
|
||||
.attr( 'transform', `translate(${ xOffset }, ${ params.height })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
.tickValues( ticks )
|
||||
|
@ -240,7 +240,7 @@ export const drawAxis = ( node, params ) => {
|
|||
node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'grid' )
|
||||
.attr( 'transform', `translate(-${ params.margin.left },0)` )
|
||||
.attr( 'transform', `translate(-${ params.margin.left }, 0)` )
|
||||
.call(
|
||||
d3AxisLeft( params.yScale )
|
||||
.tickValues( yGrids )
|
||||
|
|
|
@ -20,7 +20,7 @@ const handleMouseOverLineChart = ( date, parentNode, node, data, params, positio
|
|||
showTooltip( params, data.find( e => e.date === date ), position );
|
||||
};
|
||||
|
||||
export const drawLines = ( node, data, params ) => {
|
||||
export const drawLines = ( node, data, params, xOffset ) => {
|
||||
const series = node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'lines' )
|
||||
|
@ -36,18 +36,19 @@ export const drawLines = ( node, data, params ) => {
|
|||
lineStroke = params.width <= smallBreak ? 1.25 : lineStroke;
|
||||
const dotRadius = params.width <= wideBreak ? 4 : 6;
|
||||
|
||||
series
|
||||
.append( 'path' )
|
||||
.attr( 'fill', 'none' )
|
||||
.attr( 'stroke-width', lineStroke )
|
||||
.attr( 'stroke-linejoin', 'round' )
|
||||
.attr( 'stroke-linecap', 'round' )
|
||||
.attr( 'stroke', d => getColor( d.key, params.orderedKeys, params.colorScheme ) )
|
||||
.style( 'opacity', d => {
|
||||
const opacity = d.focus ? 1 : 0.1;
|
||||
return d.visible ? opacity : 0;
|
||||
} )
|
||||
.attr( 'd', d => params.line( d.values ) );
|
||||
params.uniqueDates.length > 1 &&
|
||||
series
|
||||
.append( 'path' )
|
||||
.attr( 'fill', 'none' )
|
||||
.attr( 'stroke-width', lineStroke )
|
||||
.attr( 'stroke-linejoin', 'round' )
|
||||
.attr( 'stroke-linecap', 'round' )
|
||||
.attr( 'stroke', d => getColor( d.key, params.orderedKeys, params.colorScheme ) )
|
||||
.style( 'opacity', d => {
|
||||
const opacity = d.focus ? 1 : 0.1;
|
||||
return d.visible ? opacity : 0;
|
||||
} )
|
||||
.attr( 'd', d => params.line( d.values ) );
|
||||
|
||||
const minDataPointSpacing = 36;
|
||||
|
||||
|
@ -65,7 +66,7 @@ export const drawLines = ( node, data, params ) => {
|
|||
const opacity = d.focus ? 1 : 0.1;
|
||||
return d.visible ? opacity : 0;
|
||||
} )
|
||||
.attr( 'cx', d => params.xLineScale( moment( d.date ).toDate() ) )
|
||||
.attr( 'cx', d => params.xLineScale( moment( d.date ).toDate() ) + xOffset )
|
||||
.attr( 'cy', d => params.yScale( d.value ) )
|
||||
.attr( 'tabindex', '0' )
|
||||
.attr( 'aria-label', d => {
|
||||
|
@ -100,9 +101,9 @@ export const drawLines = ( node, data, params ) => {
|
|||
|
||||
focusGrid
|
||||
.append( 'line' )
|
||||
.attr( 'x1', d => params.xLineScale( moment( d.date ).toDate() ) )
|
||||
.attr( 'x1', d => params.xLineScale( moment( d.date ).toDate() ) + xOffset )
|
||||
.attr( 'y1', 0 )
|
||||
.attr( 'x2', d => params.xLineScale( moment( d.date ).toDate() ) )
|
||||
.attr( 'x2', d => params.xLineScale( moment( d.date ).toDate() ) + xOffset )
|
||||
.attr( 'y2', params.height );
|
||||
|
||||
focusGrid
|
||||
|
@ -114,7 +115,7 @@ export const drawLines = ( node, data, params ) => {
|
|||
.attr( 'fill', d => getColor( d.key, params.orderedKeys, params.colorScheme ) )
|
||||
.attr( 'stroke', '#fff' )
|
||||
.attr( 'stroke-width', lineStroke + 2 )
|
||||
.attr( 'cx', d => params.xLineScale( moment( d.date ).toDate() ) )
|
||||
.attr( 'cx', d => params.xLineScale( moment( d.date ).toDate() ) + xOffset )
|
||||
.attr( 'cy', d => params.yScale( d.value ) );
|
||||
|
||||
focus
|
||||
|
@ -126,7 +127,8 @@ export const drawLines = ( node, data, params ) => {
|
|||
.attr( 'height', params.height )
|
||||
.attr( 'opacity', 0 )
|
||||
.on( 'mouseover', ( d, i, nodes ) => {
|
||||
const elementWidthRatio = i === 0 || i === params.dateSpaces.length - 1 ? 0 : 0.5;
|
||||
const isTooltipLeftAligned = ( i === 0 || i === params.dateSpaces.length - 1 ) && params.uniqueDates.length > 1;
|
||||
const elementWidthRatio = isTooltipLeftAligned ? 0 : 0.5;
|
||||
const position = calculateTooltipPosition(
|
||||
d3Event.target,
|
||||
node.node(),
|
||||
|
|
|
@ -11,17 +11,6 @@ import {
|
|||
} from 'd3-scale';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Describes and rounds the maximum y value to the nearest thousand, ten-thousand, million etc. In case it is a decimal number, ceils it.
|
||||
* @param {array} lineData - from `getLineData`
|
||||
* @returns {number} the maximum value in the timeseries multiplied by 4/3
|
||||
*/
|
||||
export const getYMax = lineData => {
|
||||
const yMax = 4 / 3 * d3Max( lineData, d => d3Max( d.values.map( date => date.value ) ) );
|
||||
const pow3Y = Math.pow( 10, ( ( Math.log( yMax ) * Math.LOG10E + 1 ) | 0 ) - 2 ) * 3;
|
||||
return Math.ceil( Math.ceil( yMax / pow3Y ) * pow3Y );
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes getXScale
|
||||
* @param {array} uniqueDates - from `getUniqueDates`
|
||||
|
@ -33,7 +22,7 @@ export const getYMax = lineData => {
|
|||
export const getXScale = ( uniqueDates, width, compact = false ) =>
|
||||
d3ScaleBand()
|
||||
.domain( uniqueDates )
|
||||
.rangeRound( [ 0, width ] )
|
||||
.range( [ 0, width ] )
|
||||
.paddingInner( compact ? 0 : 0.1 );
|
||||
|
||||
/**
|
||||
|
@ -64,6 +53,17 @@ export const getXLineScale = ( uniqueDates, width ) =>
|
|||
] )
|
||||
.rangeRound( [ 0, width ] );
|
||||
|
||||
/**
|
||||
* Describes and rounds the maximum y value to the nearest thousand, ten-thousand, million etc. In case it is a decimal number, ceils it.
|
||||
* @param {array} lineData - from `getLineData`
|
||||
* @returns {number} the maximum value in the timeseries multiplied by 4/3
|
||||
*/
|
||||
export const getYMax = lineData => {
|
||||
const yMax = 4 / 3 * d3Max( lineData, d => d3Max( d.values.map( date => date.value ) ) );
|
||||
const pow3Y = Math.pow( 10, ( ( Math.log( yMax ) * Math.LOG10E + 1 ) | 0 ) - 2 ) * 3;
|
||||
return Math.ceil( Math.ceil( yMax / pow3Y ) * pow3Y );
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes getYScale
|
||||
* @param {number} height - calculated height of the charting space
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { utcParse as d3UTCParse } from 'd3-time-format';
|
||||
import { scaleBand, scaleLinear, scaleTime } from 'd3-scale';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import dummyOrders from './fixtures/dummy-orders';
|
||||
import orderedDates from './fixtures/dummy-ordered-dates';
|
||||
import orderedKeys from './fixtures/dummy-ordered-keys';
|
||||
import {
|
||||
getOrderedKeys,
|
||||
getLineData,
|
||||
|
@ -18,61 +17,104 @@ import {
|
|||
} from '../index';
|
||||
import { getXGroupScale, getXScale, getXLineScale, getYMax, getYScale, getYTickOffset } from '../scales';
|
||||
|
||||
const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' );
|
||||
jest.mock( 'd3-scale', () => ( {
|
||||
...require.requireActual( 'd3-scale' ),
|
||||
scaleBand: jest.fn().mockReturnValue( {
|
||||
bandwidth: jest.fn().mockReturnThis(),
|
||||
domain: jest.fn().mockReturnThis(),
|
||||
padding: jest.fn().mockReturnThis(),
|
||||
paddingInner: jest.fn().mockReturnThis(),
|
||||
range: jest.fn().mockReturnThis(),
|
||||
rangeRound: jest.fn().mockReturnThis(),
|
||||
} ),
|
||||
scaleLinear: jest.fn().mockReturnValue( {
|
||||
domain: jest.fn().mockReturnThis(),
|
||||
rangeRound: jest.fn().mockReturnThis(),
|
||||
} ),
|
||||
scaleTime: jest.fn().mockReturnValue( {
|
||||
domain: jest.fn().mockReturnThis(),
|
||||
rangeRound: jest.fn().mockReturnThis(),
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
const testUniqueKeys = getUniqueKeys( dummyOrders );
|
||||
const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys );
|
||||
const testLineData = getLineData( dummyOrders, testOrderedKeys );
|
||||
const testUniqueDates = getUniqueDates( testLineData, parseDate );
|
||||
const testXScale = getXScale( testUniqueDates, 100 );
|
||||
const testXLineScale = getXLineScale( testUniqueDates, 100 );
|
||||
const testYMax = getYMax( testLineData );
|
||||
const testYScale = getYScale( 100, testYMax );
|
||||
|
||||
describe( 'getXScale', () => {
|
||||
it( 'properly scale inputs to the provided domain and range', () => {
|
||||
expect( testXScale( orderedDates[ 0 ] ) ).toEqual( 3 );
|
||||
expect( testXScale( orderedDates[ 2 ] ) ).toEqual( 35 );
|
||||
expect( testXScale( orderedDates[ orderedDates.length - 1 ] ) ).toEqual( 83 );
|
||||
describe( 'X scales', () => {
|
||||
const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' );
|
||||
const testUniqueDates = getUniqueDates( testLineData, parseDate );
|
||||
|
||||
describe( 'getXScale', () => {
|
||||
it( 'creates band scale with correct parameters', () => {
|
||||
getXScale( testUniqueDates, 100 );
|
||||
|
||||
expect( scaleBand().domain ).toHaveBeenLastCalledWith( testUniqueDates );
|
||||
expect( scaleBand().range ).toHaveBeenLastCalledWith( [ 0, 100 ] );
|
||||
expect( scaleBand().paddingInner ).toHaveBeenLastCalledWith( 0.1 );
|
||||
} );
|
||||
|
||||
it( 'creates band scale with correct paddingInner parameter when it\'s in compact mode', () => {
|
||||
getXScale( testUniqueDates, 100, true );
|
||||
|
||||
expect( scaleBand().paddingInner ).toHaveBeenLastCalledWith( 0 );
|
||||
} );
|
||||
} );
|
||||
it( 'properly scale inputs and test the bandwidth', () => {
|
||||
expect( testXScale.bandwidth() ).toEqual( 14 );
|
||||
|
||||
describe( 'getXGroupScale', () => {
|
||||
const testXScale = getXScale( testUniqueDates, 100 );
|
||||
|
||||
it( 'creates band scale with correct parameters', () => {
|
||||
getXGroupScale( testOrderedKeys, testXScale );
|
||||
const filteredOrderedKeys = [ 'Cap', 'T-Shirt', 'Sunglasses', 'Polo', 'Hoodie' ];
|
||||
|
||||
expect( scaleBand().domain ).toHaveBeenLastCalledWith( filteredOrderedKeys );
|
||||
expect( scaleBand().range ).toHaveBeenLastCalledWith( [ 0, 100 ] );
|
||||
expect( scaleBand().padding ).toHaveBeenLastCalledWith( 0.07 );
|
||||
} );
|
||||
|
||||
it( 'creates band scale with correct padding parameter when it\'s in compact mode', () => {
|
||||
getXGroupScale( testOrderedKeys, testXScale, true );
|
||||
|
||||
expect( scaleBand().padding ).toHaveBeenLastCalledWith( 0 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getXLineScale', () => {
|
||||
it( 'creates time scale with correct parameters', () => {
|
||||
getXLineScale( testUniqueDates, 100 );
|
||||
|
||||
expect( scaleTime().domain ).toHaveBeenLastCalledWith( [
|
||||
new Date( '2018-05-30T00:00:00' ),
|
||||
new Date( '2018-06-04T00:00:00' ),
|
||||
] );
|
||||
expect( scaleTime().rangeRound ).toHaveBeenLastCalledWith( [ 0, 100 ] );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getXGroupScale', () => {
|
||||
it( 'properly scale inputs based on the getXScale', () => {
|
||||
const testXGroupScale = getXGroupScale( testOrderedKeys, testXScale );
|
||||
expect( testXGroupScale( orderedKeys[ 0 ].key ) ).toEqual( 2 );
|
||||
expect( testXGroupScale( orderedKeys[ 2 ].key ) ).toEqual( 6 );
|
||||
expect( testXGroupScale( orderedKeys[ orderedKeys.length - 1 ].key ) ).toEqual( 10 );
|
||||
describe( 'Y scales', () => {
|
||||
describe( 'getYMax', () => {
|
||||
it( 'calculate the correct maximum y value', () => {
|
||||
expect( getYMax( testLineData ) ).toEqual( 15000000 );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getXLineScale', () => {
|
||||
it( 'properly scale inputs for the line', () => {
|
||||
expect( testXLineScale( new Date( orderedDates[ 0 ] ) ) ).toEqual( 0 );
|
||||
expect( testXLineScale( new Date( orderedDates[ 2 ] ) ) ).toEqual( 40 );
|
||||
expect( testXLineScale( new Date( orderedDates[ orderedDates.length - 1 ] ) ) ).toEqual( 100 );
|
||||
} );
|
||||
} );
|
||||
describe( 'getYScale', () => {
|
||||
it( 'creates linear scale with correct parameters', () => {
|
||||
getYScale( 100, 15000000 );
|
||||
|
||||
describe( 'getYMax', () => {
|
||||
it( 'calculate the correct maximum y value', () => {
|
||||
expect( testYMax ).toEqual( 15000000 );
|
||||
expect( scaleLinear().domain ).toHaveBeenLastCalledWith( [ 0, 15000000 ] );
|
||||
expect( scaleLinear().rangeRound ).toHaveBeenLastCalledWith( [ 100, 0 ] );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getYScale', () => {
|
||||
it( 'properly scale the y values given the height and maximum y value', () => {
|
||||
expect( testYScale( 0 ) ).toEqual( 100 );
|
||||
expect( testYScale( testYMax ) ).toEqual( 0 );
|
||||
} );
|
||||
} );
|
||||
describe( 'getYTickOffset', () => {
|
||||
it( 'creates linear scale with correct parameters', () => {
|
||||
getYTickOffset( 100, 15000000 );
|
||||
|
||||
describe( 'getYTickOffset', () => {
|
||||
it( 'properly scale the y values for the y-axis ticks given the height and maximum y value', () => {
|
||||
const testYTickOffset1 = getYTickOffset( 100, testYMax );
|
||||
expect( testYTickOffset1( 0 ) ).toEqual( 112 );
|
||||
expect( testYTickOffset1( testYMax ) ).toEqual( 12 );
|
||||
expect( scaleLinear().domain ).toHaveBeenLastCalledWith( [ 0, 15000000 ] );
|
||||
expect( scaleLinear().rangeRound ).toHaveBeenLastCalledWith( [ 112, 12 ] );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { Component, createRef, Fragment } from '@wordpress/element';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { formatDefaultLocale as d3FormatDefaultLocale } from 'd3-format';
|
||||
import { get, isEqual, partial } from 'lodash';
|
||||
import Gridicon from 'gridicons';
|
||||
|
@ -26,11 +25,28 @@ import ChartPlaceholder from './placeholder';
|
|||
import { H, Section } from '../section';
|
||||
import { D3Chart, D3Legend } from './d3chart';
|
||||
|
||||
function getD3CurrencyFormat( symbol, position ) {
|
||||
switch ( position ) {
|
||||
case 'left_space':
|
||||
return [ symbol + ' ', '' ];
|
||||
case 'right':
|
||||
return [ '', symbol ];
|
||||
case 'right_space':
|
||||
return [ '', ' ' + symbol ];
|
||||
case 'left':
|
||||
default:
|
||||
return [ symbol, '' ];
|
||||
}
|
||||
}
|
||||
|
||||
const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ], '' );
|
||||
const symbolPosition = get( wcSettings, [ 'currency', 'position' ], 'left' );
|
||||
|
||||
d3FormatDefaultLocale( {
|
||||
decimal: '.',
|
||||
thousands: ',',
|
||||
decimal: get( wcSettings, [ 'currency', 'decimal_separator' ], '.' ),
|
||||
thousands: get( wcSettings, [ 'currency', 'thousand_separator' ], ',' ),
|
||||
grouping: [ 3 ],
|
||||
currency: [ decodeEntities( get( wcSettings, 'currency.symbol', '' ) ), '' ],
|
||||
currency: getD3CurrencyFormat( currencySymbol, symbolPosition ),
|
||||
} );
|
||||
|
||||
function getOrderedKeys( props, previousOrderedKeys = [] ) {
|
||||
|
@ -272,13 +288,13 @@ class Chart extends Component {
|
|||
|
||||
switch ( valueType ) {
|
||||
case 'average':
|
||||
yFormat = '.0f';
|
||||
yFormat = ',.0f';
|
||||
break;
|
||||
case 'currency':
|
||||
yFormat = '$.3~s';
|
||||
break;
|
||||
case 'number':
|
||||
yFormat = '.0f';
|
||||
yFormat = ',.0f';
|
||||
break;
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -27,7 +27,7 @@ class DateFilter extends Component {
|
|||
constructor( { filter } ) {
|
||||
super( ...arguments );
|
||||
|
||||
const [ isoAfter, isoBefore ] = ( filter.value || '' ).split( ',' );
|
||||
const [ isoAfter, isoBefore ] = Array.isArray( filter.value ) ? filter.value : [ null, filter.value ];
|
||||
const after = isoAfter ? toMoment( isoDateFormat, isoAfter ) : null;
|
||||
const before = isoBefore ? toMoment( isoDateFormat, isoBefore ) : null;
|
||||
|
||||
|
@ -42,6 +42,7 @@ class DateFilter extends Component {
|
|||
|
||||
this.onSingleDateChange = this.onSingleDateChange.bind( this );
|
||||
this.onRangeDateChange = this.onRangeDateChange.bind( this );
|
||||
this.onRuleChange = this.onRuleChange.bind( this );
|
||||
}
|
||||
|
||||
getBetweenString() {
|
||||
|
@ -58,7 +59,7 @@ class DateFilter extends Component {
|
|||
const { before, after } = this.state;
|
||||
|
||||
// Return nothing if we're missing input(s)
|
||||
if ( ! before || 'between' === rule.value && ! after ) {
|
||||
if ( ! before || ( 'between' === rule.value && ! after ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -75,13 +76,15 @@ class DateFilter extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
return textContent( interpolateComponents( {
|
||||
mixedString: config.labels.title,
|
||||
components: {
|
||||
filter: <Fragment>{ filterStr }</Fragment>,
|
||||
rule: <Fragment>{ rule.label }</Fragment>,
|
||||
},
|
||||
} ) );
|
||||
return textContent(
|
||||
interpolateComponents( {
|
||||
mixedString: config.labels.title,
|
||||
components: {
|
||||
filter: <Fragment>{ filterStr }</Fragment>,
|
||||
rule: <Fragment>{ rule.label }</Fragment>,
|
||||
},
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
onSingleDateChange( { date, text, error } ) {
|
||||
|
@ -118,7 +121,7 @@ class DateFilter extends Component {
|
|||
}
|
||||
|
||||
if ( nextAfter && nextBefore ) {
|
||||
onFilterChange( filter.key, 'value', [ nextAfter, nextBefore ].join( ',' ) );
|
||||
onFilterChange( filter.key, 'value', [ nextAfter, nextBefore ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,9 +171,23 @@ class DateFilter extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
onRuleChange( value ) {
|
||||
const { onFilterChange, filter, updateFilter } = this.props;
|
||||
const { before } = this.state;
|
||||
if ( 'between' === filter.rule && 'between' !== value ) {
|
||||
updateFilter( {
|
||||
key: filter.key,
|
||||
rule: value,
|
||||
value: before ? before.format( isoDateFormat ) : undefined,
|
||||
} );
|
||||
} else {
|
||||
onFilterChange( filter.key, 'rule', value );
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { config, filter, onFilterChange, isEnglish } = this.props;
|
||||
const { key, rule } = filter;
|
||||
const { config, filter, isEnglish } = this.props;
|
||||
const { rule } = filter;
|
||||
const { labels, rules } = config;
|
||||
const screenReaderText = this.getScreenReaderText( filter, config );
|
||||
const children = interpolateComponents( {
|
||||
|
@ -181,7 +198,7 @@ class DateFilter extends Component {
|
|||
className="woocommerce-filters-advanced__rule"
|
||||
options={ rules }
|
||||
value={ rule }
|
||||
onChange={ partial( onFilterChange, key, 'rule' ) }
|
||||
onChange={ this.onRuleChange }
|
||||
aria-label={ labels.rule }
|
||||
/>
|
||||
),
|
||||
|
@ -199,9 +216,7 @@ class DateFilter extends Component {
|
|||
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/
|
||||
return (
|
||||
<fieldset tabIndex="0">
|
||||
<legend className="screen-reader-text">
|
||||
{ labels.add || '' }
|
||||
</legend>
|
||||
<legend className="screen-reader-text">{ labels.add || '' }</legend>
|
||||
<div
|
||||
className={ classnames( 'woocommerce-filters-advanced__fieldset', {
|
||||
'is-english': isEnglish,
|
||||
|
@ -209,11 +224,7 @@ class DateFilter extends Component {
|
|||
>
|
||||
{ children }
|
||||
</div>
|
||||
{ screenReaderText && (
|
||||
<span className="screen-reader-text">
|
||||
{ screenReaderText }
|
||||
</span>
|
||||
) }
|
||||
{ screenReaderText && <span className="screen-reader-text">{ screenReaderText }</span> }
|
||||
</fieldset>
|
||||
);
|
||||
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/
|
||||
|
|
|
@ -55,6 +55,7 @@ class AdvancedFilters extends Component {
|
|||
this.removeFilter = this.removeFilter.bind( this );
|
||||
this.clearFilters = this.clearFilters.bind( this );
|
||||
this.getUpdateHref = this.getUpdateHref.bind( this );
|
||||
this.updateFilter = this.updateFilter.bind( this );
|
||||
}
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
|
@ -85,6 +86,17 @@ class AdvancedFilters extends Component {
|
|||
this.setState( { activeFilters } );
|
||||
}
|
||||
|
||||
updateFilter( filter ) {
|
||||
const activeFilters = this.state.activeFilters.map( activeFilter => {
|
||||
if ( filter.key === activeFilter.key ) {
|
||||
return filter;
|
||||
}
|
||||
return activeFilter;
|
||||
} );
|
||||
|
||||
this.setState( { activeFilters } );
|
||||
}
|
||||
|
||||
removeFilter( key ) {
|
||||
const activeFilters = [ ...this.state.activeFilters ];
|
||||
const index = findIndex( activeFilters, filter => filter.key === key );
|
||||
|
@ -218,6 +230,7 @@ class AdvancedFilters extends Component {
|
|||
onFilterChange={ this.onFilterChange }
|
||||
isEnglish={ isEnglish }
|
||||
query={ query }
|
||||
updateFilter={ this.updateFilter }
|
||||
/>
|
||||
) }
|
||||
<IconButton
|
||||
|
|
|
@ -20,6 +20,15 @@
|
|||
.components-base-control__field {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
margin: $gap 0;
|
||||
border: 1px solid $core-grey-light-700;
|
||||
}
|
||||
|
||||
@include breakpoint( '<400px' ) {
|
||||
margin: $gap-small 0;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters-advanced__title-select {
|
||||
|
@ -52,11 +61,23 @@
|
|||
width: 40px;
|
||||
height: 38px;
|
||||
align-self: center;
|
||||
|
||||
@include breakpoint( '<400px' ) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: $gap-smallest;
|
||||
}
|
||||
}
|
||||
|
||||
.components-form-token-field {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@include breakpoint( '<400px' ) {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: $gap-smaller $gap-smaller 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters-advanced__add-filter {
|
||||
|
@ -85,11 +106,14 @@
|
|||
padding: 0 $gap-smallest;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: $gap-smallest 0;
|
||||
}
|
||||
|
||||
@include breakpoint( '<400px' ) {
|
||||
//display: block;
|
||||
//margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
display: flex;
|
||||
|
@ -186,10 +210,6 @@
|
|||
}
|
||||
|
||||
.separator {
|
||||
padding: 0 8px;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
padding: 0;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,9 @@ class FilterPicker extends Component {
|
|||
renderButton( filter, onClose ) {
|
||||
if ( filter.component ) {
|
||||
const { type, labels } = filter.settings;
|
||||
const { selectedTag } = this.state;
|
||||
const persistedFilter = this.getFilter();
|
||||
const selectedTag = persistedFilter.value === filter.value ? this.state.selectedTag : null;
|
||||
|
||||
return (
|
||||
<Search
|
||||
className="woocommerce-filters-filter__search"
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
.components-base-control__field {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@include breakpoint( '<400px' ) {
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters__basic-filters {
|
||||
|
@ -13,10 +18,14 @@
|
|||
@include breakpoint( '<1280px' ) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters-filter {
|
||||
width: 33.3%;
|
||||
width: 25%;
|
||||
padding: 0 $gap-small;
|
||||
min-height: 82px;
|
||||
display: flex;
|
||||
|
@ -31,6 +40,10 @@
|
|||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include breakpoint( '<1440px' ) {
|
||||
width: 33.3%;
|
||||
}
|
||||
|
||||
@include breakpoint( '<1280px' ) {
|
||||
width: 50%;
|
||||
padding: 0;
|
||||
|
|
|
@ -146,6 +146,9 @@ export class Autocomplete extends Component {
|
|||
const promise = ( this.activePromise = Promise.resolve(
|
||||
typeof options === 'function' ? options( query ) : options
|
||||
).then( optionsData => {
|
||||
if ( ! optionsData ) {
|
||||
return;
|
||||
}
|
||||
const { selected } = this.props;
|
||||
if ( promise !== this.activePromise ) {
|
||||
// Another promise has become active since this one was asked to resolve, so do nothing,
|
||||
|
|
|
@ -24,7 +24,7 @@ export default {
|
|||
return wcSettings.dataEndpoints.countries || [];
|
||||
},
|
||||
getOptionKeywords( country ) {
|
||||
return [ decodeEntities( country.name ) ];
|
||||
return [ country.code, decodeEntities( country.name ) ];
|
||||
},
|
||||
getOptionLabel( country, query ) {
|
||||
const name = decodeEntities( country.name );
|
||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
|||
};
|
||||
payload = stringifyQuery( query );
|
||||
}
|
||||
return apiFetch( { path: `/wc/v3/coupons${ payload }` } );
|
||||
return apiFetch( { path: `/wc/v4/coupons${ payload }` } );
|
||||
},
|
||||
isDebounced: true,
|
||||
getOptionKeywords( coupon ) {
|
||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
|||
};
|
||||
payload = stringifyQuery( query );
|
||||
}
|
||||
return apiFetch( { path: `/wc/v3/customers${ payload }` } );
|
||||
return apiFetch( { path: `/wc/v4/customers${ payload }` } );
|
||||
},
|
||||
isDebounced: true,
|
||||
getOptionKeywords( customer ) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue