Merge branch 'master' into add/name-filter-autocompleter
This commit is contained in:
commit
c4290f757e
|
@ -26,6 +26,7 @@ There are also some helper scripts:
|
|||
|
||||
- `npm run lint` : Run eslint over the javascript files
|
||||
- `npm run i18n` : A multi-step process, used to create a pot file from both the JS and PHP gettext calls. First it runs `i18n:js`, which creates a temporary `.pot` file from the JS files. Next it runs `i18n:php`, which converts that `.pot` file to a PHP file. Lastly, it runs `i18n:pot`, which creates the final `.pot` file from all the PHP files in the plugin (including the generated one with the JS strings).
|
||||
- `npm test` : Run the JS test suite
|
||||
|
||||
## Privacy
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { applyFilters } from '@wordpress/hooks';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import { get, orderBy } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -20,10 +21,33 @@ import { onQueryChange } from '@woocommerce/navigation';
|
|||
import ReportError from 'analytics/components/report-error';
|
||||
import { getReportChartData, getReportTableData } from 'store/reports/utils';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import { extendTableData } from './utils';
|
||||
|
||||
const TABLE_FILTER = 'woocommerce_admin_report_table';
|
||||
|
||||
class ReportTable extends Component {
|
||||
onColumnsChange = columns => {
|
||||
const { columnPrefsKey } = this.props;
|
||||
|
||||
if ( columnPrefsKey ) {
|
||||
const userDataFields = {
|
||||
[ columnPrefsKey ]: columns,
|
||||
};
|
||||
this.props.updateCurrentUserData( userDataFields );
|
||||
}
|
||||
};
|
||||
|
||||
filterShownHeaders = ( headers, shownKeys ) => {
|
||||
if ( ! shownKeys ) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
return headers.map( header => {
|
||||
const hidden = ! shownKeys.includes( header.key );
|
||||
return { ...header, hiddenByDefault: hidden };
|
||||
} );
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
getHeadersContent,
|
||||
|
@ -36,6 +60,7 @@ class ReportTable extends Component {
|
|||
// so they are not included in the `tableProps` variable.
|
||||
endpoint,
|
||||
tableQuery,
|
||||
userPrefColumns,
|
||||
...tableProps
|
||||
} = this.props;
|
||||
|
||||
|
@ -50,7 +75,7 @@ class ReportTable extends Component {
|
|||
const isRequesting = tableData.isRequesting || primaryData.isRequesting;
|
||||
const orderedItems = orderBy( items.data, query.orderby, query.order );
|
||||
const totals = get( primaryData, [ 'data', 'totals' ], null );
|
||||
const totalCount = items.totalCount || 0;
|
||||
const totalResults = items.totalResults || 0;
|
||||
const { headers, ids, rows, summary } = applyFilters( TABLE_FILTER, {
|
||||
endpoint: endpoint,
|
||||
headers: getHeadersContent(),
|
||||
|
@ -58,20 +83,24 @@ class ReportTable extends Component {
|
|||
ids: itemIdField ? orderedItems.map( item => item[ itemIdField ] ) : null,
|
||||
rows: getRowsContent( orderedItems ),
|
||||
totals: totals,
|
||||
summary: getSummary ? getSummary( totals, totalCount ) : null,
|
||||
summary: getSummary ? getSummary( totals, totalResults ) : null,
|
||||
} );
|
||||
|
||||
// Hide any headers based on user prefs, if loaded.
|
||||
const filteredHeaders = this.filterShownHeaders( headers, userPrefColumns );
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
downloadable
|
||||
headers={ headers }
|
||||
headers={ filteredHeaders }
|
||||
ids={ ids }
|
||||
isLoading={ isRequesting }
|
||||
onQueryChange={ onQueryChange }
|
||||
onColumnsChange={ this.onColumnsChange }
|
||||
rows={ rows }
|
||||
rowsPerPage={ parseInt( query.per_page ) }
|
||||
summary={ summary }
|
||||
totalRows={ totalCount }
|
||||
totalRows={ totalResults }
|
||||
{ ...tableProps }
|
||||
/>
|
||||
);
|
||||
|
@ -79,10 +108,24 @@ class ReportTable extends Component {
|
|||
}
|
||||
|
||||
ReportTable.propTypes = {
|
||||
/**
|
||||
* The key for user preferences settings for column visibility.
|
||||
*/
|
||||
columnPrefsKey: PropTypes.string,
|
||||
/**
|
||||
* The endpoint to use in API calls.
|
||||
*/
|
||||
endpoint: PropTypes.string,
|
||||
/**
|
||||
* Name of the methods available via `select( 'wc-api' )` that will be used to
|
||||
* load more data for table items. If omitted, no call will be made and only
|
||||
* the data returned by the reports endpoint will be used.
|
||||
*/
|
||||
extendItemsMethodNames: PropTypes.shape( {
|
||||
getError: PropTypes.string,
|
||||
isRequesting: PropTypes.string,
|
||||
load: PropTypes.string,
|
||||
} ),
|
||||
/**
|
||||
* A function that returns the headers object to build the table.
|
||||
*/
|
||||
|
@ -124,16 +167,33 @@ ReportTable.defaultProps = {
|
|||
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { endpoint, getSummary, query, tableData, tableQuery } = props;
|
||||
const { endpoint, getSummary, query, tableData, tableQuery, columnPrefsKey } = props;
|
||||
const chartEndpoint = 'variations' === endpoint ? 'products' : endpoint;
|
||||
const primaryData = getSummary
|
||||
? getReportChartData( chartEndpoint, 'primary', query, select )
|
||||
: {};
|
||||
const queriedTableData = tableData || getReportTableData( endpoint, query, select, tableQuery );
|
||||
const extendedTableData = extendTableData( select, props, queriedTableData );
|
||||
|
||||
const selectProps = {
|
||||
primaryData,
|
||||
tableData: extendedTableData,
|
||||
};
|
||||
|
||||
if ( columnPrefsKey ) {
|
||||
const { getCurrentUserData } = select( 'wc-api' );
|
||||
const userData = getCurrentUserData();
|
||||
|
||||
selectProps.userPrefColumns = userData[ columnPrefsKey ];
|
||||
}
|
||||
|
||||
return selectProps;
|
||||
} ),
|
||||
withDispatch( dispatch => {
|
||||
const { updateCurrentUserData } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
primaryData,
|
||||
tableData: queriedTableData,
|
||||
updateCurrentUserData,
|
||||
};
|
||||
} )
|
||||
)( ReportTable );
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { first } from 'lodash';
|
||||
|
||||
export function extendTableData( select, props, queriedTableData ) {
|
||||
const { extendItemsMethodNames, itemIdField } = props;
|
||||
const itemsData = queriedTableData.items.data;
|
||||
if (
|
||||
! Array.isArray( itemsData ) ||
|
||||
! itemsData.length ||
|
||||
! extendItemsMethodNames ||
|
||||
! itemIdField
|
||||
) {
|
||||
return queriedTableData;
|
||||
}
|
||||
|
||||
const {
|
||||
[ extendItemsMethodNames.getError ]: getErrorMethod,
|
||||
[ extendItemsMethodNames.isRequesting ]: isRequestingMethod,
|
||||
[ extendItemsMethodNames.load ]: loadMethod,
|
||||
} = select( 'wc-api' );
|
||||
const extendQuery = {
|
||||
include: itemsData.map( item => item[ itemIdField ] ).join( ',' ),
|
||||
per_page: itemsData.length,
|
||||
};
|
||||
const extendedItems = loadMethod( extendQuery );
|
||||
const isExtendedItemsRequesting = isRequestingMethod ? isRequestingMethod( extendQuery ) : false;
|
||||
const isExtendedItemsError = getErrorMethod ? getErrorMethod( extendQuery ) : false;
|
||||
|
||||
const extendedItemsData = itemsData.map( item => {
|
||||
const extendedItemData = first(
|
||||
extendedItems.filter( extendedItem => item.id === extendedItem.id )
|
||||
);
|
||||
return {
|
||||
...item,
|
||||
...extendedItemData,
|
||||
};
|
||||
} );
|
||||
|
||||
const isRequesting = queriedTableData.isRequesting || isExtendedItemsRequesting;
|
||||
const isError = queriedTableData.isError || isExtendedItemsError;
|
||||
|
||||
return {
|
||||
...queriedTableData,
|
||||
isRequesting,
|
||||
isError,
|
||||
items: {
|
||||
...queriedTableData.items,
|
||||
data: extendedItemsData,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -98,15 +98,15 @@ export default class CategoriesReportTable extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
getSummary( totals, totalCount ) {
|
||||
getSummary( totals, totalResults ) {
|
||||
if ( ! totals ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: _n( 'category', 'categories', totalCount, 'wc-admin' ),
|
||||
value: numberFormat( totalCount ),
|
||||
label: _n( 'category', 'categories', totalResults, 'wc-admin' ),
|
||||
value: numberFormat( totalResults ),
|
||||
},
|
||||
{
|
||||
label: _n( 'item sold', 'items sold', totals.items_sold, 'wc-admin' ),
|
||||
|
@ -136,6 +136,7 @@ export default class CategoriesReportTable extends Component {
|
|||
itemIdField="category_id"
|
||||
query={ query }
|
||||
title={ __( 'Categories', 'wc-admin' ) }
|
||||
columnPrefsKey="categories_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -162,6 +162,7 @@ export default class CouponsReportTable extends Component {
|
|||
itemIdField="coupon_id"
|
||||
query={ query }
|
||||
title={ __( 'Coupons', 'wc-admin' ) }
|
||||
columnPrefsKey="coupons_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,13 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { getRequestByIdString } from '../../../lib/async-requests';
|
||||
import { NAMESPACE } from '../../../store/constants';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getRequestByIdString } from 'lib/async-requests';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
|
@ -30,7 +35,7 @@ export const advancedFilters = {
|
|||
name: {
|
||||
labels: {
|
||||
add: __( 'Name', 'wc-admin' ),
|
||||
placeholder: __( 'Search customer name', 'wc-admin' ),
|
||||
placeholder: __( 'Search', 'wc-admin' ),
|
||||
remove: __( 'Remove customer name filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer name filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
|
@ -58,6 +63,78 @@ export const advancedFilters = {
|
|||
} ) ),
|
||||
},
|
||||
},
|
||||
country: {
|
||||
labels: {
|
||||
add: __( 'Country', 'wc-admin' ),
|
||||
placeholder: __( 'Search', 'wc-admin' ),
|
||||
remove: __( 'Remove country filter', 'wc-admin' ),
|
||||
rule: __( 'Select a country filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Country {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select country', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'includes',
|
||||
/* translators: Sentence fragment, logical, "Includes" refers to countries including a given country or countries. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Includes', 'countries', 'wc-admin' ),
|
||||
},
|
||||
{
|
||||
value: 'excludes',
|
||||
/* translators: Sentence fragment, logical, "Excludes" refers to countries excluding a given country or countries. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Excludes', 'countries', 'wc-admin' ),
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Search',
|
||||
type: 'countries',
|
||||
getLabels: async value => {
|
||||
const countries =
|
||||
( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || [];
|
||||
|
||||
const allLabels = countries.map( country => ( {
|
||||
id: country.code,
|
||||
label: decodeEntities( country.name ),
|
||||
} ) );
|
||||
|
||||
const labels = value.split( ',' );
|
||||
return await allLabels.filter( label => {
|
||||
return labels.includes( label.id );
|
||||
} );
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
labels: {
|
||||
add: __( 'Email', 'wc-admin' ),
|
||||
placeholder: __( 'Search customer email', 'wc-admin' ),
|
||||
remove: __( 'Remove customer email filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer email filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a customer email filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Email {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select customer email', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'includes',
|
||||
/* translators: Sentence fragment, logical, "Includes" refers to customer emails including a given email(s). Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Includes', 'customer emails', 'wc-admin' ),
|
||||
},
|
||||
{
|
||||
value: 'excludes',
|
||||
/* translators: Sentence fragment, logical, "Excludes" refers to customer emails excluding a given email(s). Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
label: _x( 'Excludes', 'customer emails', 'wc-admin' ),
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Search',
|
||||
type: 'emails',
|
||||
getLabels: getRequestByIdString( NAMESPACE + 'customers', customer => ( {
|
||||
id: customer.id,
|
||||
label: customer.email,
|
||||
} ) ),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/*eslint-enable max-len*/
|
||||
|
|
|
@ -66,7 +66,7 @@ export default class CustomersReportTable extends Component {
|
|||
{
|
||||
label: __( 'AOV', 'wc-admin' ),
|
||||
screenReaderLabel: __( 'Average Order Value', 'wc-admin' ),
|
||||
key: 'average_order_value',
|
||||
key: 'avg_order_value',
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
|
@ -98,19 +98,20 @@ export default class CustomersReportTable extends Component {
|
|||
|
||||
return customers.map( customer => {
|
||||
const {
|
||||
average_order_value,
|
||||
id,
|
||||
city,
|
||||
country,
|
||||
avg_order_value,
|
||||
billing,
|
||||
date_last_active,
|
||||
date_sign_up,
|
||||
email,
|
||||
name,
|
||||
first_name,
|
||||
id,
|
||||
last_name,
|
||||
orders_count,
|
||||
postal_code,
|
||||
username,
|
||||
total_spend,
|
||||
} = customer;
|
||||
const { postcode, city, country } = billing || {};
|
||||
const name = `${ first_name } ${ last_name }`;
|
||||
|
||||
const customerNameLink = (
|
||||
<Link href={ 'user-edit.php?user_id=' + id } type="wp-admin">
|
||||
|
@ -144,8 +145,8 @@ export default class CustomersReportTable extends Component {
|
|||
value: getCurrencyFormatDecimal( total_spend ),
|
||||
},
|
||||
{
|
||||
display: average_order_value,
|
||||
value: getCurrencyFormatDecimal( average_order_value ),
|
||||
display: formatCurrency( avg_order_value ),
|
||||
value: getCurrencyFormatDecimal( avg_order_value ),
|
||||
},
|
||||
{
|
||||
display: formatDate( formats.tableFormat, date_last_active ),
|
||||
|
@ -160,8 +161,8 @@ export default class CustomersReportTable extends Component {
|
|||
value: city,
|
||||
},
|
||||
{
|
||||
display: postal_code,
|
||||
value: postal_code,
|
||||
display: postcode,
|
||||
value: postcode,
|
||||
},
|
||||
];
|
||||
} );
|
||||
|
@ -173,6 +174,11 @@ 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"
|
||||
|
@ -181,6 +187,7 @@ export default class CustomersReportTable extends Component {
|
|||
searchBy="customers"
|
||||
searchParam="name_includes"
|
||||
title={ __( 'Registered Customers', 'wc-admin' ) }
|
||||
columnPrefsKey="customers_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getRequestByIdString } from 'lib/async-requests';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
label: __( 'Show', 'wc-admin' ),
|
||||
staticParams: [],
|
||||
param: 'filter',
|
||||
showFilters: () => true,
|
||||
filters: [
|
||||
{ label: __( 'All Downloads', 'wc-admin' ), value: 'all' },
|
||||
{ label: __( 'Advanced Filters', 'wc-admin' ), value: 'advanced' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/*eslint-disable max-len*/
|
||||
export const advancedFilters = {
|
||||
title: _x(
|
||||
'Downloads Match {{select /}} Filters',
|
||||
'A sentence describing filters for Downloads. See screen shot for context: https://cloudup.com/ccxhyH2mEDg',
|
||||
'wc-admin'
|
||||
),
|
||||
filters: {
|
||||
product: {
|
||||
labels: {
|
||||
add: __( 'Product', 'wc-admin' ),
|
||||
placeholder: __( 'Search', 'wc-admin' ),
|
||||
remove: __( 'Remove product filter', 'wc-admin' ),
|
||||
rule: __( 'Select a product filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
title: __( 'Product {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select product', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'includes',
|
||||
/* translators: Sentence fragment, logical, "Includes" refers to products including a given product(s). Screenshot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
label: _x( 'Includes', 'products', 'wc-admin' ),
|
||||
},
|
||||
{
|
||||
value: 'excludes',
|
||||
/* translators: Sentence fragment, logical, "Excludes" refers to products excluding a products(s). Screenshot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
label: _x( 'Excludes', 'products', 'wc-admin' ),
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Search',
|
||||
type: 'products',
|
||||
getLabels: getRequestByIdString( NAMESPACE + 'products', product => ( {
|
||||
id: product.id,
|
||||
label: product.name,
|
||||
} ) ),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/*eslint-enable max-len*/
|
|
@ -0,0 +1,38 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { ReportFilters } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { filters, advancedFilters } from './config';
|
||||
|
||||
export default class DownloadsReport extends Component {
|
||||
render() {
|
||||
const { query, path } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ReportFilters
|
||||
query={ query }
|
||||
path={ path }
|
||||
filters={ filters }
|
||||
showDatePicker={ false }
|
||||
advancedFilters={ advancedFilters }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadsReport.propTypes = {
|
||||
query: PropTypes.object.isRequired,
|
||||
};
|
|
@ -24,6 +24,8 @@ import RevenueReport from './revenue';
|
|||
import CategoriesReport from './categories';
|
||||
import CouponsReport from './coupons';
|
||||
import TaxesReport from './taxes';
|
||||
import DownloadsReport from './downloads';
|
||||
import StockReport from './stock';
|
||||
import CustomersReport from './customers';
|
||||
|
||||
const REPORTS_FILTER = 'woocommerce-reports-list';
|
||||
|
@ -60,6 +62,16 @@ const getReports = () => {
|
|||
title: __( 'Taxes', 'wc-admin' ),
|
||||
component: TaxesReport,
|
||||
},
|
||||
{
|
||||
report: 'downloads',
|
||||
title: __( 'Downloads', 'wc-admin' ),
|
||||
component: DownloadsReport,
|
||||
},
|
||||
{
|
||||
report: 'stock',
|
||||
title: __( 'Stock', 'wc-admin' ),
|
||||
component: StockReport,
|
||||
},
|
||||
{
|
||||
report: 'customers',
|
||||
title: __( 'Customers', 'wc-admin' ),
|
||||
|
|
|
@ -250,6 +250,7 @@ class OrdersReportTable extends Component {
|
|||
query={ query }
|
||||
tableData={ tableData }
|
||||
title={ __( 'Orders', 'wc-admin' ) }
|
||||
columnPrefsKey="orders_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -261,7 +262,7 @@ export default compose(
|
|||
const datesFromQuery = getCurrentDates( query );
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
const { getOrders, getOrdersTotalCount, isGetOrdersError, isGetOrdersRequesting } = select(
|
||||
const { getOrders, getOrdersTotalCount, getOrdersError, isGetOrdersRequesting } = select(
|
||||
'wc-api'
|
||||
);
|
||||
|
||||
|
@ -277,14 +278,14 @@ export default compose(
|
|||
};
|
||||
const orders = getOrders( tableQuery );
|
||||
const ordersTotalCount = getOrdersTotalCount( tableQuery );
|
||||
const isError = isGetOrdersError( tableQuery );
|
||||
const isError = Boolean( getOrdersError( tableQuery ) );
|
||||
const isRequesting = isGetOrdersRequesting( tableQuery );
|
||||
|
||||
return {
|
||||
tableData: {
|
||||
items: {
|
||||
data: formatTableOrders( orders ),
|
||||
totalCount: ordersTotalCount,
|
||||
totalResults: ordersTotalCount,
|
||||
},
|
||||
isError,
|
||||
isRequesting,
|
||||
|
|
|
@ -178,6 +178,7 @@ export default class VariationsReportTable extends Component {
|
|||
extended_info: true,
|
||||
} }
|
||||
title={ __( 'Variations', 'wc-admin' ) }
|
||||
columnPrefsKey="variations_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -214,6 +214,7 @@ export default class ProductsReportTable extends Component {
|
|||
extended_info: true,
|
||||
} }
|
||||
title={ __( 'Products', 'wc-admin' ) }
|
||||
columnPrefsKey="products_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -162,15 +162,15 @@ class RevenueReportTable extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
getSummary( totals, totalCount ) {
|
||||
getSummary( totals, totalResults ) {
|
||||
if ( ! totals ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: _n( 'day', 'days', totalCount, 'wc-admin' ),
|
||||
value: numberFormat( totalCount ),
|
||||
label: _n( 'day', 'days', totalResults, 'wc-admin' ),
|
||||
value: numberFormat( totalResults ),
|
||||
},
|
||||
{
|
||||
label: _n( 'order', 'orders', totals.orders_count, 'wc-admin' ),
|
||||
|
@ -215,6 +215,7 @@ class RevenueReportTable extends Component {
|
|||
query={ query }
|
||||
tableData={ tableData }
|
||||
title={ __( 'Revenue', 'wc-admin' ) }
|
||||
columnPrefsKey="revenue_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -224,7 +225,7 @@ export default compose(
|
|||
withSelect( ( select, props ) => {
|
||||
const { query } = props;
|
||||
const datesFromQuery = getCurrentDates( query );
|
||||
const { getReportStats, isReportStatsRequesting, isReportStatsError } = select( 'wc-api' );
|
||||
const { getReportStats, getReportStatsError, isReportStatsRequesting } = select( 'wc-api' );
|
||||
|
||||
// TODO Support hour here when viewing a single day
|
||||
const tableQuery = {
|
||||
|
@ -237,14 +238,14 @@ export default compose(
|
|||
before: appendTimestamp( datesFromQuery.primary.before, 'end' ),
|
||||
};
|
||||
const revenueData = getReportStats( 'revenue', tableQuery );
|
||||
const isError = isReportStatsError( 'revenue', tableQuery );
|
||||
const isError = Boolean( getReportStatsError( 'revenue', tableQuery ) );
|
||||
const isRequesting = isReportStatsRequesting( 'revenue', tableQuery );
|
||||
|
||||
return {
|
||||
tableData: {
|
||||
items: {
|
||||
data: get( revenueData, [ 'data', 'intervals' ] ),
|
||||
totalCount: get( revenueData, [ 'totalResults' ] ),
|
||||
totalResults: get( revenueData, [ 'totalResults' ] ),
|
||||
},
|
||||
isError,
|
||||
isRequesting,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export const showDatePicker = false;
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
label: __( 'Show', 'wc-admin' ),
|
||||
staticParams: [],
|
||||
param: 'type',
|
||||
showFilters: () => true,
|
||||
filters: [
|
||||
{ label: __( 'All Products', 'wc-admin' ), value: 'all' },
|
||||
{ label: __( 'Out of Stock', 'wc-admin' ), value: 'outofstock' },
|
||||
{ label: __( 'Low Stock', 'wc-admin' ), value: 'lowstock' },
|
||||
{ label: __( 'In Stock', 'wc-admin' ), value: 'instock' },
|
||||
],
|
||||
},
|
||||
];
|
|
@ -0,0 +1,39 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { ReportFilters } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { showDatePicker, filters } from './config';
|
||||
import StockReportTable from './table';
|
||||
|
||||
export default class StockReport extends Component {
|
||||
render() {
|
||||
const { query, path } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ReportFilters
|
||||
query={ query }
|
||||
path={ path }
|
||||
showDatePicker={ showDatePicker }
|
||||
filters={ filters }
|
||||
/>
|
||||
<StockReportTable query={ query } />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StockReport.propTypes = {
|
||||
query: PropTypes.object.isRequired,
|
||||
};
|
|
@ -0,0 +1,145 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class StockReportTable extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.getHeadersContent = this.getHeadersContent.bind( this );
|
||||
this.getRowsContent = this.getRowsContent.bind( this );
|
||||
this.getSummary = this.getSummary.bind( this );
|
||||
}
|
||||
|
||||
getHeadersContent() {
|
||||
return [
|
||||
{
|
||||
label: __( 'Product / Variation', 'wc-admin' ),
|
||||
key: 'product_variation',
|
||||
required: true,
|
||||
isLeftAligned: true,
|
||||
},
|
||||
{
|
||||
label: __( 'SKU', 'wc-admin' ),
|
||||
key: 'sku',
|
||||
},
|
||||
{
|
||||
label: __( 'Status', 'wc-admin' ),
|
||||
key: 'stock_status',
|
||||
},
|
||||
{
|
||||
label: __( 'Stock', 'wc-admin' ),
|
||||
key: 'stock_quantity',
|
||||
isSortable: true,
|
||||
defaultSort: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getRowsContent( products ) {
|
||||
const { query } = this.props;
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
const { stockStatuses } = wcSettings;
|
||||
|
||||
return products.map( product => {
|
||||
const { id, name, parent_id, sku, stock_quantity, stock_status } = product;
|
||||
|
||||
const productDetailLink = getNewPath( persistedQuery, 'products', {
|
||||
filter: 'single_product',
|
||||
products: parent_id || id,
|
||||
} );
|
||||
|
||||
const formattedName = name.replace( ' - ', ' / ' );
|
||||
|
||||
const nameLink = (
|
||||
<Link href={ productDetailLink } type="wc-admin">
|
||||
{ formattedName }
|
||||
</Link>
|
||||
);
|
||||
|
||||
const stockStatusLink = (
|
||||
<Link href={ 'post.php?action=edit&post=' + parent_id || id } type="wp-admin">
|
||||
{ stockStatuses[ stock_status ] }
|
||||
</Link>
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
display: nameLink,
|
||||
value: formattedName,
|
||||
},
|
||||
{
|
||||
display: sku,
|
||||
value: sku,
|
||||
},
|
||||
{
|
||||
display: stockStatusLink,
|
||||
value: stock_status,
|
||||
},
|
||||
{
|
||||
display: numberFormat( stock_quantity ),
|
||||
value: stock_quantity,
|
||||
},
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
getSummary( totals ) {
|
||||
if ( ! totals ) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: _n( 'product', 'products', totals.products, 'wc-admin' ),
|
||||
value: numberFormat( totals.products ),
|
||||
},
|
||||
{
|
||||
label: __( 'out of stock', totals.out_of_stock, 'wc-admin' ),
|
||||
value: numberFormat( totals.out_of_stock ),
|
||||
},
|
||||
{
|
||||
label: __( 'low stock', totals.low_stock, 'wc-admin' ),
|
||||
value: numberFormat( totals.low_stock ),
|
||||
},
|
||||
{
|
||||
label: __( 'in stock', totals.in_stock, 'wc-admin' ),
|
||||
value: numberFormat( totals.in_stock ),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query } = this.props;
|
||||
|
||||
return (
|
||||
<ReportTable
|
||||
endpoint="stock"
|
||||
getHeadersContent={ this.getHeadersContent }
|
||||
getRowsContent={ this.getRowsContent }
|
||||
// getSummary={ this.getSummary }
|
||||
query={ query }
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'stock_quantity',
|
||||
order: query.order || 'desc',
|
||||
type: query.type || 'all',
|
||||
} }
|
||||
title={ __( 'Stock', 'wc-admin' ) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import { map } from 'lodash';
|
|||
*/
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getTaxCode } from './utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -71,25 +72,23 @@ export default class TaxesReportTable extends Component {
|
|||
|
||||
getRowsContent( taxes ) {
|
||||
return map( taxes, tax => {
|
||||
const { order_tax, orders_count, tax_rate_id, total_tax, shipping_tax } = tax;
|
||||
const { order_tax, orders_count, tax_rate, tax_rate_id, total_tax, shipping_tax } = tax;
|
||||
|
||||
// @TODO must link to the tax detail report
|
||||
const taxLink = (
|
||||
<Link href="" type="wc-admin">
|
||||
{ tax_rate_id }
|
||||
{ getTaxCode( tax ) }
|
||||
</Link>
|
||||
);
|
||||
|
||||
return [
|
||||
// @TODO it should be the tax code, not the tax ID
|
||||
{
|
||||
display: taxLink,
|
||||
value: tax_rate_id,
|
||||
},
|
||||
{
|
||||
// @TODO add `rate` once it's returned by the API
|
||||
display: '',
|
||||
value: '',
|
||||
display: tax_rate.toFixed( 2 ) + '%',
|
||||
value: tax_rate,
|
||||
},
|
||||
{
|
||||
display: formatCurrency( total_tax ),
|
||||
|
@ -115,12 +114,10 @@ export default class TaxesReportTable extends Component {
|
|||
if ( ! totals ) {
|
||||
return [];
|
||||
}
|
||||
// @TODO the number of total rows should come from the API
|
||||
const totalRows = 0;
|
||||
return [
|
||||
{
|
||||
label: _n( 'tax code', 'tax codes', totalRows, 'wc-admin' ),
|
||||
value: numberFormat( totalRows ),
|
||||
label: _n( 'tax code', 'tax codes', totals.tax_codes, 'wc-admin' ),
|
||||
value: numberFormat( totals.tax_codes ),
|
||||
},
|
||||
{
|
||||
label: __( 'total tax', 'wc-admin' ),
|
||||
|
@ -135,7 +132,7 @@ export default class TaxesReportTable extends Component {
|
|||
value: formatCurrency( totals.shipping_tax ),
|
||||
},
|
||||
{
|
||||
label: _n( 'order', 'orders', totals.orders_count, 'wc-admin' ),
|
||||
label: _n( 'order', 'orders', totals.orders, 'wc-admin' ),
|
||||
value: numberFormat( totals.orders_count ),
|
||||
},
|
||||
];
|
||||
|
@ -153,7 +150,11 @@ export default class TaxesReportTable extends Component {
|
|||
getSummary={ this.getSummary }
|
||||
itemIdField="tax_rate_id"
|
||||
query={ query }
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'tax_rate_id',
|
||||
} }
|
||||
title={ __( 'Taxes', 'wc-admin' ) }
|
||||
columnPrefsKey="taxes_report_columns"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ export class TopSellingProducts extends Component {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getReportStats, isReportStatsRequesting, isReportStatsError } = select( 'wc-admin' );
|
||||
const { getReportStats, getReportStatsError, isReportStatsRequesting } = select( 'wc-admin' );
|
||||
const endpoint = NAMESPACE + 'reports/products';
|
||||
// @TODO We will need to add the date parameters from the Date Picker
|
||||
// { after: '2018-04-22', before: '2018-05-06' }
|
||||
|
@ -131,7 +131,7 @@ export default compose(
|
|||
|
||||
const stats = getReportStats( endpoint, query );
|
||||
const isRequesting = isReportStatsRequesting( endpoint, query );
|
||||
const isError = isReportStatsError( endpoint, query );
|
||||
const isError = Boolean( getReportStatsError( endpoint, query ) );
|
||||
|
||||
return { data: get( stats, 'data', [] ), isRequesting, isError };
|
||||
} )
|
||||
|
|
|
@ -50,14 +50,14 @@ describe( 'TopSellingProducts', () => {
|
|||
test( 'should load report stats from API', () => {
|
||||
const getReportStatsMock = jest.fn().mockReturnValue( { data: mockData } );
|
||||
const isReportStatsRequestingMock = jest.fn().mockReturnValue( false );
|
||||
const isReportStatsErrorMock = jest.fn().mockReturnValue( false );
|
||||
const getReportStatsErrorMock = jest.fn().mockReturnValue( undefined );
|
||||
const registry = createRegistry();
|
||||
registry.registerStore( 'wc-admin', {
|
||||
reducer: () => {},
|
||||
selectors: {
|
||||
getReportStats: getReportStatsMock,
|
||||
isReportStatsRequesting: isReportStatsRequestingMock,
|
||||
isReportStatsError: isReportStatsErrorMock,
|
||||
getReportStatsError: getReportStatsErrorMock,
|
||||
},
|
||||
} );
|
||||
const topSellingProductsWrapper = TestRenderer.create(
|
||||
|
@ -74,8 +74,8 @@ describe( 'TopSellingProducts', () => {
|
|||
expect( getReportStatsMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
|
||||
expect( isReportStatsRequestingMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
|
||||
expect( isReportStatsRequestingMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
|
||||
expect( isReportStatsErrorMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
|
||||
expect( isReportStatsErrorMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
|
||||
expect( getReportStatsErrorMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
|
||||
expect( getReportStatsErrorMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
|
||||
expect( topSellingProducts.props.data ).toBe( mockData );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -23,5 +23,6 @@
|
|||
{ "component": "Summary", "render": "MySummaryList" },
|
||||
{ "component": "Table" },
|
||||
{ "component": "Tag" },
|
||||
{ "component": "TextControlWithAffixes" },
|
||||
{ "component": "ViewMoreList" }
|
||||
]
|
||||
|
|
|
@ -89,7 +89,7 @@ class InboxPanel extends Component {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getNotes, isGetNotesError, isGetNotesRequesting } = select( 'wc-api' );
|
||||
const { getNotes, getNotesError, isGetNotesRequesting } = select( 'wc-api' );
|
||||
const inboxQuery = {
|
||||
page: 1,
|
||||
per_page: QUERY_DEFAULTS.pageSize,
|
||||
|
@ -97,7 +97,7 @@ export default compose(
|
|||
};
|
||||
|
||||
const notes = getNotes( inboxQuery );
|
||||
const isError = isGetNotesError( inboxQuery );
|
||||
const isError = Boolean( getNotesError( inboxQuery ) );
|
||||
const isRequesting = isGetNotesRequesting( inboxQuery );
|
||||
|
||||
return { notes, isError, isRequesting };
|
||||
|
|
|
@ -178,7 +178,7 @@ OrdersPanel.defaultProps = {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getOrders, isGetOrdersError, isGetOrdersRequesting } = select( 'wc-api' );
|
||||
const { getOrders, getOrdersError, isGetOrdersRequesting } = select( 'wc-api' );
|
||||
const ordersQuery = {
|
||||
page: 1,
|
||||
per_page: QUERY_DEFAULTS.pageSize,
|
||||
|
@ -186,7 +186,7 @@ export default compose(
|
|||
};
|
||||
|
||||
const orders = getOrders( ordersQuery );
|
||||
const isError = isGetOrdersError( ordersQuery );
|
||||
const isError = Boolean( getOrdersError( ordersQuery ) );
|
||||
const isRequesting = isGetOrdersRequesting( ordersQuery );
|
||||
|
||||
return { orders, isError, isRequesting };
|
||||
|
|
|
@ -172,7 +172,7 @@ ReviewsPanel.defaultProps = {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getReviews, isGetReviewsError, isGetReviewsRequesting } = select( 'wc-api' );
|
||||
const { getReviews, getReviewsError, isGetReviewsRequesting } = select( 'wc-api' );
|
||||
const reviewsQuery = {
|
||||
page: 1,
|
||||
per_page: QUERY_DEFAULTS.pageSize,
|
||||
|
@ -180,7 +180,7 @@ export default compose(
|
|||
};
|
||||
|
||||
const reviews = getReviews( reviewsQuery );
|
||||
const isError = isGetReviewsError( reviewsQuery );
|
||||
const isError = Boolean( getReviewsError( reviewsQuery ) );
|
||||
const isRequesting = isGetReviewsRequesting( reviewsQuery );
|
||||
|
||||
return { reviews, isError, isRequesting };
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
async getReportItems( ...args ) {
|
||||
const [ endpoint, query ] = args.slice( -2 );
|
||||
|
||||
const swaggerEndpoints = [ 'categories', 'coupons', 'customers', 'taxes' ];
|
||||
const swaggerEndpoints = [ 'categories', 'coupons' ];
|
||||
if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
|
|
|
@ -23,12 +23,12 @@ export default {
|
|||
// TODO: Change to just `getNotes( endpoint, query )`
|
||||
// after Gutenberg plugin uses @wordpress/data 3+
|
||||
const [ endpoint, query ] = args.length === 2 ? args : args.slice( 1, 3 );
|
||||
const statEndpoints = [ 'orders', 'revenue', 'products' ];
|
||||
const statEndpoints = [ 'orders', 'revenue', 'products', 'taxes' ];
|
||||
|
||||
let apiPath = endpoint + stringifyQuery( query );
|
||||
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories', 'coupons', 'taxes' ];
|
||||
const swaggerEndpoints = [ 'categories', 'coupons' ];
|
||||
if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
apiPath = SWAGGERNAMESPACE + 'reports/' + endpoint + '/stats' + stringifyQuery( query );
|
||||
try {
|
||||
|
|
|
@ -64,13 +64,13 @@ describe( 'getReportChartData()', () => {
|
|||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportStats = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportStatsRequesting = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).isReportStatsError = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).getReportStatsError = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'wc-api' ).getReportStats.mockRestore();
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockRestore();
|
||||
select( 'wc-api' ).isReportStatsError.mockRestore();
|
||||
select( 'wc-api' ).getReportStatsError.mockRestore();
|
||||
} );
|
||||
|
||||
function setGetReportStats( func ) {
|
||||
|
@ -78,13 +78,11 @@ describe( 'getReportChartData()', () => {
|
|||
}
|
||||
|
||||
function setIsReportStatsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) =>
|
||||
func( ...args )
|
||||
);
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setIsReportStatsError( func ) {
|
||||
select( 'wc-api' ).isReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
function setGetReportStatsError( func ) {
|
||||
select( 'wc-api' ).getReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if first request is in progress', () => {
|
||||
|
@ -99,8 +97,8 @@ describe( 'getReportChartData()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return true;
|
||||
setGetReportStatsError( () => {
|
||||
return { error: 'Error' };
|
||||
} );
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
|
@ -127,8 +125,8 @@ describe( 'getReportChartData()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return false;
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
|
@ -161,8 +159,8 @@ describe( 'getReportChartData()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return false;
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( ( endpoint, query ) => {
|
||||
if ( 2 === query.page ) {
|
||||
|
@ -202,8 +200,8 @@ describe( 'getReportChartData()', () => {
|
|||
}
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return false;
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
|
@ -214,11 +212,11 @@ describe( 'getReportChartData()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( ( endpoint, query ) => {
|
||||
setGetReportStatsError( ( endpoint, query ) => {
|
||||
if ( 2 === query.page ) {
|
||||
return true;
|
||||
return { error: 'Error' };
|
||||
}
|
||||
return false;
|
||||
return undefined;
|
||||
} );
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
|
@ -228,8 +226,8 @@ describe( 'getReportChartData()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return false;
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
|
@ -264,13 +262,13 @@ describe( 'getSummaryNumbers()', () => {
|
|||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportStats = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportStatsRequesting = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).isReportStatsError = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).getReportStatsError = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'wc-api' ).getReportStats.mockRestore();
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockRestore();
|
||||
select( 'wc-api' ).isReportStatsError.mockRestore();
|
||||
select( 'wc-api' ).getReportStatsError.mockRestore();
|
||||
} );
|
||||
|
||||
function setGetReportStats( func ) {
|
||||
|
@ -278,13 +276,11 @@ describe( 'getSummaryNumbers()', () => {
|
|||
}
|
||||
|
||||
function setIsReportStatsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) =>
|
||||
func( ...args )
|
||||
);
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setIsReportStatsError( func ) {
|
||||
select( 'wc-api' ).isReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
function setGetReportStatsError( func ) {
|
||||
select( 'wc-api' ).getReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if a request is in progress', () => {
|
||||
|
@ -299,8 +295,8 @@ describe( 'getSummaryNumbers()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return true;
|
||||
setGetReportStatsError( () => {
|
||||
return { error: 'Error' };
|
||||
} );
|
||||
const result = getSummaryNumbers( 'revenue', query, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
|
@ -321,8 +317,8 @@ describe( 'getSummaryNumbers()', () => {
|
|||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setIsReportStatsError( () => {
|
||||
return false;
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
|
@ -464,31 +460,29 @@ describe( 'getReportTableData()', () => {
|
|||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportItems = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportItemsRequesting = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).isReportItemsError = jest.fn().mockReturnValue( false );
|
||||
select( 'wc-api' ).getReportItemsError = jest.fn().mockReturnValue( undefined );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'wc-api' ).getReportItems.mockRestore();
|
||||
select( 'wc-api' ).isReportItemsRequesting.mockRestore();
|
||||
select( 'wc-api' ).isReportItemsError.mockRestore();
|
||||
select( 'wc-api' ).getReportItemsError.mockRestore();
|
||||
} );
|
||||
|
||||
function setGetReportItems( func ) {
|
||||
select( 'wc-api' ).getReportItems.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setisReportItemsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportItemsRequesting.mockImplementation( ( ...args ) =>
|
||||
func( ...args )
|
||||
);
|
||||
function setIsReportItemsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportItemsRequesting.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setisReportItemsError( func ) {
|
||||
select( 'wc-api' ).isReportItemsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
function setGetReportItemsError( func ) {
|
||||
select( 'wc-api' ).getReportItemsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if a request is in progress', () => {
|
||||
setisReportItemsRequesting( () => true );
|
||||
setIsReportItemsRequesting( () => true );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
||||
|
@ -498,12 +492,12 @@ describe( 'getReportTableData()', () => {
|
|||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).isReportItemsError ).toHaveBeenCalledTimes( 0 );
|
||||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenCalledTimes( 0 );
|
||||
} );
|
||||
|
||||
it( 'returns isError if request errors', () => {
|
||||
setisReportItemsRequesting( () => false );
|
||||
setisReportItemsError( () => true );
|
||||
setIsReportItemsRequesting( () => false );
|
||||
setGetReportItemsError( () => ( { error: 'Error' } ) );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
||||
|
@ -513,16 +507,13 @@ describe( 'getReportTableData()', () => {
|
|||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).isReportItemsError ).toHaveBeenLastCalledWith(
|
||||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
} );
|
||||
|
||||
it( 'returns results after queries finish', () => {
|
||||
const items = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
setisReportItemsRequesting( () => false );
|
||||
setisReportItemsError( () => false );
|
||||
setIsReportItemsRequesting( () => false );
|
||||
setGetReportItemsError( () => undefined );
|
||||
setGetReportItems( () => items );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
@ -533,9 +524,6 @@ describe( 'getReportTableData()', () => {
|
|||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).isReportItemsError ).toHaveBeenLastCalledWith(
|
||||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -141,7 +141,7 @@ function getRequestQuery( endpoint, dataType, query ) {
|
|||
* @return {Object} Object containing summary number responses.
|
||||
*/
|
||||
export function getSummaryNumbers( endpoint, query, select ) {
|
||||
const { getReportStats, isReportStatsRequesting, isReportStatsError } = select( 'wc-api' );
|
||||
const { getReportStats, getReportStatsError, isReportStatsRequesting } = select( 'wc-api' );
|
||||
const response = {
|
||||
isRequesting: false,
|
||||
isError: false,
|
||||
|
@ -155,7 +155,7 @@ export function getSummaryNumbers( endpoint, query, select ) {
|
|||
const primary = getReportStats( endpoint, primaryQuery );
|
||||
if ( isReportStatsRequesting( endpoint, primaryQuery ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
} else if ( isReportStatsError( endpoint, primaryQuery ) ) {
|
||||
} else if ( getReportStatsError( endpoint, primaryQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ export function getSummaryNumbers( endpoint, query, select ) {
|
|||
const secondary = getReportStats( endpoint, secondaryQuery );
|
||||
if ( isReportStatsRequesting( endpoint, secondaryQuery ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
} else if ( isReportStatsError( endpoint, secondaryQuery ) ) {
|
||||
} else if ( getReportStatsError( endpoint, secondaryQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,7 @@ export function getSummaryNumbers( endpoint, query, select ) {
|
|||
* @return {Object} Object containing API request information (response, fetching, and error details)
|
||||
*/
|
||||
export function getReportChartData( endpoint, dataType, query, select ) {
|
||||
const { getReportStats, isReportStatsRequesting, isReportStatsError } = select( 'wc-api' );
|
||||
const { getReportStats, getReportStatsError, isReportStatsRequesting } = select( 'wc-api' );
|
||||
|
||||
const response = {
|
||||
isEmpty: false,
|
||||
|
@ -201,7 +201,7 @@ export function getReportChartData( endpoint, dataType, query, select ) {
|
|||
|
||||
if ( isReportStatsRequesting( endpoint, requestQuery ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
} else if ( isReportStatsError( endpoint, requestQuery ) ) {
|
||||
} else if ( getReportStatsError( endpoint, requestQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
} else if ( isReportDataEmpty( stats ) ) {
|
||||
return { ...response, isEmpty: true };
|
||||
|
@ -224,7 +224,7 @@ export function getReportChartData( endpoint, dataType, query, select ) {
|
|||
if ( isReportStatsRequesting( endpoint, nextQuery ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( isReportStatsError( endpoint, nextQuery ) ) {
|
||||
if ( getReportStatsError( endpoint, nextQuery ) ) {
|
||||
isError = true;
|
||||
isFetching = false;
|
||||
break;
|
||||
|
@ -298,7 +298,7 @@ export function getReportTableQuery( endpoint, urlQuery, query ) {
|
|||
* @return {Object} Object Table data response
|
||||
*/
|
||||
export function getReportTableData( endpoint, urlQuery, select, query = {} ) {
|
||||
const { getReportItems, isReportItemsRequesting, isReportItemsError } = select( 'wc-api' );
|
||||
const { getReportItems, getReportItemsError, isReportItemsRequesting } = select( 'wc-api' );
|
||||
|
||||
const tableQuery = reportsUtils.getReportTableQuery( endpoint, urlQuery, query );
|
||||
const response = {
|
||||
|
@ -313,7 +313,7 @@ export function getReportTableData( endpoint, urlQuery, select, query = {} ) {
|
|||
const items = getReportItems( endpoint, tableQuery );
|
||||
if ( isReportItemsRequesting( endpoint, tableQuery ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
} else if ( isReportItemsError( endpoint, tableQuery ) ) {
|
||||
} else if ( getReportItemsError( endpoint, tableQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import operations from './operations';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
operations,
|
||||
selectors,
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/** @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,
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/** @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,
|
||||
};
|
|
@ -21,6 +21,11 @@ const getNotes = ( getResource, requireResource ) => (
|
|||
return notes;
|
||||
};
|
||||
|
||||
const getNotesError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'note-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isGetNotesRequesting = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'note-query', query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
@ -36,13 +41,8 @@ const isGetNotesRequesting = getResource => ( query = {} ) => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isGetNotesError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'note-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
export default {
|
||||
getNotes,
|
||||
getNotesError,
|
||||
isGetNotesRequesting,
|
||||
isGetNotesError,
|
||||
};
|
||||
|
|
|
@ -21,6 +21,11 @@ const getOrders = ( getResource, requireResource ) => (
|
|||
return orders;
|
||||
};
|
||||
|
||||
const getOrdersError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const getOrdersTotalCount = ( getResource, requireResource ) => (
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
|
@ -40,14 +45,9 @@ const isGetOrdersRequesting = getResource => ( query = {} ) => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isGetOrdersError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
export default {
|
||||
getOrders,
|
||||
getOrdersError,
|
||||
getOrdersTotalCount,
|
||||
isGetOrdersRequesting,
|
||||
isGetOrdersError,
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ import { NAMESPACE } from '../../constants';
|
|||
import { SWAGGERNAMESPACE } from 'store/constants';
|
||||
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories', 'coupons', 'customers', 'taxes' ];
|
||||
const swaggerEndpoints = [ 'categories', 'coupons', 'customers' ];
|
||||
|
||||
const typeEndpointMap = {
|
||||
'report-items-query-orders': 'orders',
|
||||
|
@ -28,6 +28,7 @@ const typeEndpointMap = {
|
|||
'report-items-query-taxes': 'taxes',
|
||||
'report-items-query-variations': 'variations',
|
||||
'report-items-query-customers': 'customers',
|
||||
'report-items-query-stock': 'stock',
|
||||
};
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
|
|
|
@ -20,6 +20,11 @@ const getReportItems = ( getResource, requireResource ) => (
|
|||
return requireResource( requirement, resourceName ) || {};
|
||||
};
|
||||
|
||||
const getReportItemsError = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-items-query-${ type }`, query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isReportItemsRequesting = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-items-query-${ type }`, query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
@ -31,13 +36,8 @@ const isReportItemsRequesting = getResource => ( type, query = {} ) => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isReportItemsError = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-items-query-${ type }`, query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
export default {
|
||||
getReportItems,
|
||||
getReportItemsError,
|
||||
isReportItemsRequesting,
|
||||
isReportItemsError,
|
||||
};
|
||||
|
|
|
@ -16,9 +16,9 @@ import { getResourceIdentifier, getResourcePrefix } from '../../utils';
|
|||
import { NAMESPACE } from '../../constants';
|
||||
import { SWAGGERNAMESPACE } from 'store/constants';
|
||||
|
||||
const statEndpoints = [ 'orders', 'revenue', 'products' ];
|
||||
const statEndpoints = [ 'orders', 'revenue', 'products', 'taxes' ];
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories', 'coupons', 'taxes' ];
|
||||
const swaggerEndpoints = [ 'categories', 'coupons' ];
|
||||
|
||||
const typeEndpointMap = {
|
||||
'report-stats-query-orders': 'orders',
|
||||
|
|
|
@ -22,6 +22,11 @@ const getReportStats = ( getResource, requireResource ) => (
|
|||
return data;
|
||||
};
|
||||
|
||||
const getReportStatsError = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-stats-query-${ type }`, query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isReportStatsRequesting = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-stats-query-${ type }`, query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
@ -33,13 +38,8 @@ const isReportStatsRequesting = getResource => ( type, query = {} ) => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isReportStatsError = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `report-stats-query-${ type }`, query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
export default {
|
||||
getReportStats,
|
||||
getReportStatsError,
|
||||
isReportStatsRequesting,
|
||||
isReportStatsError,
|
||||
};
|
||||
|
|
|
@ -29,6 +29,11 @@ const getReviewsTotalCount = ( getResource, requireResource ) => (
|
|||
return requireResource( requirement, resourceName ).totalCount || 0;
|
||||
};
|
||||
|
||||
const getReviewsError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'review-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isGetReviewsRequesting = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'review-query', query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
@ -40,14 +45,9 @@ const isGetReviewsRequesting = getResource => ( query = {} ) => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isGetReviewsError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'review-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
export default {
|
||||
getReviews,
|
||||
getReviewsError,
|
||||
getReviewsTotalCount,
|
||||
isGetReviewsRequesting,
|
||||
isGetReviewsError,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import operations from './operations';
|
||||
import selectors from './selectors';
|
||||
import mutations from './mutations';
|
||||
|
||||
export default {
|
||||
operations,
|
||||
selectors,
|
||||
mutations,
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/** @format */
|
||||
|
||||
const updateCurrentUserData = operations => userDataFields => {
|
||||
const resourceKey = 'current-user-data';
|
||||
operations.update( [ resourceKey ], { [ resourceKey ]: userDataFields } );
|
||||
};
|
||||
|
||||
export default {
|
||||
updateCurrentUserData,
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { mapValues, pick } from 'lodash';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
return [ ...readCurrentUserData( resourceNames, fetch ) ];
|
||||
}
|
||||
|
||||
function update( resourceNames, data, fetch = apiFetch ) {
|
||||
return [ ...updateCurrentUserData( resourceNames, data, fetch ) ];
|
||||
}
|
||||
|
||||
function readCurrentUserData( resourceNames, fetch ) {
|
||||
if ( resourceNames.includes( 'current-user-data' ) ) {
|
||||
const url = '/wp/v2/users/me?context=edit';
|
||||
|
||||
return [
|
||||
fetch( { path: url } )
|
||||
.then( userToUserDataResource )
|
||||
.catch( error => {
|
||||
return { [ 'current-user-data' ]: { error: String( error.message ) } };
|
||||
} ),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function updateCurrentUserData( resourceNames, data, fetch ) {
|
||||
const resourceName = 'current-user-data';
|
||||
const userDataFields = [
|
||||
'categories_report_columns',
|
||||
'coupons_report_columns',
|
||||
'customers_report_columns',
|
||||
'orders_report_columns',
|
||||
'products_report_columns',
|
||||
'revenue_report_columns',
|
||||
'taxes_report_columns',
|
||||
'variations_report_columns',
|
||||
];
|
||||
|
||||
if ( resourceNames.includes( resourceName ) ) {
|
||||
const url = '/wp/v2/users/me';
|
||||
const userData = pick( data[ resourceName ], userDataFields );
|
||||
const meta = mapValues( userData, JSON.stringify );
|
||||
const user = { woocommerce_meta: meta };
|
||||
|
||||
return [
|
||||
fetch( { path: url, method: 'POST', data: user } )
|
||||
.then( userToUserDataResource )
|
||||
.catch( error => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} ),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function userToUserDataResource( user ) {
|
||||
const userData = mapValues( user.woocommerce_meta, data => {
|
||||
if ( ! data || 0 === data.length ) {
|
||||
return '';
|
||||
}
|
||||
return JSON.parse( data );
|
||||
} );
|
||||
return { [ 'current-user-data' ]: { data: userData } };
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
update,
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getCurrentUserData = ( getResource, requireResource ) => (
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
return requireResource( requirement, 'current-user-data' ).data || {};
|
||||
};
|
||||
|
||||
export default {
|
||||
getCurrentUserData,
|
||||
};
|
|
@ -3,31 +3,43 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import customers from './customers';
|
||||
import notes from './notes';
|
||||
import orders from './orders';
|
||||
import reportItems from './reports/items';
|
||||
import reportStats from './reports/stats';
|
||||
import reviews from './reviews';
|
||||
import user from './user';
|
||||
|
||||
function createWcApiSpec() {
|
||||
return {
|
||||
mutations: {
|
||||
...user.mutations,
|
||||
},
|
||||
selectors: {
|
||||
...customers.selectors,
|
||||
...notes.selectors,
|
||||
...orders.selectors,
|
||||
...reportItems.selectors,
|
||||
...reportStats.selectors,
|
||||
...reviews.selectors,
|
||||
...user.selectors,
|
||||
},
|
||||
operations: {
|
||||
read( resourceNames ) {
|
||||
return [
|
||||
...customers.operations.read( resourceNames ),
|
||||
...notes.operations.read( resourceNames ),
|
||||
...orders.operations.read( resourceNames ),
|
||||
...reportItems.operations.read( resourceNames ),
|
||||
...reportStats.operations.read( resourceNames ),
|
||||
...reviews.operations.read( resourceNames ),
|
||||
...user.operations.read( resourceNames ),
|
||||
];
|
||||
},
|
||||
update( resourceNames, data ) {
|
||||
return [ ...user.operations.update( resourceNames, data ) ];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,9 +43,9 @@ const withSelect = mapSelectToProps =>
|
|||
super( props );
|
||||
|
||||
this.onStoreChange = this.onStoreChange.bind( this );
|
||||
|
||||
this.subscribe( props.registry );
|
||||
|
||||
this.onUnmounts = {};
|
||||
this.mergeProps = this.getNextMergeProps( props );
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ const withSelect = mapSelectToProps =>
|
|||
getNextMergeProps( props ) {
|
||||
const storeSelectors = {};
|
||||
const onCompletes = [];
|
||||
const onUnmounts = {};
|
||||
const componentContext = { component: this };
|
||||
|
||||
const getStoreFromRegistry = ( key, registry, context ) => {
|
||||
|
@ -69,8 +70,9 @@ const withSelect = mapSelectToProps =>
|
|||
if ( isFunction( selectorsForKey ) ) {
|
||||
// This store has special handling for its selectors.
|
||||
// We give it a context, and we check for a "resolve"
|
||||
const { selectors, onComplete } = selectorsForKey( context );
|
||||
const { selectors, onComplete, onUnmount } = selectorsForKey( context );
|
||||
onComplete && onCompletes.push( onComplete );
|
||||
onUnmount && ( onUnmounts[ key ] = onUnmount );
|
||||
storeSelectors[ key ] = selectors;
|
||||
} else {
|
||||
storeSelectors[ key ] = selectorsForKey;
|
||||
|
@ -107,6 +109,7 @@ const withSelect = mapSelectToProps =>
|
|||
componentWillUnmount() {
|
||||
this.canRunSelection = false;
|
||||
this.unsubscribe();
|
||||
Object.keys( this.onUnmounts ).forEach( key => this.onUnmounts[ key ]() );
|
||||
}
|
||||
|
||||
shouldComponentUpdate( nextProps, nextState ) {
|
||||
|
|
|
@ -39,8 +39,8 @@ function createWcApiStore() {
|
|||
return getComponentSelectors( component );
|
||||
},
|
||||
getActions() {
|
||||
// TODO: Add mutations here.
|
||||
return {};
|
||||
const mutations = apiClient.getMutations();
|
||||
return mutations;
|
||||
},
|
||||
subscribe: apiClient.subscribe,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
* [Overview](/)
|
||||
* [Components](components/)
|
||||
* [Data](data)
|
||||
* [Documentation](documentation)
|
||||
* [Layout](layout)
|
||||
* [CSS Structure](stylesheets)
|
||||
* [Examples](examples/)
|
||||
|
|
|
@ -27,4 +27,5 @@
|
|||
* [Summary](components/summary.md)
|
||||
* [Table](components/table.md)
|
||||
* [Tag](components/tag.md)
|
||||
* [TextControlWithAffixes](components/text-control-with-affixes.md)
|
||||
* [ViewMoreList](components/view-more-list.md)
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
`TextControlWithAffixes` (component)
|
||||
====================================
|
||||
|
||||
This component is essentially a wrapper (really a reimplementation) around the
|
||||
TextControl component that adds support for affixes, i.e. the ability to display
|
||||
a fixed part either at the beginning or at the end of the text input.
|
||||
|
||||
Props
|
||||
-----
|
||||
|
||||
### `label`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
If this property is added, a label will be generated using label property as the content.
|
||||
|
||||
### `help`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
If this property is added, a help text will be generated using help property as the content.
|
||||
|
||||
### `type`
|
||||
|
||||
- Type: String
|
||||
- Default: `'text'`
|
||||
|
||||
Type of the input element to render. Defaults to "text".
|
||||
|
||||
### `value`
|
||||
|
||||
- **Required**
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
The current value of the input.
|
||||
|
||||
### `className`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
The class that will be added with "components-base-control" to the classes of the wrapper div.
|
||||
If no className is passed only components-base-control is used.
|
||||
|
||||
### `onChange`
|
||||
|
||||
- **Required**
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
A function that receives the value of the input.
|
||||
|
||||
### `prefix`
|
||||
|
||||
- Type: ReactNode
|
||||
- Default: null
|
||||
|
||||
Markup to be inserted at the beginning of the input.
|
||||
|
||||
### `suffix`
|
||||
|
||||
- Type: ReactNode
|
||||
- Default: null
|
||||
|
||||
Markup to be appended at the end of the input.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# How To Document Components
|
||||
|
||||
We rely on inline documentation and a markdown example file for both [the docs site on github.io,](https://woocommerce.github.io/wc-admin/#/) and the DevDocs section in wp-admin. This allows us to keep the docs site up-to-date if any components change, because the documentation is kept in the same place as the code. Read on for how to build out the docs files and devdocs section.
|
||||
|
||||
## 1. Add the inline documentation to generate a markdown file
|
||||
|
||||
This consists of a docblock for a component description at the top of the component, correct `PropTypes` definitions for all props used, and docblocks for each prop in PropTypes. See [count/index.js](https://github.com/woocommerce/wc-admin/blob/master/packages/components/src/count/index.js) for a simple example, or [table/table.js](https://github.com/woocommerce/wc-admin/blob/master/packages/components/src/table/table.js) for a very detailed example.
|
||||
|
||||
## 2. Generate the docs with `npm run docs`
|
||||
|
||||
This creates the file in `/docs/components/` for your new component, or adds it to the existing component doc (if this is a sub-component, like TablePlaceholder).
|
||||
|
||||
You can test this by running `npx docsify serve docs`, it will spin up a server at localhost:3000 to preview the docs. This also live-reloads as you're making changes.
|
||||
|
||||
## 3. Add an `example.md` file
|
||||
|
||||
You can use [`card/example.md`](https://raw.githubusercontent.com/woocommerce/wc-admin/master/packages/components/src/card/example.md) as a simple example, or [`pagination/example.md`](https://raw.githubusercontent.com/woocommerce/wc-admin/master/packages/components/src/pagination/example.md) as an example with state ([using `withState` HoC from gutenberg](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src/with-state)).
|
||||
|
||||
## 4. Add your example to `client/devdocs/examples.json`
|
||||
|
||||
Keep these alphabetized. Optional properties here are `render` and `filePath`. `render` defaults to `My{ComponentName}`, and `filePath` defaults to `/docs/component/{component-name-as-slug}`.
|
||||
|
||||
Now you can visit `/wp-admin/admin.php?page=wc-admin#/devdocs` to see your component in action.
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Customers Controller
|
||||
*
|
||||
* Handles requests to /customers/*
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Customers controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Customers_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Customers_Controller extends WC_REST_Customers_Controller {
|
||||
|
||||
// TODO Add support for guests here. See https://wp.me/p7bje6-1dM.
|
||||
|
||||
/**
|
||||
* Searches emails by partial search instead of a strict match.
|
||||
* See "search parameters" under https://codex.wordpress.org/Class_Reference/WP_User_Query.
|
||||
*
|
||||
* @param array $prepared_args Prepared search filter args from the customer endpoint.
|
||||
* @param array $request Request/query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public static function update_search_filters( $prepared_args, $request ) {
|
||||
if ( ! empty( $request['email'] ) ) {
|
||||
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
|
||||
}
|
||||
return $prepared_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
// Allow partial email matches. Previously, this was of format 'email' which required a strict "test@example.com" format.
|
||||
// This, in combination with `update_search_filters` allows us to do partial searches.
|
||||
$params['email']['format'] = '';
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_rest_customer_query', array( 'WC_Admin_REST_Customers_Controller', 'update_search_filters' ), 10, 2 );
|
|
@ -0,0 +1,393 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Reports stock controller
|
||||
*
|
||||
* Handles requests to the /reports/stock endpoint.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST API Reports stock controller class.
|
||||
*
|
||||
* @package WooCommerce/API
|
||||
* @extends WC_REST_Reports_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'reports/stock';
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param WP_REST_Request $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
$args['offset'] = $request['offset'];
|
||||
$args['order'] = $request['order'];
|
||||
$args['orderby'] = $request['orderby'];
|
||||
$args['paged'] = $request['page'];
|
||||
$args['post__in'] = $request['include'];
|
||||
$args['post__not_in'] = $request['exclude'];
|
||||
$args['posts_per_page'] = $request['per_page'];
|
||||
$args['post_parent__in'] = $request['parent'];
|
||||
$args['post_parent__not_in'] = $request['parent_exclude'];
|
||||
|
||||
if ( 'date' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'date ID';
|
||||
} elseif ( 'stock_quantity' === $args['orderby'] ) {
|
||||
$args['meta_key'] = '_stock'; // WPCS: slow query ok.
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
} elseif ( 'include' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'post__in';
|
||||
} elseif ( 'id' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'ID'; // ID must be capitalized.
|
||||
} elseif ( 'slug' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'name';
|
||||
}
|
||||
|
||||
$args['post_type'] = array( 'product', 'product_variation' );
|
||||
|
||||
if ( 'lowstock' === $request['type'] ) {
|
||||
$low_stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
|
||||
$no_stock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
|
||||
|
||||
$args['meta_query'] = array( // WPCS: slow query ok.
|
||||
array(
|
||||
'key' => '_manage_stock',
|
||||
'value' => 'yes',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock',
|
||||
'value' => array( $no_stock, $low_stock ),
|
||||
'compare' => 'BETWEEN',
|
||||
'type' => 'NUMERIC',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'value' => 'instock',
|
||||
),
|
||||
);
|
||||
} elseif ( in_array( $request['type'], array_keys( wc_get_product_stock_status_options() ), true ) ) {
|
||||
$args['meta_query'] = array( // WPCS: slow query ok.
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'value' => $request['type'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$query_args['ignore_sticky_posts'] = true;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query products.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_products( $query_args ) {
|
||||
$query = new WP_Query();
|
||||
$result = $query->query( $query_args );
|
||||
|
||||
$total_posts = $query->found_posts;
|
||||
if ( $total_posts < 1 ) {
|
||||
// Out-of-bounds, run the query again without LIMIT for total count.
|
||||
unset( $query_args['paged'] );
|
||||
$count_query = new WP_Query();
|
||||
$count_query->query( $query_args );
|
||||
$total_posts = $count_query->found_posts;
|
||||
}
|
||||
|
||||
return array(
|
||||
'objects' => array_map( 'wc_get_product', $result ),
|
||||
'total' => (int) $total_posts,
|
||||
'pages' => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$query_results = $this->get_products( $query_args );
|
||||
|
||||
$objects = array();
|
||||
foreach ( $query_results['objects'] as $object ) {
|
||||
$data = $this->prepare_item_for_response( $object, $request );
|
||||
$objects[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$page = (int) $query_args['paged'];
|
||||
$max_pages = $query_results['pages'];
|
||||
|
||||
$response = rest_ensure_response( $objects );
|
||||
$response->header( 'X-WP-Total', $query_results['total'] );
|
||||
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
||||
|
||||
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||
|
||||
if ( $page > 1 ) {
|
||||
$prev_page = $page - 1;
|
||||
if ( $prev_page > $max_pages ) {
|
||||
$prev_page = $max_pages;
|
||||
}
|
||||
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $max_pages > $page ) {
|
||||
$next_page = $page + 1;
|
||||
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param WC_Product $product Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_item_for_response( $product, $request ) {
|
||||
$data = array(
|
||||
'id' => $product->get_id(),
|
||||
'parent_id' => $product->get_parent_id(),
|
||||
'name' => $product->get_name(),
|
||||
'sku' => $product->get_sku(),
|
||||
'stock_status' => $product->get_stock_status(),
|
||||
'stock_quantity' => (float) $product->get_stock_quantity(),
|
||||
);
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
$response->add_links( $this->prepare_links( $product ) );
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
*
|
||||
* Allows modification of the report data right before it is returned.
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WC_Product $product The original product object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_stock', $response, $product, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param WC_Product $product Object data.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $product ) {
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$links = array(
|
||||
'product' => array(
|
||||
'href' => rest_url( sprintf( '/%s/products/%d/variations/%d', $this->namespace, $product->get_parent_id(), $product->get_id() ) ),
|
||||
),
|
||||
'parent' => array(
|
||||
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_parent_id() ) ),
|
||||
),
|
||||
);
|
||||
} elseif ( $product->get_parent_id() ) {
|
||||
$links = array(
|
||||
'product' => array(
|
||||
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_id() ) ),
|
||||
),
|
||||
'parent' => array(
|
||||
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_parent_id() ) ),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
$links = array(
|
||||
'product' => array(
|
||||
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_id() ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'report_stock',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'parent_id' => array(
|
||||
'description' => __( 'Product parent ID.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Product name.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'sku' => array(
|
||||
'description' => __( 'Unique identifier.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'stock_status' => array(
|
||||
'description' => __( 'Stock status.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_keys( wc_get_product_stock_status_options() ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'stock_quantity' => array(
|
||||
'description' => __( 'Stock quantity.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = array();
|
||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||
$params['page'] = array(
|
||||
'description' => __( 'Current page of the collection.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'minimum' => 1,
|
||||
);
|
||||
$params['per_page'] = array(
|
||||
'description' => __( 'Maximum number of items to be returned in result set.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'default' => 10,
|
||||
'minimum' => 1,
|
||||
'maximum' => 100,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['exclude'] = array(
|
||||
'description' => __( 'Ensure result set excludes specific IDs.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['include'] = array(
|
||||
'description' => __( 'Limit result set to specific ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['offset'] = array(
|
||||
'description' => __( 'Offset the result set by a specific number of items.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'asc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'stock_quantity',
|
||||
'enum' => array(
|
||||
'stock_quantity',
|
||||
'date',
|
||||
'id',
|
||||
'include',
|
||||
'title',
|
||||
'slug',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['parent'] = array(
|
||||
'description' => __( 'Limit result set to those of particular parent IDs.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'default' => array(),
|
||||
);
|
||||
$params['parent_exclude'] = array(
|
||||
'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'default' => array(),
|
||||
);
|
||||
$params['type'] = array(
|
||||
'description' => __( 'Limit result set to items assigned a stock report type.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'all',
|
||||
'enum' => array_merge( array( 'all', 'lowstock' ), array_keys( wc_get_product_stock_status_options() ) ),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
|
@ -31,6 +31,36 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr
|
|||
*/
|
||||
protected $rest_base = 'reports/taxes/stats';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_reports_taxes_stats_select_query', array( $this, 'set_default_report_data' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default results to 0 if API returns an empty array
|
||||
*
|
||||
* @param Mixed $results Report data.
|
||||
* @return object
|
||||
*/
|
||||
public function set_default_report_data( $results ) {
|
||||
if ( empty( $results ) ) {
|
||||
$results = new stdClass();
|
||||
$results->total = 0;
|
||||
$results->totals = new stdClass();
|
||||
$results->totals->tax_codes = 0;
|
||||
$results->totals->total_tax = 0;
|
||||
$results->totals->order_tax = 0;
|
||||
$results->totals->shipping_tax = 0;
|
||||
$results->totals->orders = 0;
|
||||
$results->intervals = array();
|
||||
$results->pages = 1;
|
||||
$results->page_no = 1;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
|
@ -150,7 +180,7 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr
|
|||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'order_count' => array(
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Amount of orders.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
|
|
|
@ -76,6 +76,7 @@ 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-customers-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-products-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';
|
||||
|
@ -94,9 +95,11 @@ class WC_Admin_Api_Init {
|
|||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-stock-controller.php';
|
||||
|
||||
$controllers = array(
|
||||
'WC_Admin_REST_Admin_Notes_Controller',
|
||||
'WC_Admin_REST_Customers_Controller',
|
||||
'WC_Admin_REST_Products_Controller',
|
||||
'WC_Admin_REST_Product_Reviews_Controller',
|
||||
'WC_Admin_REST_Reports_Controller',
|
||||
|
@ -111,6 +114,7 @@ class WC_Admin_Api_Init {
|
|||
'WC_Admin_REST_Reports_Taxes_Stats_Controller',
|
||||
'WC_Admin_REST_Reports_Coupons_Controller',
|
||||
'WC_Admin_REST_Reports_Coupons_Stats_Controller',
|
||||
'WC_Admin_REST_Reports_Stock_Controller',
|
||||
);
|
||||
|
||||
foreach ( $controllers as $controller ) {
|
||||
|
@ -165,6 +169,17 @@ class WC_Admin_Api_Init {
|
|||
$endpoints['/wc/v3/products'][1] = $endpoints['/wc/v3/products'][3];
|
||||
}
|
||||
|
||||
// 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
|
||||
) {
|
||||
$endpoints['/wc/v3/customers'][0] = $endpoints['/wc/v3/customers'][2];
|
||||
$endpoints['/wc/v3/customers'][1] = $endpoints['/wc/v3/customers'][3];
|
||||
}
|
||||
|
||||
// Override /wc/v3/products/$id.
|
||||
if ( isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'] )
|
||||
&& isset( $endpoints['/wc/v3/products/(?P<id>[\d]+)'][5] )
|
||||
|
|
|
@ -30,7 +30,7 @@ class WC_Admin_Reports_Taxes_Stats_Data_Store extends WC_Admin_Reports_Data_Stor
|
|||
'total_tax' => 'floatval',
|
||||
'order_tax' => 'floatval',
|
||||
'shipping_tax' => 'floatval',
|
||||
'order_count' => 'intval',
|
||||
'orders_count' => 'intval',
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ class WC_Admin_Reports_Taxes_Stats_Data_Store extends WC_Admin_Reports_Data_Stor
|
|||
'total_tax' => 'SUM(total_tax) AS total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'order_count' => 'COUNT(DISTINCT order_id) as orders',
|
||||
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -198,6 +198,10 @@ class WC_Admin_Reports_Taxes_Stats_Data_Store extends WC_Admin_Reports_Data_Stor
|
|||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $intervals );
|
||||
$this->create_interval_subtotals( $intervals );
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
|
|
|
@ -133,6 +133,22 @@ function wc_admin_register_pages() {
|
|||
)
|
||||
);
|
||||
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'title' => __( 'Downloads', 'wc-admin' ),
|
||||
'parent' => '/analytics/revenue',
|
||||
'path' => '/analytics/downloads',
|
||||
)
|
||||
);
|
||||
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'title' => __( 'Stock', 'wc-admin' ),
|
||||
'parent' => '/analytics/revenue',
|
||||
'path' => '/analytics/stock',
|
||||
)
|
||||
);
|
||||
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'title' => __( 'Customers', 'wc-admin' ),
|
||||
|
@ -338,5 +354,80 @@ function woocommerce_embed_page_header() {
|
|||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
add_action( 'in_admin_header', 'woocommerce_embed_page_header' );
|
||||
|
||||
/**
|
||||
* Registers WooCommerce specific user data to the WordPress user API.
|
||||
*/
|
||||
function wc_admin_register_user_data() {
|
||||
register_rest_field(
|
||||
'user',
|
||||
'woocommerce_meta',
|
||||
array(
|
||||
'get_callback' => 'wc_admin_get_user_data_values',
|
||||
'update_callback' => 'wc_admin_update_user_data_values',
|
||||
'schema' => null,
|
||||
)
|
||||
);
|
||||
}
|
||||
add_action( 'rest_api_init', 'wc_admin_register_user_data' );
|
||||
|
||||
/**
|
||||
* We store some WooCommerce specific user meta attached to users endpoint,
|
||||
* so that we can track certain preferences or values such as the inbox activity panel last open time.
|
||||
* Additional fields can be added in the function below, and then used via wc-admin's currentUser data.
|
||||
*
|
||||
* @return array Fields to expose over the WP user endpoint.
|
||||
*/
|
||||
function wc_admin_get_user_data_fields() {
|
||||
$user_data_fields = array(
|
||||
'categories_report_columns',
|
||||
'coupons_report_columns',
|
||||
'customers_report_columns',
|
||||
'orders_report_columns',
|
||||
'products_report_columns',
|
||||
'revenue_report_columns',
|
||||
'taxes_report_columns',
|
||||
'variations_report_columns',
|
||||
);
|
||||
|
||||
return apply_filters( 'wc_admin_get_user_data_fields', $user_data_fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* For all the registered user data fields ( wc_admin_get_user_data_fields ), fetch the data
|
||||
* for returning via the REST API.
|
||||
*
|
||||
* @param WP_User $user Current user.
|
||||
*/
|
||||
function wc_admin_get_user_data_values( $user ) {
|
||||
$values = array();
|
||||
foreach ( wc_admin_get_user_data_fields() as $field ) {
|
||||
$values[ $field ] = get_user_meta( $user['id'], 'wc_admin_' . $field, true );
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* For all the registered user data fields ( wc_admin_get_user_data_fields ), update the data
|
||||
* for the REST API.
|
||||
*
|
||||
* @param array $values The new values for the meta.
|
||||
* @param WP_User $user The current user.
|
||||
* @param string $field_id The field id for the user meta.
|
||||
*/
|
||||
function wc_admin_update_user_data_values( $values, $user, $field_id ) {
|
||||
if ( empty( $values ) || ! is_array( $values ) || 'woocommerce_meta' !== $field_id ) {
|
||||
return;
|
||||
}
|
||||
$fields = wc_admin_get_user_data_fields();
|
||||
$updates = array();
|
||||
foreach ( $values as $field => $value ) {
|
||||
if ( in_array( $field, $fields, true ) ) {
|
||||
$updates[ $field ] = $value;
|
||||
update_user_meta( $user->ID, 'wc_admin_' . $field, $value );
|
||||
}
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,22 @@ function wc_admin_print_script_settings() {
|
|||
$tracking_script .= "window._tkq = window._tkq || [];\n";
|
||||
$tracking_script .= "document.head.appendChild( wc_tracking_script );\n";
|
||||
}
|
||||
|
||||
$preload_data_endpoints = array(
|
||||
'countries' => '/wc/v3/data/countries',
|
||||
);
|
||||
|
||||
if ( function_exists( 'gutenberg_preload_api_request' ) ) {
|
||||
$preload_function = 'gutenberg_preload_api_request';
|
||||
} else {
|
||||
$preload_function = 'rest_preload_api_request';
|
||||
}
|
||||
|
||||
$preload_data = array_reduce(
|
||||
array_values( $preload_data_endpoints ),
|
||||
$preload_function
|
||||
);
|
||||
|
||||
/**
|
||||
* TODO: On merge, once plugin images are added to core WooCommerce, `wcAdminAssetUrl` can be retired, and
|
||||
* `wcAssetUrl` can be used in its place throughout the codebase.
|
||||
|
@ -163,6 +179,10 @@ function wc_admin_print_script_settings() {
|
|||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'trackingEnabled' => $tracking_enabled,
|
||||
);
|
||||
|
||||
foreach ( $preload_data_endpoints as $key => $endpoint ) {
|
||||
$settings['dataEndpoints'][ $key ] = $preload_data[ $endpoint ]['body'];
|
||||
}
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
<?php
|
||||
|
|
|
@ -272,6 +272,7 @@ function wc_admin_currency_settings() {
|
|||
'code' => $code,
|
||||
'precision' => wc_get_price_decimals(),
|
||||
'symbol' => get_woocommerce_currency_symbol( $code ),
|
||||
'position' => get_option( 'woocommerce_currency_pos' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1289,7 +1289,7 @@
|
|||
},
|
||||
"globby": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
|
||||
"integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -1740,7 +1740,7 @@
|
|||
},
|
||||
"globby": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
|
||||
"integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -3546,11 +3546,6 @@
|
|||
"commander": "^2.11.0"
|
||||
}
|
||||
},
|
||||
"arity-n": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz",
|
||||
"integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U="
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
|
||||
|
@ -3686,7 +3681,7 @@
|
|||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -4759,11 +4754,6 @@
|
|||
"parse5": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"chickencurry": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chickencurry/-/chickencurry-1.1.1.tgz",
|
||||
"integrity": "sha1-AmVfKyazvC7hrh5TFoht4463lzg="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||
|
@ -5198,14 +5188,6 @@
|
|||
"resolved": "https://registry.npmjs.org/component-xor/-/component-xor-0.0.4.tgz",
|
||||
"integrity": "sha1-xV2DzMG5TNUImk6T+niRxyY+Wao="
|
||||
},
|
||||
"compose-function": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/compose-function/-/compose-function-2.0.0.tgz",
|
||||
"integrity": "sha1-5kL6fh2iFSlyADFHZ3b8JGkawLA=",
|
||||
"requires": {
|
||||
"arity-n": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
@ -6313,7 +6295,7 @@
|
|||
},
|
||||
"regexpu-core": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
|
||||
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6951,8 +6933,9 @@
|
|||
}
|
||||
},
|
||||
"docsify-cli": {
|
||||
"version": "github:docsifyjs/docsify-cli#5df389962a0eb69ac02701b297b77b2fbb85ec28",
|
||||
"from": "github:docsifyjs/docsify-cli#5df38996",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/docsify-cli/-/docsify-cli-4.3.0.tgz",
|
||||
"integrity": "sha512-88O1sMeoZv4lb5GPSJzDtOAv2KzBjpQaSqVlVqY+6hGJfb2wpz9PvlUhvlgPq54zu4kPDeCCyUYgqa/llhKg3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^1.1.3",
|
||||
|
@ -7831,7 +7814,7 @@
|
|||
},
|
||||
"espree": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
|
||||
"integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
|
||||
"requires": {
|
||||
"acorn": "^5.5.0",
|
||||
|
@ -7883,7 +7866,7 @@
|
|||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -10458,15 +10441,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"html-to-react": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.3.tgz",
|
||||
"integrity": "sha512-4Qi5/t8oBr6c1t1kBJKyxEeJu0lb7ctvq29oFZioiUHH0Wz88VWGwoXuH26HDt9v64bDHA4NMPNTH8bVrcaJWA==",
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.4.tgz",
|
||||
"integrity": "sha512-/tWDdb/8Koi/QEP5YUY1653PcDpBnnMblXRhotnTuhFDjI1Fc6Wzox5d4sw73Xk5rM2OdM5np4AYjT/US/Wj7Q==",
|
||||
"requires": {
|
||||
"domhandler": "^2.3.0",
|
||||
"domhandler": "^2.4.2",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"htmlparser2": "^3.8.3",
|
||||
"ramda": "^0.25.0",
|
||||
"underscore.string.fp": "^1.0.4"
|
||||
"htmlparser2": "^3.10.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"ramda": "^0.26"
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
|
@ -13079,6 +13062,11 @@
|
|||
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
|
@ -14082,7 +14070,7 @@
|
|||
},
|
||||
"camelcase-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -14130,7 +14118,7 @@
|
|||
},
|
||||
"meow": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -15977,11 +15965,24 @@
|
|||
}
|
||||
},
|
||||
"prismjs": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz",
|
||||
"integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=",
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.15.0.tgz",
|
||||
"integrity": "sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA==",
|
||||
"requires": {
|
||||
"clipboard": "^1.5.5"
|
||||
"clipboard": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz",
|
||||
"integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"private": {
|
||||
|
@ -16179,9 +16180,9 @@
|
|||
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
|
||||
"integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ=="
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
|
||||
"integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ=="
|
||||
},
|
||||
"randexp": {
|
||||
"version": "0.4.6",
|
||||
|
@ -16240,11 +16241,34 @@
|
|||
"dev": true
|
||||
},
|
||||
"raw-loader": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz",
|
||||
"integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz",
|
||||
"integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^1.1.0",
|
||||
"schema-utils": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv-keywords": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
|
||||
"integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
|
||||
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.1.0",
|
||||
"ajv-errors": "^1.0.0",
|
||||
"ajv-keywords": "^3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
|
@ -16441,6 +16465,16 @@
|
|||
"prismjs": "1.6",
|
||||
"prop-types": "^15.5.8",
|
||||
"unescape": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"prismjs": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz",
|
||||
"integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=",
|
||||
"requires": {
|
||||
"clipboard": "^1.5.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-moment-proptypes": {
|
||||
|
@ -17501,11 +17535,6 @@
|
|||
"integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
|
||||
"dev": true
|
||||
},
|
||||
"reverse-arguments": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/reverse-arguments/-/reverse-arguments-1.0.0.tgz",
|
||||
"integrity": "sha1-woCVo6khrHFdYYNN3s6QJ5kmZ80="
|
||||
},
|
||||
"rgb": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rgb/-/rgb-0.1.0.tgz",
|
||||
|
@ -18077,7 +18106,7 @@
|
|||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -18643,7 +18672,7 @@
|
|||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -20141,22 +20170,6 @@
|
|||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
|
||||
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
|
||||
},
|
||||
"underscore.string": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-3.0.3.tgz",
|
||||
"integrity": "sha1-Rhe4waJQz25QZPu7Nj0PqWzxRVI="
|
||||
},
|
||||
"underscore.string.fp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/underscore.string.fp/-/underscore.string.fp-1.0.4.tgz",
|
||||
"integrity": "sha1-BUs/GEO8rlYShsh95eiHm0/Jg2Q=",
|
||||
"requires": {
|
||||
"chickencurry": "1.1.1",
|
||||
"compose-function": "^2.0.0",
|
||||
"reverse-arguments": "1.0.0",
|
||||
"underscore.string": "3.0.3"
|
||||
}
|
||||
},
|
||||
"unescape": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unescape/-/unescape-0.2.0.tgz",
|
||||
|
@ -21437,7 +21450,7 @@
|
|||
},
|
||||
"yargs": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
|
||||
"integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
|
||||
"requires": {
|
||||
"cliui": "^4.0.0",
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
"css-loader": "2.0.0",
|
||||
"deasync": "0.1.14",
|
||||
"deep-freeze": "0.0.1",
|
||||
"docsify-cli": "github:docsifyjs/docsify-cli#5df38996",
|
||||
"docsify-cli": "4.3.0",
|
||||
"eslint": "5.10.0",
|
||||
"eslint-config-wpcalypso": "4.0.1",
|
||||
"eslint-loader": "2.1.1",
|
||||
|
@ -89,7 +89,7 @@
|
|||
"postcss-loader": "3.0.0",
|
||||
"prettier": "github:automattic/calypso-prettier#c56b4251",
|
||||
"prop-types": "15.6.2",
|
||||
"raw-loader": "0.5.1",
|
||||
"raw-loader": "1.0.0",
|
||||
"react-docgen": "2.21.0",
|
||||
"readline-sync": "1.4.9",
|
||||
"recast": "0.16.1",
|
||||
|
@ -130,10 +130,11 @@
|
|||
"gfm-code-blocks": "1.0.0",
|
||||
"gridicons": "3.1.1",
|
||||
"history": "4.7.2",
|
||||
"html-to-react": "1.3.3",
|
||||
"html-to-react": "1.3.4",
|
||||
"interpolate-components": "1.1.1",
|
||||
"lodash": "^4.17.11",
|
||||
"marked": "0.5.2",
|
||||
"prismjs": "^1.15.0",
|
||||
"qs": "^6.5.2",
|
||||
"react-click-outside": "3.0.1",
|
||||
"react-dates": "^18.0.4",
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
- Update `<Table />` to use header keys to denote which columns are shown
|
||||
- Add `onColumnsChange` property to `<Table />` which is called when columns are shown/hidden
|
||||
- Add country autocompleter to search component
|
||||
- Add customer email autocompleter to search component
|
||||
- Adding new `<Chart />` component.
|
||||
- Added new `showDatePicker` prop to `<Filters />` component which allows to use the filters component without the date picker.
|
||||
- Added new taxes and customers autocompleters, and added support for using them within `<Filters />`.
|
||||
- Bug fix for `<SummaryNumber />` returning N/A instead of zero.
|
||||
- Bug fix for screen reader label IDs in `<Table />` header.
|
||||
- Added new component `<TextControlWithAffixes />`.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import Card from '../../card';
|
|||
import Link from '../../link';
|
||||
import SelectFilter from './select-filter';
|
||||
import SearchFilter from './search-filter';
|
||||
import NumberFilter from './number-filter';
|
||||
|
||||
const matches = [
|
||||
{ value: 'all', label: __( 'All', 'wc-admin' ) },
|
||||
|
@ -191,6 +192,15 @@ class AdvancedFilters extends Component {
|
|||
query={ query }
|
||||
/>
|
||||
) }
|
||||
{ 'Number' === input.component && (
|
||||
<NumberFilter
|
||||
filter={ filter }
|
||||
config={ config.filters[ key ] }
|
||||
onFilterChange={ this.onFilterChange }
|
||||
isEnglish={ isEnglish }
|
||||
query={ query }
|
||||
/>
|
||||
) }
|
||||
<IconButton
|
||||
className="woocommerce-filters-advanced__remove"
|
||||
label={ labels.remove }
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { SelectControl, TextControl } from '@wordpress/components';
|
||||
import { get, find, partial } from 'lodash';
|
||||
import interpolateComponents from 'interpolate-components';
|
||||
import classnames from 'classnames';
|
||||
import { _x } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TextControlWithAffixes from '../../text-control-with-affixes';
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
class NumberFilter extends Component {
|
||||
getBetweenString() {
|
||||
return _x(
|
||||
'{{rangeStart /}}{{span}}and{{/span}}{{rangeEnd /}}',
|
||||
'Numerical range inputs arranged on a single line',
|
||||
'wc-admin'
|
||||
);
|
||||
}
|
||||
|
||||
getLegend( filter, config ) {
|
||||
const inputType = get( config, [ 'input', 'type' ], 'number' );
|
||||
const rule = find( config.rules, { value: filter.rule } ) || {};
|
||||
let [ rangeStart, rangeEnd ] = ( filter.value || '' ).split( ',' );
|
||||
|
||||
if ( 'currency' === inputType ) {
|
||||
rangeStart = formatCurrency( rangeStart );
|
||||
rangeEnd = formatCurrency( rangeEnd );
|
||||
}
|
||||
|
||||
let filterStr = rangeStart;
|
||||
|
||||
if ( 'between' === rule.value ) {
|
||||
filterStr = interpolateComponents( {
|
||||
mixedString: this.getBetweenString(),
|
||||
components: {
|
||||
rangeStart: <Fragment>{ rangeStart }</Fragment>,
|
||||
rangeEnd: <Fragment>{ rangeEnd }</Fragment>,
|
||||
span: <Fragment />,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
return interpolateComponents( {
|
||||
mixedString: config.labels.title,
|
||||
components: {
|
||||
filter: <span>{ filterStr }</span>,
|
||||
rule: <span>{ rule.label }</span>,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
getFormControl( type, value, onChange ) {
|
||||
if ( 'currency' === type ) {
|
||||
const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ] );
|
||||
const symbolPosition = get( wcSettings, [ 'currency', 'position' ] );
|
||||
|
||||
return (
|
||||
0 === symbolPosition.indexOf( 'right' )
|
||||
? <TextControlWithAffixes
|
||||
suffix={ <span dangerouslySetInnerHTML={ { __html: currencySymbol } } /> }
|
||||
className="woocommerce-filters-advanced__input"
|
||||
type="number"
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
: <TextControlWithAffixes
|
||||
prefix={ <span dangerouslySetInnerHTML={ { __html: currencySymbol } } /> }
|
||||
className="woocommerce-filters-advanced__input"
|
||||
type="number"
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextControl
|
||||
className="woocommerce-filters-advanced__input"
|
||||
type="number"
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getFilterInputs() {
|
||||
const { config, filter, onFilterChange } = this.props;
|
||||
const inputType = get( config, [ 'input', 'type' ], 'number' );
|
||||
|
||||
if ( 'between' === filter.rule ) {
|
||||
return this.getRangeInput();
|
||||
}
|
||||
|
||||
const [ rangeStart, rangeEnd ] = ( filter.value || '' ).split( ',' );
|
||||
if ( Boolean( rangeEnd ) ) {
|
||||
// If there's a value for rangeEnd, we've just changed from "between"
|
||||
// to "less than" or "more than" and need to transition the value
|
||||
onFilterChange( filter.key, 'value', rangeStart || rangeEnd );
|
||||
}
|
||||
|
||||
return this.getFormControl(
|
||||
inputType,
|
||||
rangeStart || rangeEnd,
|
||||
partial( onFilterChange, filter.key, 'value' )
|
||||
);
|
||||
}
|
||||
|
||||
getRangeInput() {
|
||||
const { config, filter, onFilterChange } = this.props;
|
||||
const inputType = get( config, [ 'input', 'type' ], 'number' );
|
||||
const [ rangeStart, rangeEnd ] = ( filter.value || '' ).split( ',' );
|
||||
|
||||
const rangeStartOnChange = ( newRangeStart ) => {
|
||||
const newValue = [ newRangeStart, rangeEnd ].join( ',' );
|
||||
onFilterChange( filter.key, 'value', newValue );
|
||||
};
|
||||
|
||||
const rangeEndOnChange = ( newRangeEnd ) => {
|
||||
const newValue = [ rangeStart, newRangeEnd ].join( ',' );
|
||||
onFilterChange( filter.key, 'value', newValue );
|
||||
};
|
||||
|
||||
return interpolateComponents( {
|
||||
mixedString: this.getBetweenString(),
|
||||
components: {
|
||||
rangeStart: this.getFormControl( inputType, rangeStart, rangeStartOnChange ),
|
||||
rangeEnd: this.getFormControl( inputType, rangeEnd, rangeEndOnChange ),
|
||||
span: <span className="separator" />,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { config, filter, onFilterChange, isEnglish } = this.props;
|
||||
const { key, rule } = filter;
|
||||
const { labels, rules } = config;
|
||||
|
||||
const children = interpolateComponents( {
|
||||
mixedString: labels.title,
|
||||
components: {
|
||||
rule: (
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__rule"
|
||||
options={ rules }
|
||||
value={ rule }
|
||||
onChange={ partial( onFilterChange, key, 'rule' ) }
|
||||
aria-label={ labels.rule }
|
||||
/>
|
||||
),
|
||||
filter: (
|
||||
<div
|
||||
className={ classnames( 'woocommerce-filters-advanced__input-numeric-range', {
|
||||
'is-between': 'between' === rule,
|
||||
} ) }
|
||||
>
|
||||
{ this.getFilterInputs() }
|
||||
</div>
|
||||
),
|
||||
},
|
||||
} );
|
||||
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/
|
||||
return (
|
||||
<fieldset tabIndex="0">
|
||||
<legend className="screen-reader-text">
|
||||
{ this.getLegend( filter, config ) }
|
||||
</legend>
|
||||
<div
|
||||
className={ classnames( 'woocommerce-filters-advanced__fieldset', {
|
||||
'is-english': isEnglish,
|
||||
} ) }
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberFilter;
|
|
@ -171,3 +171,26 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters-advanced__input-numeric-range {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
&.is-between {
|
||||
grid-template-columns: 1fr 36px 1fr;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 38px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.separator {
|
||||
padding: 0 8px;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,57 @@ const advancedFilters = {
|
|||
defaultOption: 'new',
|
||||
},
|
||||
},
|
||||
quantity: {
|
||||
labels: {
|
||||
add: 'Item Quantity',
|
||||
remove: 'Remove item quantity filter',
|
||||
rule: 'Select an item quantity filter match',
|
||||
title: 'Item Quantity is {{rule /}} {{filter /}}',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'lessthan',
|
||||
label: 'Less Than',
|
||||
},
|
||||
{
|
||||
value: 'morethan',
|
||||
label: 'More Than',
|
||||
},
|
||||
{
|
||||
value: 'between',
|
||||
label: 'Between',
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Number',
|
||||
},
|
||||
},
|
||||
subtotal: {
|
||||
labels: {
|
||||
add: 'Subtotal',
|
||||
remove: 'Remove subtotal filter',
|
||||
rule: 'Select a subtotal filter match',
|
||||
title: 'Subtotal is {{rule /}} {{filter /}}',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
value: 'lessthan',
|
||||
label: 'Less Than',
|
||||
},
|
||||
{
|
||||
value: 'morethan',
|
||||
label: 'More Than',
|
||||
},
|
||||
{
|
||||
value: 'between',
|
||||
label: 'Between',
|
||||
},
|
||||
],
|
||||
input: {
|
||||
component: 'Number',
|
||||
type: 'currency',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -45,5 +45,6 @@ export { default as EmptyTable } from './table/empty';
|
|||
export { default as TablePlaceholder } from './table/placeholder';
|
||||
export { default as TableSummary } from './table/summary';
|
||||
export { default as Tag } from './tag';
|
||||
export { default as TextControlWithAffixes } from './text-control-with-affixes';
|
||||
export { default as useFilters } from './higher-order/use-filters';
|
||||
export { default as ViewMoreList } from './view-more-list';
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { computeSuggestionMatch } from './utils';
|
||||
import Flag from '../../flag';
|
||||
|
||||
/**
|
||||
* A country completer.
|
||||
* See https://github.com/WordPress/gutenberg/tree/master/packages/components/src/autocomplete#the-completer-interface
|
||||
*
|
||||
* @type {Completer}
|
||||
*/
|
||||
export default {
|
||||
name: 'countries',
|
||||
className: 'woocommerce-search__country-result',
|
||||
options() {
|
||||
return wcSettings.dataEndpoints.countries || [];
|
||||
},
|
||||
getOptionKeywords( country ) {
|
||||
return [ decodeEntities( country.name ) ];
|
||||
},
|
||||
getOptionLabel( country, query ) {
|
||||
const name = decodeEntities( country.name );
|
||||
const match = computeSuggestionMatch( name, query ) || {};
|
||||
return [
|
||||
<Flag
|
||||
key="thumbnail"
|
||||
className="woocommerce-search__result-thumbnail"
|
||||
code={ country.code }
|
||||
width={ 18 }
|
||||
height={ 18 }
|
||||
/>,
|
||||
<span key="name" className="woocommerce-search__result-name" aria-label={ name }>
|
||||
{ match.suggestionBeforeMatch }
|
||||
<strong className="components-form-token-field__suggestion-match">
|
||||
{ match.suggestionMatch }
|
||||
</strong>
|
||||
{ match.suggestionAfterMatch }
|
||||
</span>,
|
||||
];
|
||||
},
|
||||
// This is slightly different than gutenberg/Autocomplete, we don't support different methods
|
||||
// of replace/insertion, so we can just return the value.
|
||||
getOptionCompletion( country ) {
|
||||
const value = {
|
||||
id: country.code,
|
||||
label: decodeEntities( country.name ),
|
||||
};
|
||||
return value;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { computeSuggestionMatch } from './utils';
|
||||
|
||||
/**
|
||||
* A customer email completer.
|
||||
* See https://github.com/WordPress/gutenberg/tree/master/packages/components/src/autocomplete#the-completer-interface
|
||||
*
|
||||
* @type {Completer}
|
||||
*/
|
||||
export default {
|
||||
name: 'emails',
|
||||
className: 'woocommerce-search__emails-result',
|
||||
options( search ) {
|
||||
let payload = '';
|
||||
if ( search ) {
|
||||
const query = {
|
||||
email: search,
|
||||
per_page: 10,
|
||||
};
|
||||
payload = stringifyQuery( query );
|
||||
}
|
||||
return apiFetch( { path: `/wc/v3/customers${ payload }` } );
|
||||
},
|
||||
isDebounced: true,
|
||||
getOptionKeywords( customer ) {
|
||||
return [ customer.email ];
|
||||
},
|
||||
getOptionLabel( customer, query ) {
|
||||
const match = computeSuggestionMatch( customer.email, query ) || {};
|
||||
return [
|
||||
<span key="name" className="woocommerce-search__result-name" aria-label={ customer.email }>
|
||||
{ match.suggestionBeforeMatch }
|
||||
<strong className="components-form-token-field__suggestion-match">
|
||||
{ match.suggestionMatch }
|
||||
</strong>
|
||||
{ match.suggestionAfterMatch }
|
||||
</span>,
|
||||
];
|
||||
},
|
||||
// This is slightly different than gutenberg/Autocomplete, we don't support different methods
|
||||
// of replace/insertion, so we can just return the value.
|
||||
getOptionCompletion( customer ) {
|
||||
return {
|
||||
id: customer.id,
|
||||
label: customer.email,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -2,8 +2,10 @@
|
|||
/**
|
||||
* Export all autocompleters
|
||||
*/
|
||||
export { default as countries } from './countries';
|
||||
export { default as coupons } from './coupons';
|
||||
export { default as customers } from './customers';
|
||||
export { default as emails } from './emails';
|
||||
export { default as product } from './product';
|
||||
export { default as productCategory } from './product-cat';
|
||||
export { default as variations } from './variations';
|
||||
|
|
|
@ -12,8 +12,7 @@ import { stringifyQuery } from '@woocommerce/navigation';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { computeSuggestionMatch } from './utils';
|
||||
import { getTaxCode } from 'analytics/report/taxes/utils';
|
||||
import { computeSuggestionMatch, getTaxCode } from './utils';
|
||||
|
||||
/**
|
||||
* A tax completer.
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Parse a string suggestion, split apart by where the first matching query is.
|
||||
* Used to display matched partial in bold.
|
||||
|
@ -19,3 +24,15 @@ export function computeSuggestionMatch( suggestion, query ) {
|
|||
suggestionAfterMatch: suggestion.substring( indexOfMatch + query.length ),
|
||||
};
|
||||
}
|
||||
|
||||
export function getTaxCode( tax ) {
|
||||
return [ tax.country, tax.state, tax.name || __( 'TAX', 'wc-admin' ), tax.priority ]
|
||||
.map( item =>
|
||||
item
|
||||
.toString()
|
||||
.toUpperCase()
|
||||
.trim()
|
||||
)
|
||||
.filter( Boolean )
|
||||
.join( '-' );
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import classnames from 'classnames';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import Autocomplete from './autocomplete';
|
||||
import { coupons, customers, product, productCategory, taxes, variations } from './autocompleters';
|
||||
import { countries, coupons, customers, emails, product, productCategory, taxes, variations } from './autocompleters';
|
||||
import Tag from '../tag';
|
||||
|
||||
/**
|
||||
|
@ -66,18 +66,22 @@ class Search extends Component {
|
|||
|
||||
getAutocompleter() {
|
||||
switch ( this.props.type ) {
|
||||
case 'countries':
|
||||
return countries;
|
||||
case 'coupons':
|
||||
return coupons;
|
||||
case 'customers':
|
||||
return customers;
|
||||
case 'emails':
|
||||
return emails;
|
||||
case 'products':
|
||||
return product;
|
||||
case 'product_cats':
|
||||
return productCategory;
|
||||
case 'coupons':
|
||||
return coupons;
|
||||
case 'taxes':
|
||||
return taxes;
|
||||
case 'variations':
|
||||
return variations;
|
||||
case 'customers':
|
||||
return customers;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -219,11 +223,13 @@ Search.propTypes = {
|
|||
* The object type to be used in searching.
|
||||
*/
|
||||
type: PropTypes.oneOf( [
|
||||
'countries',
|
||||
'coupons',
|
||||
'customers',
|
||||
'emails',
|
||||
'orders',
|
||||
'products',
|
||||
'product_cats',
|
||||
'orders',
|
||||
'customers',
|
||||
'coupons',
|
||||
'taxes',
|
||||
'variations',
|
||||
] ).isRequired,
|
||||
|
@ -238,7 +244,10 @@ Search.propTypes = {
|
|||
*/
|
||||
selected: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
id: PropTypes.number.isRequired,
|
||||
id: PropTypes.oneOfType( [
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
] ).isRequired,
|
||||
label: PropTypes.string,
|
||||
} )
|
||||
),
|
||||
|
|
|
@ -28,4 +28,5 @@
|
|||
@import 'summary/style.scss';
|
||||
@import 'table/style.scss';
|
||||
@import 'tag/style.scss';
|
||||
@import 'text-control-with-affixes/style.scss';
|
||||
@import 'view-more-list/style.scss';
|
||||
|
|
|
@ -82,7 +82,11 @@ Tag.propTypes = {
|
|||
/**
|
||||
* The ID for this item, used in the remove function.
|
||||
*/
|
||||
id: PropTypes.number,
|
||||
id: PropTypes.oneOfType( [
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
] ),
|
||||
|
||||
/**
|
||||
* The name for this item, displayed as the tag's text.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
```jsx
|
||||
import { TextControlWithAffixes } from '@woocommerce/components';
|
||||
|
||||
const MyTextControlWithAffixes = withState( {
|
||||
first: '',
|
||||
second: '',
|
||||
third: '',
|
||||
fourth: '',
|
||||
fifth: '',
|
||||
} )( ( { first, second, third, fourth, fifth, setState } ) => (
|
||||
<div>
|
||||
<TextControlWithAffixes
|
||||
label="Text field without affixes"
|
||||
value={ first }
|
||||
placeholder="Placeholder"
|
||||
onChange={ value => setState( { first: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with a prefix"
|
||||
value={ second }
|
||||
onChange={ value => setState( { second: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="Prefix"
|
||||
suffix="Suffix"
|
||||
label="Text field with both affixes"
|
||||
value={ third }
|
||||
onChange={ value => setState( { third: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
suffix="%"
|
||||
label="Text field with a suffix"
|
||||
value={ fourth }
|
||||
onChange={ value => setState( { fourth: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with prefix and help text"
|
||||
value={ fifth }
|
||||
onChange={ value => setState( { fifth: value } ) }
|
||||
help="This is some help text."
|
||||
/>
|
||||
</div>
|
||||
) );
|
||||
```
|
|
@ -0,0 +1,119 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BaseControl } from '@wordpress/components';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
|
||||
/**
|
||||
* This component is essentially a wrapper (really a reimplementation) around the
|
||||
* TextControl component that adds support for affixes, i.e. the ability to display
|
||||
* a fixed part either at the beginning or at the end of the text input.
|
||||
*/
|
||||
class TextControlWithAffixes extends Component {
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
value,
|
||||
help,
|
||||
className,
|
||||
instanceId,
|
||||
onChange,
|
||||
prefix,
|
||||
suffix,
|
||||
type,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
const id = `inspector-text-control-with-affixes-${ instanceId }`;
|
||||
const onChangeValue = ( event ) => onChange( event.target.value );
|
||||
const describedby = [];
|
||||
if ( help ) {
|
||||
describedby.push( `${ id }__help` );
|
||||
}
|
||||
if ( prefix ) {
|
||||
describedby.push( `${ id }__prefix` );
|
||||
}
|
||||
if ( suffix ) {
|
||||
describedby.push( `${ id }__suffix` );
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseControl label={ label } id={ id } help={ help } className={ className }>
|
||||
<div className="text-control-with-affixes">
|
||||
{ prefix && (
|
||||
<span
|
||||
id={ `${ id }__prefix` }
|
||||
className="text-control-with-affixes__prefix"
|
||||
>
|
||||
{ prefix }
|
||||
</span>
|
||||
) }
|
||||
|
||||
<input
|
||||
className="components-text-control__input"
|
||||
type={ type }
|
||||
id={ id }
|
||||
value={ value }
|
||||
onChange={ onChangeValue }
|
||||
aria-describedby={ describedby.join( ' ' ) }
|
||||
{ ...props }
|
||||
/>
|
||||
|
||||
{ suffix && (
|
||||
<span
|
||||
id={ `${ id }__suffix` }
|
||||
className="text-control-with-affixes__suffix"
|
||||
>
|
||||
{ suffix }
|
||||
</span>
|
||||
) }
|
||||
</div>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextControlWithAffixes.defaultProps = {
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
TextControlWithAffixes.propTypes = {
|
||||
/**
|
||||
* If this property is added, a label will be generated using label property as the content.
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
/**
|
||||
* If this property is added, a help text will be generated using help property as the content.
|
||||
*/
|
||||
help: PropTypes.string,
|
||||
/**
|
||||
* Type of the input element to render. Defaults to "text".
|
||||
*/
|
||||
type: PropTypes.string,
|
||||
/**
|
||||
* The current value of the input.
|
||||
*/
|
||||
value: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The class that will be added with "components-base-control" to the classes of the wrapper div.
|
||||
* If no className is passed only components-base-control is used.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* A function that receives the value of the input.
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Markup to be inserted at the beginning of the input.
|
||||
*/
|
||||
prefix: PropTypes.node,
|
||||
/**
|
||||
* Markup to be appended at the end of the input.
|
||||
*/
|
||||
suffix: PropTypes.node,
|
||||
};
|
||||
|
||||
export default withInstanceId( TextControlWithAffixes );
|
|
@ -0,0 +1,54 @@
|
|||
.text-control-with-affixes {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
|
||||
input[type='email'],
|
||||
input[type='password'],
|
||||
input[type='url'],
|
||||
input[type='text'],
|
||||
input[type='number'] {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
|
||||
&:disabled {
|
||||
border-right-width: 0;
|
||||
|
||||
& + .text-control-with-affixes__suffix {
|
||||
border-left: 1px solid $core-grey-light-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-control-with-affixes__prefix,
|
||||
.text-control-with-affixes__suffix {
|
||||
position: relative;
|
||||
background: $white;
|
||||
border: 1px solid $core-grey-light-500;
|
||||
color: $gray-text;
|
||||
padding: 7px 14px;
|
||||
white-space: nowrap;
|
||||
flex: 1 0 auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.text-control-with-affixes__prefix {
|
||||
border-right: none;
|
||||
|
||||
& + input[type='email'],
|
||||
& + input[type='password'],
|
||||
& + input[type='url'],
|
||||
& + input[type='text'],
|
||||
& + input[type='number'] {
|
||||
&:disabled {
|
||||
border-left-color: $core-grey-light-500;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-control-with-affixes__suffix {
|
||||
border-left: none;
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
# 1.0.3
|
||||
|
||||
- Fix missing comma seperator in date inside tooltips.
|
||||
|
||||
# 1.0.2
|
||||
|
||||
- Add `getChartTypeForQuery` function to ensure chart type is always `bar` or `line`
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
"config:base"
|
||||
],
|
||||
"lockFileMaintenance": { "enabled": true },
|
||||
"ignoreDeps": ["phpunit/phpunit"]
|
||||
"ignoreDeps": ["phpunit/phpunit"],
|
||||
"schedule": ["before 3am on wednesday"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
/**
|
||||
* Reports Stock REST API Test
|
||||
*
|
||||
* @package WooCommerce\Tests\API
|
||||
* @since 3.5.0
|
||||
*/
|
||||
class WC_Tests_API_Reports_Stock extends WC_REST_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Endpoints.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = '/wc/v3/reports/stock';
|
||||
|
||||
/**
|
||||
* Setup test reports stock data.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test route registration.
|
||||
*/
|
||||
public function test_register_routes() {
|
||||
$routes = $this->server->get_routes();
|
||||
|
||||
$this->assertArrayHasKey( $this->endpoint, $routes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting reports.
|
||||
*/
|
||||
public function test_get_reports() {
|
||||
wp_set_current_user( $this->user );
|
||||
WC_Helper_Reports::reset_stats_dbs();
|
||||
|
||||
// Populate all of the data.
|
||||
$low_stock = new WC_Product_Simple();
|
||||
$low_stock->set_name( 'Test low stock' );
|
||||
$low_stock->set_regular_price( 5 );
|
||||
$low_stock->set_manage_stock( true );
|
||||
$low_stock->set_stock_quantity( 1 );
|
||||
$low_stock->set_stock_status( 'instock' );
|
||||
$low_stock->save();
|
||||
|
||||
$out_of_stock = new WC_Product_Simple();
|
||||
$out_of_stock->set_name( 'Test out of stock' );
|
||||
$out_of_stock->set_regular_price( 5 );
|
||||
$out_of_stock->set_stock_status( 'outofstock' );
|
||||
$out_of_stock->save();
|
||||
|
||||
$request = new WP_REST_Request( 'GET', $this->endpoint );
|
||||
$request->set_param( 'include', implode( ',', array( $low_stock->get_id(), $out_of_stock->get_id() ) ) );
|
||||
$request->set_param( 'orderby', 'id' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$reports = $response->get_data();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 2, count( $reports ) );
|
||||
|
||||
$this->assertEquals( $low_stock->get_id(), $reports[0]['id'] );
|
||||
$this->assertEquals( 'instock', $reports[0]['stock_status'] );
|
||||
$this->assertEquals( 1, $reports[0]['stock_quantity'] );
|
||||
$this->assertArrayHasKey( '_links', $reports[0] );
|
||||
$this->assertArrayHasKey( 'product', $reports[0]['_links'] );
|
||||
|
||||
$request = new WP_REST_Request( 'GET', $this->endpoint );
|
||||
$request->set_param( 'include', implode( ',', array( $low_stock->get_id(), $out_of_stock->get_id() ) ) );
|
||||
$request->set_param( 'type', 'lowstock' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$reports = $response->get_data();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 1, count( $reports ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting reports without valid permissions.
|
||||
*/
|
||||
public function test_get_reports_without_permission() {
|
||||
wp_set_current_user( 0 );
|
||||
$response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) );
|
||||
$this->assertEquals( 401, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reports schema.
|
||||
*/
|
||||
public function test_reports_schema() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'OPTIONS', $this->endpoint );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$properties = $data['schema']['properties'];
|
||||
|
||||
$this->assertEquals( 6, count( $properties ) );
|
||||
$this->assertArrayHasKey( 'id', $properties );
|
||||
$this->assertArrayHasKey( 'parent_id', $properties );
|
||||
$this->assertArrayHasKey( 'name', $properties );
|
||||
$this->assertArrayHasKey( 'sku', $properties );
|
||||
$this->assertArrayHasKey( 'stock_status', $properties );
|
||||
$this->assertArrayHasKey( 'stock_quantity', $properties );
|
||||
}
|
||||
}
|
|
@ -114,7 +114,7 @@ class WC_Tests_API_Reports_Taxes_Stats extends WC_REST_Unit_Test_Case {
|
|||
$this->assertEquals( 16, $tax_report['total_tax'] );
|
||||
$this->assertEquals( 13, $tax_report['order_tax'] );
|
||||
$this->assertEquals( 3, $tax_report['shipping_tax'] );
|
||||
$this->assertEquals( 2, $tax_report['orders'] );
|
||||
$this->assertEquals( 2, $tax_report['orders_count'] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +145,7 @@ class WC_Tests_API_Reports_Taxes_Stats extends WC_REST_Unit_Test_Case {
|
|||
|
||||
$this->assertEquals( 5, count( $totals ) );
|
||||
$this->assertArrayHasKey( 'order_tax', $totals );
|
||||
$this->assertArrayHasKey( 'order_count', $totals );
|
||||
$this->assertArrayHasKey( 'orders_count', $totals );
|
||||
$this->assertArrayHasKey( 'shipping_tax', $totals );
|
||||
$this->assertArrayHasKey( 'tax_codes', $totals );
|
||||
$this->assertArrayHasKey( 'total_tax', $totals );
|
||||
|
@ -162,7 +162,7 @@ class WC_Tests_API_Reports_Taxes_Stats extends WC_REST_Unit_Test_Case {
|
|||
$subtotals = $properties['intervals']['items']['properties']['subtotals']['properties'];
|
||||
$this->assertEquals( 5, count( $subtotals ) );
|
||||
$this->assertArrayHasKey( 'order_tax', $totals );
|
||||
$this->assertArrayHasKey( 'order_count', $totals );
|
||||
$this->assertArrayHasKey( 'orders_count', $totals );
|
||||
$this->assertArrayHasKey( 'shipping_tax', $totals );
|
||||
$this->assertArrayHasKey( 'tax_codes', $totals );
|
||||
$this->assertArrayHasKey( 'total_tax', $totals );
|
||||
|
|
|
@ -49,6 +49,19 @@ function wc_admin_plugins_notice() {
|
|||
printf( '<div class="error"><p>%s</p></div>', $message ); /* WPCS: xss ok. */
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify users that the plugin needs to be built
|
||||
*/
|
||||
function wc_admin_build_notice() {
|
||||
$message_one = __( 'You have installed a development version of WooCommerce Admin which requires files to be built. From the plugin directory, run <code>npm install</code> to install dependencies, <code>npm run build</code> to build the files.', 'wc-admin' );
|
||||
$message_two = sprintf(
|
||||
/* translators: 1: URL of GitHub Repository build page */
|
||||
__( 'Or you can download a pre-built version of the plugin by visiting <a href="%1$s">the releases page in the repository</a>.', 'wc-admin' ),
|
||||
'https://github.com/woocommerce/wc-admin/releases'
|
||||
);
|
||||
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); /* WPCS: xss ok. */
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all dependencies for the wc-admin plugin are loaded.
|
||||
*
|
||||
|
@ -67,6 +80,15 @@ function dependencies_satisfied() {
|
|||
return $wordpress_includes_gutenberg || $gutenberg_plugin_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if build file exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function wc_admin_build_file_exists() {
|
||||
return file_exists( plugin_dir_path( __FILE__ ) . '/dist/app/index.js' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Daily events to run.
|
||||
*/
|
||||
|
@ -123,7 +145,7 @@ function wc_admin_init() {
|
|||
}
|
||||
|
||||
// Only create/update tables on init if WP_DEBUG is true.
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && wc_admin_build_file_exists() ) {
|
||||
WC_Admin_Api_Init::create_db_tables();
|
||||
}
|
||||
}
|
||||
|
@ -144,16 +166,22 @@ function wc_admin_plugins_loaded() {
|
|||
// Some common utilities.
|
||||
require_once dirname( __FILE__ ) . '/lib/common.php';
|
||||
|
||||
// Admin note providers.
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-new-sales-record.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-settings-notes.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-woo-subscriptions-notes.php';
|
||||
|
||||
// Verify we have a proper build.
|
||||
if ( ! wc_admin_build_file_exists() ) {
|
||||
add_action( 'admin_notices', 'wc_admin_build_notice' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Register script files.
|
||||
require_once dirname( __FILE__ ) . '/lib/client-assets.php';
|
||||
|
||||
// Create the Admin pages.
|
||||
require_once dirname( __FILE__ ) . '/lib/admin.php';
|
||||
|
||||
// Admin note providers.
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-new-sales-record.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-settings-notes.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/class-wc-admin-notes-woo-subscriptions-notes.php';
|
||||
}
|
||||
add_action( 'plugins_loaded', 'wc_admin_plugins_loaded' );
|
||||
|
||||
|
|
Loading…
Reference in New Issue