Merge branch 'master' into fix/1012-2
This commit is contained in:
commit
c2bcad2e50
|
@ -168,8 +168,12 @@ install_deps() {
|
|||
php wp-cli.phar core config --dbname=$DB_NAME --dbuser=$DB_USER --dbpass=$DB_PASS --dbhost=$DB_HOST --dbprefix=wptests_
|
||||
php wp-cli.phar core install --url="$WP_SITE_URL" --title="Example" --admin_user=admin --admin_password=password --admin_email=info@example.com --path=$WP_CORE_DIR --skip-email
|
||||
|
||||
# Install Gutenberg
|
||||
php wp-cli.phar plugin install gutenberg --activate
|
||||
# Install Gutenberg if WP < 5
|
||||
if [[ $WP_VERSION =~ ^([0-9]+)[0-9\.]+\-? ]]; then
|
||||
if [ "5" -gt "${BASH_REMATCH[1]}" ]; then
|
||||
php wp-cli.phar plugin install gutenberg --activate
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install WooCommerce
|
||||
cd "wp-content/plugins/"
|
||||
|
|
|
@ -17,7 +17,7 @@ import { Card, EmptyTable, TableCard } from '@woocommerce/components';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
import { getReportTableData } from 'store/reports/utils';
|
||||
import { getReportTableData } from 'wc-api/reports/utils';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import './style.scss';
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ import { createRegistry, RegistryProvider } from '@wordpress/data';
|
|||
* WooCommerce dependencies
|
||||
*/
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import LeaderboardWithSelect, { Leaderboard } from '../';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import { NAMESPACE } from 'wc-api/constants';
|
||||
import mockData from '../__mocks__/top-selling-products-mock-data';
|
||||
|
||||
// Mock <Table> to avoid tests failing due to it using DOM properties that
|
||||
|
|
|
@ -25,7 +25,7 @@ import { Chart } from '@woocommerce/components';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getReportChartData, getTooltipValueFormat } from 'store/reports/utils';
|
||||
import { getReportChartData, getTooltipValueFormat } from 'wc-api/reports/utils';
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
|
|
|
@ -13,13 +13,14 @@ import PropTypes from 'prop-types';
|
|||
import { getDateParamsFromQuery } from '@woocommerce/date';
|
||||
import { getNewPath } from '@woocommerce/navigation';
|
||||
import { SummaryList, SummaryListPlaceholder, SummaryNumber } from '@woocommerce/components';
|
||||
import { calculateDelta, formatValue } from '@woocommerce/number';
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getSummaryNumbers } from 'store/reports/utils';
|
||||
import { getSummaryNumbers } from 'wc-api/reports/utils';
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
import { calculateDelta, formatValue } from 'lib/number';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
/**
|
||||
|
@ -45,11 +46,16 @@ export class ReportSummary extends Component {
|
|||
const renderSummaryNumbers = ( { onToggle } ) =>
|
||||
charts.map( chart => {
|
||||
const { key, label, type } = chart;
|
||||
const isCurrency = 'currency' === type;
|
||||
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
||||
const href = getNewPath( { chart: key } );
|
||||
const prevValue = formatValue( type, secondaryTotals[ key ] );
|
||||
const prevValue = isCurrency
|
||||
? formatCurrency( secondaryTotals[ key ] )
|
||||
: formatValue( type, secondaryTotals[ key ] );
|
||||
const isSelected = selectedChart.key === key;
|
||||
const value = formatValue( type, primaryTotals[ key ] );
|
||||
const value = isCurrency
|
||||
? formatCurrency( primaryTotals[ key ] )
|
||||
: formatValue( type, primaryTotals[ key ] );
|
||||
|
||||
return (
|
||||
<SummaryNumber
|
||||
|
|
|
@ -19,7 +19,7 @@ import { onQueryChange } from '@woocommerce/navigation';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
import { getReportChartData, getReportTableData } from 'store/reports/utils';
|
||||
import { getReportChartData, getReportTableData } from 'wc-api/reports/utils';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import { extendTableData } from './utils';
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export default class CategoryBreadcrumbs extends Component {
|
|||
let parent = category.parent;
|
||||
while ( parent ) {
|
||||
ancestors.unshift( parent );
|
||||
parent = categories[ parent ].parent;
|
||||
parent = categories.get( parent ).parent;
|
||||
}
|
||||
return ancestors;
|
||||
}
|
||||
|
@ -30,20 +30,20 @@ export default class CategoryBreadcrumbs extends Component {
|
|||
return;
|
||||
}
|
||||
if ( ancestorIds.length === 1 ) {
|
||||
return categories[ first( ancestorIds ) ].name + ' › ';
|
||||
return categories.get( first( ancestorIds ) ).name + ' › ';
|
||||
}
|
||||
if ( ancestorIds.length === 2 ) {
|
||||
return (
|
||||
categories[ first( ancestorIds ) ].name +
|
||||
categories.get( first( ancestorIds ) ).name +
|
||||
' › ' +
|
||||
categories[ last( ancestorIds ) ].name +
|
||||
categories.get( last( ancestorIds ) ).name +
|
||||
' › '
|
||||
);
|
||||
}
|
||||
return (
|
||||
categories[ first( ancestorIds ) ].name +
|
||||
categories.get( first( ancestorIds ) ).name +
|
||||
' … ' +
|
||||
categories[ last( ancestorIds ) ].name +
|
||||
categories.get( last( ancestorIds ) ).name +
|
||||
' › '
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ import { map } from 'lodash';
|
|||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CategoryBreacrumbs from './breadcrumbs';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
|
@ -68,11 +68,10 @@ class CategoriesReportTable extends Component {
|
|||
}
|
||||
|
||||
getRowsContent( categoryStats ) {
|
||||
const { query } = this.props;
|
||||
return map( categoryStats, categoryStat => {
|
||||
const { category_id, items_sold, net_revenue, products_count, orders_count } = categoryStat;
|
||||
const { categories, query } = this.props;
|
||||
const category = categories[ category_id ];
|
||||
const category = categories.get( category_id );
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
return [
|
||||
|
@ -154,6 +153,7 @@ class CategoriesReportTable extends Component {
|
|||
getSummary={ this.getSummary }
|
||||
itemIdField="category_id"
|
||||
query={ query }
|
||||
searchBy="categories"
|
||||
labels={ labels }
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'items_sold',
|
||||
|
@ -169,14 +169,14 @@ class CategoriesReportTable extends Component {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||
const tableQuery = {
|
||||
per_page: -1,
|
||||
};
|
||||
|
||||
const categories = getCategories( tableQuery );
|
||||
const isError = Boolean( getCategoriesError( tableQuery ) );
|
||||
const isRequesting = isGetCategoriesRequesting( tableQuery );
|
||||
const categories = getItems( 'categories', tableQuery );
|
||||
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
|
||||
|
||||
return { categories, isError, isRequesting };
|
||||
} )
|
||||
|
|
|
@ -12,12 +12,13 @@ import { map } from 'lodash';
|
|||
import { Date, Link } from '@woocommerce/components';
|
||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class CouponsReportTable extends Component {
|
||||
constructor() {
|
||||
|
@ -67,22 +68,29 @@ export default class CouponsReportTable extends Component {
|
|||
}
|
||||
|
||||
getRowsContent( coupons ) {
|
||||
const { query } = this.props;
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
return map( coupons, coupon => {
|
||||
const { amount, coupon_id, extended_info, orders_count } = coupon;
|
||||
const { code, date_created, date_expires, discount_type } = extended_info;
|
||||
|
||||
// @TODO must link to the coupon detail report
|
||||
const couponUrl = getNewPath( persistedQuery, '/analytics/coupons', {
|
||||
filter: 'single_coupon',
|
||||
coupons: coupon_id,
|
||||
} );
|
||||
const couponLink = (
|
||||
<Link href="" type="wc-admin">
|
||||
<Link href={ couponUrl } type="wc-admin">
|
||||
{ code }
|
||||
</Link>
|
||||
);
|
||||
|
||||
const ordersUrl = getNewPath( persistedQuery, '/analytics/orders', {
|
||||
filter: 'advanced',
|
||||
coupon_includes: coupon_id,
|
||||
} );
|
||||
const ordersLink = (
|
||||
<Link
|
||||
href={ '/analytics/orders?filter=advanced&code_includes=' + coupon_id }
|
||||
type="wc-admin"
|
||||
>
|
||||
<Link href={ ordersUrl } type="wc-admin">
|
||||
{ numberFormat( orders_count ) }
|
||||
</Link>
|
||||
);
|
||||
|
@ -161,9 +169,10 @@ export default class CouponsReportTable extends Component {
|
|||
getSummary={ this.getSummary }
|
||||
itemIdField="coupon_id"
|
||||
query={ query }
|
||||
searchBy="coupons"
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'coupon_id',
|
||||
order: query.order || 'asc',
|
||||
orderby: query.orderby || 'orders_count',
|
||||
order: query.order || 'desc',
|
||||
extended_info: true,
|
||||
} }
|
||||
title={ __( 'Coupons', 'wc-admin' ) }
|
||||
|
|
|
@ -9,7 +9,7 @@ import { decodeEntities } from '@wordpress/html-entities';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { getCustomerLabels, getRequestByIdString } from 'lib/async-requests';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import { NAMESPACE } from 'wc-api/constants';
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
|
@ -163,7 +163,7 @@ export const advancedFilters = {
|
|||
} ) ),
|
||||
},
|
||||
},
|
||||
order_count: {
|
||||
orders_count: {
|
||||
labels: {
|
||||
add: __( 'No. of Orders', 'wc-admin' ),
|
||||
remove: __( 'Remove order filter', 'wc-admin' ),
|
||||
|
|
|
@ -20,7 +20,7 @@ export default class CustomersReport extends Component {
|
|||
render() {
|
||||
const { query, path } = this.props;
|
||||
const tableQuery = {
|
||||
orderby: 'date_registered',
|
||||
orderby: 'date_last_active',
|
||||
order: 'desc',
|
||||
...query,
|
||||
};
|
||||
|
|
|
@ -12,12 +12,12 @@ import { Tooltip } from '@wordpress/components';
|
|||
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { Date, Link } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class CustomersReportTable extends Component {
|
||||
constructor() {
|
||||
|
@ -25,6 +25,7 @@ export default class CustomersReportTable extends Component {
|
|||
|
||||
this.getHeadersContent = this.getHeadersContent.bind( this );
|
||||
this.getRowsContent = this.getRowsContent.bind( this );
|
||||
this.getSummary = this.getSummary.bind( this );
|
||||
}
|
||||
|
||||
getHeadersContent() {
|
||||
|
@ -41,10 +42,15 @@ export default class CustomersReportTable extends Component {
|
|||
key: 'username',
|
||||
hiddenByDefault: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Last Active', 'wc-admin' ),
|
||||
key: 'date_last_active',
|
||||
defaultSort: true,
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Sign Up', 'wc-admin' ),
|
||||
key: 'date_registered',
|
||||
defaultSort: true,
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
|
@ -58,7 +64,7 @@ export default class CustomersReportTable extends Component {
|
|||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Lifetime Spend', 'wc-admin' ),
|
||||
label: __( 'Total Spend', 'wc-admin' ),
|
||||
key: 'total_spend',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
|
@ -69,11 +75,6 @@ export default class CustomersReportTable extends Component {
|
|||
key: 'avg_order_value',
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Last Active', 'wc-admin' ),
|
||||
key: 'date_last_active',
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Country', 'wc-admin' ),
|
||||
key: 'country',
|
||||
|
@ -147,6 +148,12 @@ export default class CustomersReportTable extends Component {
|
|||
display: username,
|
||||
value: username,
|
||||
},
|
||||
{
|
||||
display: date_last_active && (
|
||||
<Date date={ date_last_active } visibleFormat={ defaultTableDateFormat } />
|
||||
),
|
||||
value: date_last_active,
|
||||
},
|
||||
{
|
||||
display: dateRegistered,
|
||||
value: date_registered,
|
||||
|
@ -167,10 +174,6 @@ export default class CustomersReportTable extends Component {
|
|||
display: formatCurrency( avg_order_value ),
|
||||
value: getCurrencyFormatDecimal( avg_order_value ),
|
||||
},
|
||||
{
|
||||
display: <Date date={ date_last_active } visibleFormat={ defaultTableDateFormat } />,
|
||||
value: date_last_active,
|
||||
},
|
||||
{
|
||||
display: countryDisplay,
|
||||
value: country,
|
||||
|
@ -187,6 +190,30 @@ export default class CustomersReportTable extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
getSummary( totals ) {
|
||||
if ( ! totals ) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: __( 'customers', 'wc-admin' ),
|
||||
value: numberFormat( totals.customers_count ),
|
||||
},
|
||||
{
|
||||
label: __( 'average orders', 'wc-admin' ),
|
||||
value: numberFormat( totals.avg_orders_count ),
|
||||
},
|
||||
{
|
||||
label: __( 'average lifetime spend', 'wc-admin' ),
|
||||
value: formatCurrency( totals.avg_total_spend ),
|
||||
},
|
||||
{
|
||||
label: __( 'average order value', 'wc-admin' ),
|
||||
value: formatCurrency( totals.avg_avg_order_value ),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query } = this.props;
|
||||
|
||||
|
@ -195,11 +222,11 @@ export default class CustomersReportTable extends Component {
|
|||
endpoint="customers"
|
||||
getHeadersContent={ this.getHeadersContent }
|
||||
getRowsContent={ this.getRowsContent }
|
||||
getSummary={ this.getSummary }
|
||||
itemIdField="id"
|
||||
query={ query }
|
||||
labels={ { placeholder: __( 'Search by customer name', 'wc-admin' ) } }
|
||||
searchBy="customers"
|
||||
searchParam="name_includes"
|
||||
title={ __( 'Customers', 'wc-admin' ) }
|
||||
columnPrefsKey="customers_report_columns"
|
||||
/>
|
||||
|
|
|
@ -13,12 +13,12 @@ import moment from 'moment';
|
|||
import { defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
||||
import { Date, Link } from '@woocommerce/components';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class CouponsReportTable extends Component {
|
||||
constructor() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import PropTypes from 'prop-types';
|
||||
import { find } from 'lodash';
|
||||
|
||||
|
@ -12,6 +13,7 @@ import { find } from 'lodash';
|
|||
* WooCommerce dependencies
|
||||
*/
|
||||
import { useFilters } from '@woocommerce/components';
|
||||
import { getQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -27,6 +29,8 @@ import TaxesReport from './taxes';
|
|||
import DownloadsReport from './downloads';
|
||||
import StockReport from './stock';
|
||||
import CustomersReport from './customers';
|
||||
import { searchItemsByString } from 'wc-api/items/utils';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
const REPORTS_FILTER = 'woocommerce-reports-list';
|
||||
|
||||
|
@ -131,4 +135,27 @@ Report.propTypes = {
|
|||
params: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default useFilters( REPORTS_FILTER )( Report );
|
||||
export default compose(
|
||||
useFilters( REPORTS_FILTER ),
|
||||
withSelect( ( select, props ) => {
|
||||
const { search } = getQuery();
|
||||
|
||||
if ( ! search ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { report } = props.params;
|
||||
const items = searchItemsByString( select, report, search );
|
||||
const ids = Object.keys( items );
|
||||
if ( ! ids.length ) {
|
||||
return {}; // @TODO if no results were found, we should avoid making a server request.
|
||||
}
|
||||
|
||||
return {
|
||||
query: {
|
||||
...props.query,
|
||||
[ report ]: ids.join( ',' ),
|
||||
},
|
||||
};
|
||||
} )
|
||||
)( Report );
|
||||
|
|
|
@ -12,11 +12,11 @@ import { map } from 'lodash';
|
|||
import { Date, Link, OrderStatus, ViewMoreList } from '@woocommerce/components';
|
||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { numberFormat } from 'lib/number';
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import './style.scss';
|
||||
|
|
|
@ -24,7 +24,7 @@ import VariationsReportTable from './table-variations';
|
|||
export default class ProductsReport extends Component {
|
||||
render() {
|
||||
const { path, query } = this.props;
|
||||
const isProductDetailsView = query.products && 1 === query.products.split( ',' ).length;
|
||||
const isProductDetailsView = query.filter === 'single_product';
|
||||
|
||||
const itemsLabel = isProductDetailsView
|
||||
? __( '%s variations', 'wc-admin' )
|
||||
|
|
|
@ -12,12 +12,12 @@ import { map, get } from 'lodash';
|
|||
import { Link } from '@woocommerce/components';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import { isLowStock } from './utils';
|
||||
|
||||
export default class VariationsReportTable extends Component {
|
||||
|
@ -36,6 +36,12 @@ export default class VariationsReportTable extends Component {
|
|||
required: true,
|
||||
isLeftAligned: true,
|
||||
},
|
||||
{
|
||||
label: __( 'SKU', 'wc-admin' ),
|
||||
key: 'sku',
|
||||
hiddenByDefault: true,
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
key: 'items_sold',
|
||||
|
@ -77,7 +83,7 @@ export default class VariationsReportTable extends Component {
|
|||
|
||||
return map( data, row => {
|
||||
const { items_sold, net_revenue, orders_count, extended_info, product_id } = row;
|
||||
const { stock_status, stock_quantity, low_stock_amount } = extended_info;
|
||||
const { stock_status, stock_quantity, low_stock_amount, sku } = extended_info;
|
||||
const name = get( row, [ 'extended_info', 'name' ], '' ).replace( ' - ', ' / ' );
|
||||
const ordersLink = getNewPath( persistedQuery, 'orders', {
|
||||
filter: 'advanced',
|
||||
|
@ -94,6 +100,10 @@ export default class VariationsReportTable extends Component {
|
|||
),
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
display: sku,
|
||||
value: sku,
|
||||
},
|
||||
{
|
||||
display: items_sold,
|
||||
value: items_sold,
|
||||
|
@ -172,6 +182,7 @@ export default class VariationsReportTable extends Component {
|
|||
labels={ labels }
|
||||
query={ query }
|
||||
getSummary={ this.getSummary }
|
||||
searchBy="variations"
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'items_sold',
|
||||
order: query.order || 'desc',
|
||||
|
|
|
@ -13,13 +13,13 @@ import { map } from 'lodash';
|
|||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link, Tag } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CategoryBreacrumbs from '../categories/breadcrumbs';
|
||||
import { isLowStock } from './utils';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import './style.scss';
|
||||
|
@ -119,11 +119,11 @@ class ProductsReportTable extends Component {
|
|||
filter: 'single_product',
|
||||
products: product_id,
|
||||
} );
|
||||
const categories = this.props.categories;
|
||||
const { categories } = this.props;
|
||||
|
||||
const productCategories =
|
||||
( category_ids &&
|
||||
category_ids.map( category_id => categories[ category_id ] ).filter( Boolean ) ) ||
|
||||
category_ids.map( category_id => categories.get( category_id ) ).filter( Boolean ) ) ||
|
||||
[];
|
||||
|
||||
return [
|
||||
|
@ -245,6 +245,7 @@ class ProductsReportTable extends Component {
|
|||
itemIdField="product_id"
|
||||
labels={ labels }
|
||||
query={ query }
|
||||
searchBy="products"
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'items_sold',
|
||||
order: query.order || 'desc',
|
||||
|
@ -259,14 +260,14 @@ class ProductsReportTable extends Component {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||
const tableQuery = {
|
||||
per_page: -1,
|
||||
};
|
||||
|
||||
const categories = getCategories( tableQuery );
|
||||
const isError = Boolean( getCategoriesError( tableQuery ) );
|
||||
const isRequesting = isGetCategoriesRequesting( tableQuery );
|
||||
const categories = getItems( 'categories', tableQuery );
|
||||
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
|
||||
|
||||
return { categories, isError, isRequesting };
|
||||
} )
|
||||
|
|
|
@ -14,12 +14,12 @@ import { get } from 'lodash';
|
|||
import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
||||
import { Date, Link } from '@woocommerce/components';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { QUERY_DEFAULTS } from 'store/constants';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@ import { Component } from '@wordpress/element';
|
|||
*/
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class StockReportTable extends Component {
|
||||
constructor() {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n';
|
|||
*/
|
||||
import { getRequestByIdString } from 'lib/async-requests';
|
||||
import { getTaxCode } from './utils';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import { NAMESPACE } from 'wc-api/constants';
|
||||
|
||||
export const charts = [
|
||||
{
|
||||
|
|
|
@ -12,12 +12,12 @@ import { map } from 'lodash';
|
|||
import { Link } from '@woocommerce/components';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getTaxCode } from './utils';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ReportTable from 'analytics/components/report-table';
|
||||
import { numberFormat } from 'lib/number';
|
||||
|
||||
export default class TaxesReportTable extends Component {
|
||||
constructor() {
|
||||
|
@ -150,6 +150,7 @@ export default class TaxesReportTable extends Component {
|
|||
getSummary={ this.getSummary }
|
||||
itemIdField="tax_rate_id"
|
||||
query={ query }
|
||||
searchBy="taxes"
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'tax_rate_id',
|
||||
} }
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
Settings
|
||||
=======
|
||||
|
||||
The settings used to modify the way data is retreived or displayed in WooCommerce reports.
|
||||
|
||||
## Extending Settings
|
||||
|
||||
Settings can be added, removed, or modified outside oc `wc-admin` by hooking into `woocommerce_admin_analytics_settings`. For example:
|
||||
|
||||
```js
|
||||
addFilter( 'woocommerce_admin_analytics_settings', 'wc-example/my-setting', settings => {
|
||||
return [
|
||||
...settings,
|
||||
{
|
||||
name: 'custom_setting',
|
||||
label: __( 'Custom setting:', 'wc-admin' ),
|
||||
inputType: 'text',
|
||||
helpText: __( 'Help text to describe what the setting does.' ),
|
||||
initialValue: 'Initial value used',
|
||||
defaultValue: 'Default value',
|
||||
},
|
||||
];
|
||||
} );
|
||||
```
|
||||
|
||||
Each settings has the following properties:
|
||||
|
||||
- `name` (string): The slug of the setting to be updated.
|
||||
- `label` (string): The label used to describe and displayed next to the setting.
|
||||
- `inputType` (enum: text|checkbox|checkboxGroup): The type of input to use.
|
||||
- `helpText` (string): Text displayed beneath the setting.
|
||||
- `options` (array): Array of options used for inputs with selectable options.
|
||||
- `initialValue` (string|array): Initial value used when rendering the setting.
|
||||
- `defaultValue` (string|array): Value used when resetting to default settings.
|
|
@ -0,0 +1,68 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import interpolateComponents from 'interpolate-components';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
const SETTINGS_FILTER = 'woocommerce_admin_analytics_settings';
|
||||
|
||||
const defaultOrderStatuses = [
|
||||
'completed',
|
||||
'processing',
|
||||
'refunded',
|
||||
'cancelled',
|
||||
'failed',
|
||||
'pending',
|
||||
'on-hold',
|
||||
];
|
||||
const orderStatuses = Object.keys( wcSettings.orderStatuses )
|
||||
.filter( status => status !== 'refunded' )
|
||||
.map( key => {
|
||||
return {
|
||||
value: key,
|
||||
label: wcSettings.orderStatuses[ key ],
|
||||
description: sprintf(
|
||||
__( 'Exclude the %s status from reports', 'wc-admin' ),
|
||||
wcSettings.orderStatuses[ key ]
|
||||
),
|
||||
};
|
||||
} );
|
||||
|
||||
export const analyticsSettings = applyFilters( SETTINGS_FILTER, [
|
||||
{
|
||||
name: 'woocommerce_excluded_report_order_statuses',
|
||||
label: __( 'Excluded Statuses:', 'wc-admin' ),
|
||||
inputType: 'checkboxGroup',
|
||||
options: [
|
||||
{
|
||||
key: 'defaultStatuses',
|
||||
options: orderStatuses.filter( status => defaultOrderStatuses.includes( status.value ) ),
|
||||
},
|
||||
{
|
||||
key: 'customStatuses',
|
||||
label: __( 'Custom Statuses', 'wc-admin' ),
|
||||
options: orderStatuses.filter( status => ! defaultOrderStatuses.includes( status.value ) ),
|
||||
},
|
||||
],
|
||||
helpText: interpolateComponents( {
|
||||
mixedString: __(
|
||||
'Orders with these statuses are excluded from the totals in your reports. ' +
|
||||
'The {{strong}}Refunded{{/strong}} status can not be excluded. {{moreLink}}Learn more{{/moreLink}}',
|
||||
'wc-admin'
|
||||
),
|
||||
components: {
|
||||
strong: <strong />,
|
||||
moreLink: <Link href="#" type="external" />, // @TODO: this needs to be replaced with a real link.
|
||||
},
|
||||
} ),
|
||||
initialValue: wcSettings.wcAdminSettings.woocommerce_excluded_report_order_statuses || [],
|
||||
defaultValue: [ 'pending', 'cancelled', 'failed' ],
|
||||
},
|
||||
] );
|
|
@ -0,0 +1,134 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { remove } from 'lodash';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { SectionHeader, useFilters } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './index.scss';
|
||||
import { analyticsSettings } from './config';
|
||||
import Header from 'header';
|
||||
import Setting from './setting';
|
||||
|
||||
const SETTINGS_FILTER = 'woocommerce_admin_analytics_settings';
|
||||
|
||||
class Settings extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
const settings = {};
|
||||
analyticsSettings.forEach( setting => ( settings[ setting.name ] = setting.initialValue ) );
|
||||
|
||||
this.state = {
|
||||
settings: settings,
|
||||
};
|
||||
|
||||
this.handleInputChange = this.handleInputChange.bind( this );
|
||||
}
|
||||
|
||||
componentDidCatch( error ) {
|
||||
this.setState( {
|
||||
hasError: true,
|
||||
} );
|
||||
/* eslint-disable no-console */
|
||||
console.warn( error );
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
resetDefaults = () => {
|
||||
if (
|
||||
window.confirm(
|
||||
__( 'Are you sure you want to reset all settings to default values?', 'wc-admin' )
|
||||
)
|
||||
) {
|
||||
const settings = {};
|
||||
analyticsSettings.forEach( setting => ( settings[ setting.name ] = setting.defaultValue ) );
|
||||
this.setState( { settings }, this.saveChanges );
|
||||
}
|
||||
};
|
||||
|
||||
saveChanges = () => {
|
||||
this.props.updateSettings( this.state.settings );
|
||||
// @TODO: Need a confirmation on successful update.
|
||||
};
|
||||
|
||||
handleInputChange( e ) {
|
||||
const { checked, name, type, value } = e.target;
|
||||
const { settings } = this.state;
|
||||
|
||||
if ( 'checkbox' === type ) {
|
||||
if ( checked ) {
|
||||
settings[ name ].push( value );
|
||||
} else {
|
||||
remove( settings[ name ], v => v === value );
|
||||
}
|
||||
} else {
|
||||
settings[ name ] = value;
|
||||
}
|
||||
|
||||
this.setState( { settings } );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError } = this.state;
|
||||
if ( hasError ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header
|
||||
sections={ [
|
||||
[ '/analytics/revenue', __( 'Analytics', 'wc-admin' ) ],
|
||||
__( 'Settings', 'wc-admin' ),
|
||||
] }
|
||||
/>
|
||||
<SectionHeader title={ __( 'Analytics Settings', 'wc-admin' ) } />
|
||||
<div className="woocommerce-settings__wrapper">
|
||||
{ analyticsSettings.map( setting => (
|
||||
<Setting
|
||||
handleChange={ this.handleInputChange }
|
||||
helpText={ setting.helpText }
|
||||
inputType={ setting.inputType }
|
||||
key={ setting.name }
|
||||
label={ setting.label }
|
||||
name={ setting.name }
|
||||
options={ setting.options }
|
||||
value={ this.state.settings[ setting.name ] }
|
||||
/>
|
||||
) ) }
|
||||
<div className="woocommerce-settings__actions">
|
||||
<Button isDefault onClick={ this.resetDefaults }>
|
||||
{ __( 'Reset Defaults', 'wc-admin' ) }
|
||||
</Button>
|
||||
<Button isPrimary onClick={ this.saveChanges }>
|
||||
{ __( 'Save Changes', 'wc-admin' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDispatch( dispatch => {
|
||||
const { updateSettings } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
updateSettings,
|
||||
};
|
||||
} )
|
||||
)( useFilters( SETTINGS_FILTER )( Settings ) );
|
|
@ -0,0 +1,17 @@
|
|||
/** @format */
|
||||
|
||||
.woocommerce-settings__wrapper {
|
||||
@include breakpoint( '>782px' ) {
|
||||
padding: 0 ($gap - 3);
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-settings__actions {
|
||||
@include breakpoint( '>1280px' ) {
|
||||
margin-left: 15%;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: $gap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './setting.scss';
|
||||
|
||||
class Setting extends Component {
|
||||
renderInput = () => {
|
||||
const { handleChange, name, inputType, options, value } = this.props;
|
||||
const id = uniqueId( name );
|
||||
|
||||
switch ( inputType ) {
|
||||
case 'checkboxGroup':
|
||||
return options.map(
|
||||
optionGroup =>
|
||||
optionGroup.options.length > 0 && (
|
||||
<div
|
||||
className="woocommerce-setting__options-group"
|
||||
key={ optionGroup.key }
|
||||
aria-labelledby={ name + '-label' }
|
||||
>
|
||||
{ optionGroup.label && (
|
||||
<span className="woocommerce-setting__options-group-label">
|
||||
{ optionGroup.label }
|
||||
</span>
|
||||
) }
|
||||
{ this.renderCheckboxOptions( optionGroup.options ) }
|
||||
</div>
|
||||
)
|
||||
);
|
||||
case 'checkbox':
|
||||
return this.renderCheckboxOptions( options );
|
||||
case 'text':
|
||||
default:
|
||||
return (
|
||||
<input id={ id } type="text" name={ name } onChange={ handleChange } value={ value } />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderCheckboxOptions( options ) {
|
||||
const { handleChange, name, value } = this.props;
|
||||
|
||||
return options.map( option => {
|
||||
const id = uniqueId( name + '-' + option.value );
|
||||
return (
|
||||
<label htmlFor={ id } key={ option.value }>
|
||||
<input
|
||||
id={ id }
|
||||
type="checkbox"
|
||||
name={ name }
|
||||
onChange={ handleChange }
|
||||
aria-label={ option.description }
|
||||
checked={ value && value.includes( option.value ) }
|
||||
value={ option.value }
|
||||
/>
|
||||
{ option.label }
|
||||
</label>
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { helpText, label, name } = this.props;
|
||||
|
||||
return (
|
||||
<div className="woocommerce-setting">
|
||||
<div className="woocommerce-setting__label" id={ name + '-label' }>
|
||||
{ label }
|
||||
</div>
|
||||
<div className="woocommerce-setting__options">
|
||||
{ this.renderInput() }
|
||||
{ helpText && <span className="woocommerce-setting__help">{ helpText }</span> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Setting.propTypes = {
|
||||
/**
|
||||
* Function assigned to the onChange of all inputs.
|
||||
*/
|
||||
handleChange: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Optional help text displayed underneath the setting.
|
||||
*/
|
||||
helpText: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array ] ),
|
||||
/**
|
||||
* Type of input to use; defaults to a text input.
|
||||
*/
|
||||
inputType: PropTypes.oneOf( [ 'checkbox', 'checkboxGroup', 'text' ] ),
|
||||
/**
|
||||
* Label used for describing the setting.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Setting slug applied to input names.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Array of options used for when the `inputType` allows multiple selections.
|
||||
*/
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* Input value for this option.
|
||||
*/
|
||||
value: PropTypes.string,
|
||||
/**
|
||||
* Label for this option or above a group for a group `inputType`.
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
/**
|
||||
* Description used for screen readers.
|
||||
*/
|
||||
description: PropTypes.string,
|
||||
/**
|
||||
* Key used for a group `inputType`.
|
||||
*/
|
||||
key: PropTypes.string,
|
||||
/**
|
||||
* Nested options for a group `inputType`.
|
||||
*/
|
||||
options: PropTypes.array,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* The string value used for the input or array of items if the input allows multiselection.
|
||||
*/
|
||||
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array ] ),
|
||||
};
|
||||
|
||||
export default Setting;
|
|
@ -0,0 +1,49 @@
|
|||
/** @format */
|
||||
|
||||
.woocommerce-setting {
|
||||
display: flex;
|
||||
margin-bottom: $gap-large;
|
||||
@include breakpoint( '<1280px' ) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-setting__label {
|
||||
@include font-size(16);
|
||||
margin-bottom: $gap;
|
||||
padding-right: $gap;
|
||||
font-weight: bold;
|
||||
@include breakpoint( '>1280px' ) {
|
||||
width: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-setting__options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@include breakpoint( '>1280px' ) {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-bottom: $gap-small;
|
||||
color: $core-grey-dark-500;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
margin-right: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-setting__options-group-label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
|
||||
.woocommerce-setting__help {
|
||||
font-style: italic;
|
||||
color: $core-grey-dark-300;
|
||||
}
|
|
@ -21,6 +21,7 @@ import { EllipsisMenu, MenuItem, MenuTitle, SectionHeader } from '@woocommerce/c
|
|||
import withSelect from 'wc-api/with-select';
|
||||
import TopSellingCategories from './top-selling-categories';
|
||||
import TopSellingProducts from './top-selling-products';
|
||||
import TopCoupons from './top-coupons';
|
||||
import './style.scss';
|
||||
|
||||
class Leaderboards extends Component {
|
||||
|
@ -134,10 +135,12 @@ class Leaderboards extends Component {
|
|||
{ ! hiddenLeaderboardKeys.includes( 'top-products' ) && (
|
||||
<TopSellingProducts query={ query } totalRows={ rowsPerTable } />
|
||||
) }
|
||||
|
||||
{ ! hiddenLeaderboardKeys.includes( 'top-categories' ) && (
|
||||
<TopSellingCategories query={ query } totalRows={ rowsPerTable } />
|
||||
) }
|
||||
{ ! hiddenLeaderboardKeys.includes( 'top-coupons' ) && (
|
||||
<TopCoupons query={ query } totalRows={ rowsPerTable } />
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { map } from 'lodash';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Leaderboard from 'analytics/components/leaderboard';
|
||||
|
||||
export class TopCoupons extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.getRowsContent = this.getRowsContent.bind( this );
|
||||
this.getHeadersContent = this.getHeadersContent.bind( this );
|
||||
}
|
||||
|
||||
getHeadersContent() {
|
||||
return [
|
||||
{
|
||||
label: __( 'Coupon Code', 'wc-admin' ),
|
||||
key: 'code',
|
||||
required: true,
|
||||
isLeftAligned: true,
|
||||
isSortable: false,
|
||||
},
|
||||
{
|
||||
label: __( 'Orders', 'wc-admin' ),
|
||||
key: 'orders_count',
|
||||
required: true,
|
||||
defaultSort: true,
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Amount Discounted', 'wc-admin' ),
|
||||
key: 'amount',
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getRowsContent( data ) {
|
||||
const { query } = this.props;
|
||||
const persistedQuery = getPersistedQuery( query );
|
||||
return map( data, row => {
|
||||
const { amount, coupon_id, extended_info, orders_count } = row;
|
||||
const { code } = extended_info;
|
||||
|
||||
const couponUrl = getNewPath( persistedQuery, 'analytics/coupons', {
|
||||
filter: 'single_coupon',
|
||||
coupons: coupon_id,
|
||||
} );
|
||||
const couponLink = (
|
||||
<Link href={ couponUrl } type="wc-admin">
|
||||
{ code }
|
||||
</Link>
|
||||
);
|
||||
|
||||
const ordersUrl = getNewPath( persistedQuery, 'analytics/orders', {
|
||||
filter: 'advanced',
|
||||
coupon_includes: coupon_id,
|
||||
} );
|
||||
const ordersLink = (
|
||||
<Link href={ ordersUrl } type="wc-admin">
|
||||
{ numberFormat( orders_count ) }
|
||||
</Link>
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
display: couponLink,
|
||||
value: code,
|
||||
},
|
||||
{
|
||||
display: ordersLink,
|
||||
value: orders_count,
|
||||
},
|
||||
{
|
||||
display: formatCurrency( amount ),
|
||||
value: getCurrencyFormatDecimal( amount ),
|
||||
},
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, totalRows } = this.props;
|
||||
const tableQuery = {
|
||||
orderby: 'orders_count',
|
||||
order: 'desc',
|
||||
per_page: totalRows,
|
||||
extended_info: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<Leaderboard
|
||||
endpoint="coupons"
|
||||
getHeadersContent={ this.getHeadersContent }
|
||||
getRowsContent={ this.getRowsContent }
|
||||
query={ query }
|
||||
tableQuery={ tableQuery }
|
||||
title={ __( 'Top Coupons', 'wc-admin' ) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TopCoupons;
|
|
@ -12,11 +12,11 @@ import { get, map } from 'lodash';
|
|||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { numberFormat } from 'lib/number';
|
||||
import Leaderboard from 'analytics/components/leaderboard';
|
||||
|
||||
export class TopSellingCategories extends Component {
|
||||
|
|
|
@ -12,11 +12,11 @@ import { get, map } from 'lodash';
|
|||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { numberFormat } from '@woocommerce/number';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { numberFormat } from 'lib/number';
|
||||
import Leaderboard from 'analytics/components/leaderboard';
|
||||
|
||||
export class TopSellingProducts extends Component {
|
||||
|
|
|
@ -15,6 +15,8 @@ import { find } from 'lodash';
|
|||
*/
|
||||
import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@woocommerce/date';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { calculateDelta, formatValue } from '@woocommerce/number';
|
||||
import { formatCurrency } from '@woocommerce/currency';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -31,7 +33,6 @@ import {
|
|||
} from '@woocommerce/components';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import './style.scss';
|
||||
import { calculateDelta, formatValue } from 'lib/number';
|
||||
|
||||
class StorePerformance extends Component {
|
||||
constructor( props ) {
|
||||
|
@ -128,9 +129,15 @@ class StorePerformance extends Component {
|
|||
'';
|
||||
const reportUrl =
|
||||
( href && getNewPath( persistedQuery, href, { chart: primaryItem.chart } ) ) || '';
|
||||
const isCurrency = 'currency' === primaryItem.format;
|
||||
|
||||
const delta = calculateDelta( primaryItem.value, secondaryItem.value );
|
||||
const primaryValue = formatValue( primaryItem.format, primaryItem.value );
|
||||
const secondaryValue = formatValue( secondaryItem.format, secondaryItem.value );
|
||||
const primaryValue = isCurrency
|
||||
? formatCurrency( primaryItem.value )
|
||||
: formatValue( primaryItem.format, primaryItem.value );
|
||||
const secondaryValue = isCurrency
|
||||
? formatCurrency( secondaryItem.value )
|
||||
: formatValue( secondaryItem.format, secondaryItem.value );
|
||||
|
||||
return (
|
||||
<SummaryNumber
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
|||
*/
|
||||
import './stylesheets/_embedded.scss';
|
||||
import { EmbedLayout } from './layout';
|
||||
import 'store';
|
||||
import 'wc-api/wp-data-store';
|
||||
|
||||
render(
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
|||
import ActivityHeader from '../activity-header';
|
||||
import { EmptyContent, Section } from '@woocommerce/components';
|
||||
import sanitizeHTML from 'lib/sanitize-html';
|
||||
import { QUERY_DEFAULTS } from 'store/constants';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
|
||||
class InboxPanel extends Component {
|
||||
render() {
|
||||
|
|
|
@ -33,7 +33,7 @@ import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
|||
import ActivityHeader from '../activity-header';
|
||||
import ActivityOutboundLink from '../activity-outbound-link';
|
||||
import { getOrderRefundTotal } from 'lib/order-values';
|
||||
import { QUERY_DEFAULTS } from 'store/constants';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||
|
@ -85,13 +85,58 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
orderLink: <Link href={ 'post.php?action=edit&post=' + order.id } type="wp-admin" />,
|
||||
// @TODO: Hook up customer name link
|
||||
customerLink: <Link href={ '#' } type="wp-admin" />,
|
||||
destinationFlag: <Flag order={ order } round={ false } height={ 9 } width={ 12 } />,
|
||||
destinationFlag: <Flag order={ order } round={ false } />,
|
||||
},
|
||||
} ) }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const cards = [];
|
||||
orders.forEach( ( order, id ) => {
|
||||
// We want the billing address, but shipping can be used as a fallback.
|
||||
const address = { ...order.shipping, ...order.billing };
|
||||
const productsCount = order.line_items.reduce( ( total, line ) => total + line.quantity, 0 );
|
||||
|
||||
const total = order.total;
|
||||
const refundValue = getOrderRefundTotal( order );
|
||||
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
|
||||
|
||||
cards.push(
|
||||
<ActivityCard
|
||||
key={ id }
|
||||
className="woocommerce-order-activity-card"
|
||||
title={ orderCardTitle( order, address ) }
|
||||
date={ order.date_created }
|
||||
subtitle={
|
||||
<div>
|
||||
<span>
|
||||
{ sprintf(
|
||||
_n( '%d product', '%d products', productsCount, 'wc-admin' ),
|
||||
productsCount
|
||||
) }
|
||||
</span>
|
||||
{ refundValue ? (
|
||||
<span>
|
||||
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
|
||||
{ formatCurrency( remainingTotal, order.currency_symbol ) }
|
||||
</span>
|
||||
) : (
|
||||
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
|
||||
) }
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<Button isDefault href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }>
|
||||
{ __( 'Begin fulfillment' ) }
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<OrderStatus order={ order } />
|
||||
</ActivityCard>
|
||||
);
|
||||
} );
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ActivityHeader title={ __( 'Orders', 'wc-admin' ) } menu={ menu } />
|
||||
|
@ -105,55 +150,7 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
{ orders.map( ( order, i ) => {
|
||||
// We want the billing address, but shipping can be used as a fallback.
|
||||
const address = { ...order.shipping, ...order.billing };
|
||||
const productsCount = order.line_items.reduce(
|
||||
( total, line ) => total + line.quantity,
|
||||
0
|
||||
);
|
||||
|
||||
const total = order.total;
|
||||
const refundValue = getOrderRefundTotal( order );
|
||||
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
|
||||
|
||||
return (
|
||||
<ActivityCard
|
||||
key={ i }
|
||||
className="woocommerce-order-activity-card"
|
||||
title={ orderCardTitle( order, address ) }
|
||||
date={ order.date_created }
|
||||
subtitle={
|
||||
<div>
|
||||
<span>
|
||||
{ sprintf(
|
||||
_n( '%d product', '%d products', productsCount, 'wc-admin' ),
|
||||
productsCount
|
||||
) }
|
||||
</span>
|
||||
{ refundValue ? (
|
||||
<span>
|
||||
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
|
||||
{ formatCurrency( remainingTotal, order.currency_symbol ) }
|
||||
</span>
|
||||
) : (
|
||||
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
|
||||
) }
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<Button
|
||||
isDefault
|
||||
href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }
|
||||
>
|
||||
{ __( 'Begin fulfillment' ) }
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<OrderStatus order={ order } />
|
||||
</ActivityCard>
|
||||
);
|
||||
} ) }
|
||||
{ cards }
|
||||
<ActivityOutboundLink href={ 'edit.php?post_type=shop_order' }>
|
||||
{ __( 'Manage all orders' ) }
|
||||
</ActivityOutboundLink>
|
||||
|
@ -165,29 +162,29 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
}
|
||||
|
||||
OrdersPanel.propTypes = {
|
||||
orders: PropTypes.array.isRequired,
|
||||
orders: PropTypes.instanceOf( Map ).isRequired,
|
||||
isError: PropTypes.bool,
|
||||
isRequesting: PropTypes.bool,
|
||||
};
|
||||
|
||||
OrdersPanel.defaultProps = {
|
||||
orders: [],
|
||||
orders: new Map(),
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getOrders, getOrdersError, isGetOrdersRequesting } = select( 'wc-api' );
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||
const ordersQuery = {
|
||||
page: 1,
|
||||
per_page: QUERY_DEFAULTS.pageSize,
|
||||
status: 'processing',
|
||||
};
|
||||
|
||||
const orders = getOrders( ordersQuery );
|
||||
const isError = Boolean( getOrdersError( ordersQuery ) );
|
||||
const isRequesting = isGetOrdersRequesting( ordersQuery );
|
||||
const orders = getItems( 'orders', ordersQuery );
|
||||
const isError = Boolean( getItemsError( 'orders', ordersQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'orders', ordersQuery );
|
||||
|
||||
return { orders, isError, isRequesting };
|
||||
} )
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
*/
|
||||
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||
import ActivityHeader from '../activity-header';
|
||||
import { QUERY_DEFAULTS } from 'store/constants';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import sanitizeHTML from 'lib/sanitize-html';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
|||
*/
|
||||
import './stylesheets/_index.scss';
|
||||
import { PageLayout } from './layout';
|
||||
import 'store';
|
||||
import 'wc-api/wp-data-store';
|
||||
|
||||
render(
|
||||
|
|
|
@ -16,6 +16,7 @@ import { getPersistedQuery, stringifyQuery } from '@woocommerce/navigation';
|
|||
*/
|
||||
import Analytics from 'analytics';
|
||||
import AnalyticsReport from 'analytics/report';
|
||||
import AnalyticsSettings from 'analytics/settings';
|
||||
import Dashboard from 'dashboard';
|
||||
import DevDocs from 'devdocs';
|
||||
|
||||
|
@ -33,6 +34,12 @@ const getPages = () => {
|
|||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
},
|
||||
{
|
||||
container: AnalyticsSettings,
|
||||
path: '/analytics/settings',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
},
|
||||
{
|
||||
container: AnalyticsReport,
|
||||
path: '/analytics/:report',
|
||||
|
|
|
@ -13,7 +13,7 @@ import { getIdsFromQuery, stringifyQuery } from '@woocommerce/navigation';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import { NAMESPACE } from 'wc-api/constants';
|
||||
|
||||
/**
|
||||
* Get a function that accepts ids as they are found in url parameter and
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
export const NAMESPACE = '/wc/v4/';
|
||||
export const SWAGGERNAMESPACE = 'https://virtserver.swaggerhub.com/peterfabian/wc-v3-api/1.0.0/';
|
||||
export const ERROR = 'ERROR';
|
||||
|
||||
// WordPress & WooCommerce both set a hard limit of 100 for the per_page parameter
|
||||
export const MAX_PER_PAGE = 100;
|
||||
|
||||
export const QUERY_DEFAULTS = {
|
||||
pageSize: 25,
|
||||
period: 'month',
|
||||
compare: 'previous_year',
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { combineReducers, registerStore } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { applyMiddleware, addThunks } from './middleware';
|
||||
import orders from 'store/orders';
|
||||
import reports from 'store/reports';
|
||||
import notes from 'store/notes';
|
||||
|
||||
const store = registerStore( 'wc-admin', {
|
||||
reducer: combineReducers( {
|
||||
orders: orders.reducer,
|
||||
reports: reports.reducer,
|
||||
notes: notes.reducer,
|
||||
} ),
|
||||
|
||||
actions: {
|
||||
...orders.actions,
|
||||
...reports.actions,
|
||||
...notes.actions,
|
||||
},
|
||||
|
||||
selectors: {
|
||||
...orders.selectors,
|
||||
...reports.selectors,
|
||||
...notes.selectors,
|
||||
},
|
||||
|
||||
resolvers: {
|
||||
...orders.resolvers,
|
||||
...reports.resolvers,
|
||||
...notes.resolvers,
|
||||
},
|
||||
} );
|
||||
|
||||
applyMiddleware( store, [ addThunks ] );
|
|
@ -1,16 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export function applyMiddleware( store, middlewares ) {
|
||||
middlewares = middlewares.slice();
|
||||
middlewares.reverse();
|
||||
let dispatch = store.dispatch;
|
||||
middlewares.forEach( middleware => ( dispatch = middleware( store )( dispatch ) ) );
|
||||
return Object.assign( store, { dispatch } );
|
||||
}
|
||||
|
||||
export const addThunks = ( { getState } ) => next => action => {
|
||||
if ( 'function' === typeof action ) {
|
||||
return action( getState );
|
||||
}
|
||||
return next( action );
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export default {
|
||||
setNotes( notes, query ) {
|
||||
return {
|
||||
type: 'SET_NOTES',
|
||||
notes,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
setNotesError( query ) {
|
||||
return {
|
||||
type: 'SET_NOTES_ERROR',
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import actions from './actions';
|
||||
import reducer from './reducer';
|
||||
import resolvers from './resolvers';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
actions,
|
||||
reducer,
|
||||
resolvers,
|
||||
selectors,
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
export default function notesReducer( state = DEFAULT_STATE, action ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
|
||||
switch ( action.type ) {
|
||||
case 'SET_NOTES':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: action.notes,
|
||||
} );
|
||||
case 'SET_NOTES_ERROR':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: ERROR,
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export default {
|
||||
// TODO: Use controls data plugin or fresh-data instead of async
|
||||
async getNotes( ...args ) {
|
||||
// This is interim code to work with either 2.x or 3.x version of @wordpress/data
|
||||
// TODO: Change to just `getNotes( query )` after Gutenberg plugin uses @wordpress/data 3+
|
||||
const query = args.length === 1 ? args[ 0 ] : args[ 1 ];
|
||||
|
||||
try {
|
||||
const notes = await apiFetch( { path: NAMESPACE + 'admin/notes' + stringifyQuery( query ) } );
|
||||
dispatch( 'wc-admin' ).setNotes( notes, query );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setNotesError( query );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getJsonString } from 'store/utils';
|
||||
import { ERROR } from 'store/constants';
|
||||
|
||||
/**
|
||||
* Returns notes for a specific query.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Note query parameters
|
||||
* @return {Array} Notes
|
||||
*/
|
||||
function getNotes( state, query = {} ) {
|
||||
return get( state, [ 'notes', getJsonString( query ) ], [] );
|
||||
}
|
||||
|
||||
export default {
|
||||
getNotes,
|
||||
|
||||
/**
|
||||
* Returns true if a query is pending.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Boolean} True if the `getNotes` request is pending, false otherwise
|
||||
*/
|
||||
isGetNotesRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getNotes', args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a get notes request has returned an error.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Query parameters
|
||||
* @return {Boolean} True if the `getNotes` request has failed, false otherwise
|
||||
*/
|
||||
isGetNotesError( state, query ) {
|
||||
return ERROR === getNotes( state, query );
|
||||
},
|
||||
};
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import notesReducer from '../reducer';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
describe( 'notesReducer()', () => {
|
||||
it( 'returns an empty data object by default', () => {
|
||||
const state = notesReducer( undefined, {} );
|
||||
expect( state ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'returns with received notes data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
page: 2,
|
||||
};
|
||||
const notes = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = notesReducer( originalState, {
|
||||
type: 'SET_NOTES',
|
||||
query,
|
||||
notes,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( notes );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple queries in notes data', () => {
|
||||
const otherQuery = {
|
||||
page: 3,
|
||||
};
|
||||
const otherQueryKey = getJsonString( otherQuery );
|
||||
const otherNotes = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
const otherQueryState = {
|
||||
[ otherQueryKey ]: otherNotes,
|
||||
};
|
||||
const originalState = deepFreeze( otherQueryState );
|
||||
const query = {
|
||||
page: 2,
|
||||
};
|
||||
const notes = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = notesReducer( originalState, {
|
||||
type: 'SET_NOTES',
|
||||
query,
|
||||
notes,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( notes );
|
||||
expect( state[ otherQueryKey ] ).toEqual( otherNotes );
|
||||
} );
|
||||
|
||||
it( 'returns with received error data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
page: 2,
|
||||
};
|
||||
|
||||
const state = notesReducer( originalState, {
|
||||
type: 'SET_NOTES_ERROR',
|
||||
query,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getNotes } = resolvers;
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
dispatch: jest.fn().mockReturnValue( {
|
||||
setNotes: jest.fn(),
|
||||
} ),
|
||||
} ) );
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getNotes', () => {
|
||||
const NOTES_1 = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const NOTES_2 = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === NAMESPACE + 'admin/notes' ) {
|
||||
return Promise.resolve( NOTES_1 );
|
||||
}
|
||||
if ( options.path === NAMESPACE + 'admin/notes?page=2' ) {
|
||||
return Promise.resolve( NOTES_2 );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested data', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getNotes();
|
||||
expect( dispatch().setNotes ).toHaveBeenCalledWith( NOTES_1, undefined );
|
||||
} );
|
||||
|
||||
it( 'returns requested data for a specific query', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getNotes( { page: 2 } );
|
||||
expect( dispatch().setNotes ).toHaveBeenCalledWith( NOTES_2, { page: 2 } );
|
||||
} );
|
||||
} );
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import selectors from '../selectors';
|
||||
import { select } from '@wordpress/data';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const { getNotes, isGetNotesRequesting, isGetNotesError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const query = { page: 1 };
|
||||
const queryKey = getJsonString( query );
|
||||
|
||||
describe( 'getNotes()', () => {
|
||||
it( 'returns an empty array when no notes are available', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getNotes( state, query ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
it( 'returns stored notes for current query', () => {
|
||||
const notes = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const state = deepFreeze( {
|
||||
notes: {
|
||||
[ queryKey ]: notes,
|
||||
},
|
||||
} );
|
||||
expect( getNotes( state, query ) ).toEqual( notes );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetNotesRequesting()', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
isResolving && reducerKey === 'wc-admin' && selectorName === 'getNotes'
|
||||
);
|
||||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isGetNotesRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isGetNotesRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isGetNotesRequesting( query );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetNotesError()', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isGetNotesError( state, query ) ).toEqual( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const state = deepFreeze( {
|
||||
notes: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
expect( isGetNotesError( state, query ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
|
@ -1,17 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export default {
|
||||
setOrders( orders, query ) {
|
||||
return {
|
||||
type: 'SET_ORDERS',
|
||||
orders,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
setOrdersError( query ) {
|
||||
return {
|
||||
type: 'SET_ORDERS_ERROR',
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import actions from './actions';
|
||||
import reducer from './reducer';
|
||||
import resolvers from './resolvers';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
actions,
|
||||
reducer,
|
||||
resolvers,
|
||||
selectors,
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
export default function ordersReducer( state = DEFAULT_STATE, action ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
|
||||
switch ( action.type ) {
|
||||
case 'SET_ORDERS':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: action.orders,
|
||||
} );
|
||||
|
||||
case 'SET_ORDERS_ERROR':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: ERROR,
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export default {
|
||||
// TODO: Use controls data plugin or fresh-data instead of async
|
||||
async getOrders( ...args ) {
|
||||
// This is interim code to work with either 2.x or 3.x version of @wordpress/data
|
||||
// TODO: Change to just `getNotes( query )` after Gutenberg plugin uses @wordpress/data 3+
|
||||
const query = args.length === 1 ? args[ 0 ] : args[ 1 ];
|
||||
|
||||
try {
|
||||
const orders = await apiFetch( { path: NAMESPACE + 'orders' + stringifyQuery( query ) } );
|
||||
dispatch( 'wc-admin' ).setOrders( orders, query );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setOrdersError( query );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getJsonString } from 'store/utils';
|
||||
import { ERROR } from 'store/constants';
|
||||
|
||||
/**
|
||||
* Returns orders for a specific query.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Array} Report details
|
||||
*/
|
||||
function getOrders( state, query = {} ) {
|
||||
return get( state, [ 'orders', getJsonString( query ) ], [] );
|
||||
}
|
||||
|
||||
export default {
|
||||
getOrders,
|
||||
|
||||
/**
|
||||
* Returns true if a getOrders request is pending.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Boolean} True if the `getOrders` request is pending, false otherwise
|
||||
*/
|
||||
isGetOrdersRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getOrders', args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a getOrders request has returned an error.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Query parameters
|
||||
* @return {Boolean} True if the `getOrders` request has failed, false otherwise
|
||||
*/
|
||||
isGetOrdersError( state, query ) {
|
||||
return ERROR === getOrders( state, query );
|
||||
},
|
||||
};
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import ordersReducer from '../reducer';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
describe( 'ordersReducer()', () => {
|
||||
it( 'returns an empty data object by default', () => {
|
||||
const state = ordersReducer( undefined, {} );
|
||||
expect( state ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'returns with received orders data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS',
|
||||
query,
|
||||
orders,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( orders );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple queries in orders data', () => {
|
||||
const otherQuery = {
|
||||
orderby: 'id',
|
||||
};
|
||||
const otherQueryKey = getJsonString( otherQuery );
|
||||
const otherOrders = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
const otherQueryState = {
|
||||
[ otherQueryKey ]: otherOrders,
|
||||
};
|
||||
const originalState = deepFreeze( otherQueryState );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS',
|
||||
query,
|
||||
orders,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( orders );
|
||||
expect( state[ otherQueryKey ] ).toEqual( otherOrders );
|
||||
} );
|
||||
|
||||
it( 'returns with received error data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS_ERROR',
|
||||
query,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getOrders } = resolvers;
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
dispatch: jest.fn().mockReturnValue( {
|
||||
setOrders: jest.fn(),
|
||||
} ),
|
||||
} ) );
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getOrders', () => {
|
||||
const ORDERS_1 = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const ORDERS_2 = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === NAMESPACE + 'orders' ) {
|
||||
return Promise.resolve( ORDERS_1 );
|
||||
}
|
||||
if ( options.path === NAMESPACE + 'orders?orderby=id' ) {
|
||||
return Promise.resolve( ORDERS_2 );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getOrders();
|
||||
expect( dispatch().setOrders ).toHaveBeenCalledWith( ORDERS_1, undefined );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data for a specific query', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getOrders( { orderby: 'id' } );
|
||||
expect( dispatch().setOrders ).toHaveBeenCalledWith( ORDERS_2, { orderby: 'id' } );
|
||||
} );
|
||||
} );
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import selectors from '../selectors';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const { getOrders, isGetOrdersRequesting, isGetOrdersError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const query = { orderby: 'date' };
|
||||
const queryKey = getJsonString( query );
|
||||
|
||||
describe( 'getOrders()', () => {
|
||||
it( 'returns an empty array when no orders are available', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getOrders( state, query ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
it( 'returns stored orders for current query', () => {
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const state = deepFreeze( {
|
||||
orders: {
|
||||
[ queryKey ]: orders,
|
||||
},
|
||||
} );
|
||||
expect( getOrders( state, query ) ).toEqual( orders );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetOrdersRequesting()', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
isResolving && reducerKey === 'wc-admin' && selectorName === 'getOrders'
|
||||
);
|
||||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetOrdersError()', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isGetOrdersError( state, query ) ).toEqual( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const state = deepFreeze( {
|
||||
orders: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
expect( isGetOrdersError( state, query ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
|
@ -1,31 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { combineReducers } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import items from './items';
|
||||
import stats from './stats';
|
||||
|
||||
export default {
|
||||
reducer: combineReducers( {
|
||||
items: items.reducer,
|
||||
stats: stats.reducer,
|
||||
} ),
|
||||
actions: {
|
||||
...items.actions,
|
||||
...stats.actions,
|
||||
},
|
||||
selectors: {
|
||||
...items.selectors,
|
||||
...stats.selectors,
|
||||
},
|
||||
resolvers: {
|
||||
...items.resolvers,
|
||||
...stats.resolvers,
|
||||
},
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export default {
|
||||
setReportItems( endpoint, query, data, totalCount ) {
|
||||
return {
|
||||
type: 'SET_REPORT_ITEMS',
|
||||
endpoint,
|
||||
query: query || {},
|
||||
data,
|
||||
totalCount,
|
||||
};
|
||||
},
|
||||
setReportItemsError( endpoint, query ) {
|
||||
return {
|
||||
type: 'SET_REPORT_ITEMS_ERROR',
|
||||
endpoint,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import actions from './actions';
|
||||
import reducer from './reducer';
|
||||
import resolvers from './resolvers';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
actions,
|
||||
reducer,
|
||||
resolvers,
|
||||
selectors,
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
export default function reportItemsReducer( state = DEFAULT_STATE, action ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
|
||||
switch ( action.type ) {
|
||||
case 'SET_REPORT_ITEMS':
|
||||
return merge( {}, state, {
|
||||
[ action.endpoint ]: {
|
||||
[ queryKey ]: {
|
||||
data: action.data,
|
||||
totalCount: action.totalCount,
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
||||
case 'SET_REPORT_ITEMS_ERROR':
|
||||
return merge( {}, state, {
|
||||
[ action.endpoint ]: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export default {
|
||||
async getReportItems( ...args ) {
|
||||
const [ endpoint, query ] = args.slice( -2 );
|
||||
|
||||
try {
|
||||
const response = await apiFetch( {
|
||||
parse: false,
|
||||
path: NAMESPACE + 'reports/' + endpoint + stringifyQuery( query ),
|
||||
} );
|
||||
|
||||
const itemsData = await response.json();
|
||||
const totalCount = parseInt( response.headers.get( 'x-wp-total' ) );
|
||||
dispatch( 'wc-admin' ).setReportItems( endpoint, query, itemsData, totalCount );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setReportItemsError( endpoint, query );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
/**
|
||||
* Returns report items for a specific endpoint query.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {String} endpoint Stats endpoint
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Object} Report details
|
||||
*/
|
||||
function getReportItems( state, endpoint, query = {} ) {
|
||||
return get( state, [ 'reports', 'items', endpoint, getJsonString( query ) ], { data: [] } );
|
||||
}
|
||||
|
||||
export default {
|
||||
getReportItems,
|
||||
|
||||
/**
|
||||
* Returns true if a getReportItems request is pending.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Boolean} True if the `getReportItems` request is pending, false otherwise
|
||||
*/
|
||||
isGetReportItemsRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getReportItems', args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a getReportItems request has returned an error.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {String} endpoint Items endpoint
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Boolean} True if the `getReportItems` request has failed, false otherwise
|
||||
*/
|
||||
isGetReportItemsError( state, endpoint, query ) {
|
||||
return ERROR === getReportItems( state, endpoint, query );
|
||||
},
|
||||
};
|
|
@ -1,104 +0,0 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import reportItemsReducer from '../reducer';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
describe( 'reportItemsReducer()', () => {
|
||||
const endpoint = 'coupons';
|
||||
|
||||
it( 'returns an empty object by default', () => {
|
||||
const state = reportItemsReducer( undefined, {} );
|
||||
expect( state ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'returns with received items data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'orders_count',
|
||||
};
|
||||
const itemsData = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const itemsTotalCount = 50;
|
||||
|
||||
const state = reportItemsReducer( originalState, {
|
||||
type: 'SET_REPORT_ITEMS',
|
||||
endpoint,
|
||||
query,
|
||||
data: itemsData,
|
||||
totalCount: itemsTotalCount,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( {
|
||||
data: itemsData,
|
||||
totalCount: itemsTotalCount,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple queries in items data', () => {
|
||||
const otherQuery = {
|
||||
orderby: 'id',
|
||||
};
|
||||
const otherQueryKey = getJsonString( otherQuery );
|
||||
const otherItemsData = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
const otherItemsTotalCount = 70;
|
||||
const otherQueryState = {
|
||||
[ endpoint ]: {
|
||||
[ otherQueryKey ]: {
|
||||
data: otherItemsData,
|
||||
totalCount: otherItemsTotalCount,
|
||||
},
|
||||
},
|
||||
};
|
||||
const originalState = deepFreeze( otherQueryState );
|
||||
const query = {
|
||||
orderby: 'orders_count',
|
||||
};
|
||||
const itemsData = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const itemsTotalCount = 50;
|
||||
|
||||
const state = reportItemsReducer( originalState, {
|
||||
type: 'SET_REPORT_ITEMS',
|
||||
endpoint,
|
||||
query,
|
||||
data: itemsData,
|
||||
totalCount: itemsTotalCount,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( {
|
||||
data: itemsData,
|
||||
totalCount: itemsTotalCount,
|
||||
} );
|
||||
expect( state[ endpoint ][ otherQueryKey ] ).toEqual( {
|
||||
data: otherItemsData,
|
||||
totalCount: otherItemsTotalCount,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns with received error data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'orders_count',
|
||||
};
|
||||
|
||||
const state = reportItemsReducer( originalState, {
|
||||
type: 'SET_REPORT_ITEMS_ERROR',
|
||||
endpoint,
|
||||
query,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getReportItems } = resolvers;
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
dispatch: jest.fn().mockReturnValue( {
|
||||
setReportItems: jest.fn(),
|
||||
} ),
|
||||
} ) );
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getReportItems', () => {
|
||||
const ITEMS_1 = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const ITEMS_1_COUNT = 50;
|
||||
const ITEMS_2 = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
const ITEMS_2_COUNT = 75;
|
||||
const endpoint = 'products';
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === `/wc/v4/reports/${ endpoint }` ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: () => ITEMS_1_COUNT,
|
||||
},
|
||||
json: () => Promise.resolve( ITEMS_1 ),
|
||||
} );
|
||||
}
|
||||
if ( options.path === `/wc/v4/reports/${ endpoint }?orderby=id` ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: () => ITEMS_2_COUNT,
|
||||
},
|
||||
json: () => Promise.resolve( ITEMS_2 ),
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getReportItems( endpoint );
|
||||
expect( dispatch().setReportItems ).toHaveBeenCalledWith(
|
||||
endpoint,
|
||||
undefined,
|
||||
ITEMS_1,
|
||||
ITEMS_1_COUNT
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'returns requested report data for a specific query', async () => {
|
||||
expect.assertions( 1 );
|
||||
await getReportItems( endpoint, { orderby: 'id' } );
|
||||
expect( dispatch().setReportItems ).toHaveBeenCalledWith(
|
||||
endpoint,
|
||||
{ orderby: 'id' },
|
||||
ITEMS_2,
|
||||
ITEMS_2_COUNT
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
import selectors from '../selectors';
|
||||
|
||||
const { getReportItems, isGetReportItemsRequesting, isGetReportItemsError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const query = { orderby: 'date' };
|
||||
const queryKey = getJsonString( query );
|
||||
const endpoint = 'coupons';
|
||||
|
||||
describe( 'getReportItems()', () => {
|
||||
it( 'returns an empty object when no items are available', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getReportItems( state, endpoint, query ) ).toEqual( { data: [] } );
|
||||
} );
|
||||
|
||||
it( 'returns stored items for current query', () => {
|
||||
const itemsData = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const itemsTotalCount = 50;
|
||||
const queryState = {
|
||||
data: itemsData,
|
||||
totalCount: itemsTotalCount,
|
||||
};
|
||||
const state = deepFreeze( {
|
||||
reports: {
|
||||
items: {
|
||||
[ endpoint ]: {
|
||||
[ queryKey ]: queryState,
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
expect( getReportItems( state, endpoint, query ) ).toEqual( queryState );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetReportItemsRequesting()', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
isResolving && reducerKey === 'wc-admin' && selectorName === 'getReportItems'
|
||||
);
|
||||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isGetReportItemsRequesting( endpoint );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isGetReportItemsRequesting( endpoint );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isGetReportItemsRequesting( endpoint );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetReportItemsError()', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isGetReportItemsError( state, endpoint, query ) ).toEqual( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const state = deepFreeze( {
|
||||
reports: {
|
||||
items: {
|
||||
[ endpoint ]: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
expect( isGetReportItemsError( state, endpoint, query ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
|
@ -1,21 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export default {
|
||||
setReportStats( endpoint, report, query, totalResults, totalPages ) {
|
||||
return {
|
||||
type: 'SET_REPORT_STATS',
|
||||
endpoint,
|
||||
report,
|
||||
totalResults,
|
||||
totalPages,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
setReportStatsError( endpoint, query ) {
|
||||
return {
|
||||
type: 'SET_REPORT_STATS_ERROR',
|
||||
endpoint,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import actions from './actions';
|
||||
import reducer from './reducer';
|
||||
import resolvers from './resolvers';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
actions,
|
||||
reducer,
|
||||
resolvers,
|
||||
selectors,
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
export default function reportStatsReducer( state = DEFAULT_STATE, action ) {
|
||||
if ( 'SET_REPORT_STATS' === action.type ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
return merge( {}, state, {
|
||||
[ action.endpoint ]: {
|
||||
[ queryKey ]: {
|
||||
data: action.report,
|
||||
totalResults: action.totalResults,
|
||||
totalPages: action.totalPages,
|
||||
},
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
if ( 'SET_REPORT_STATS_ERROR' === action.type ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
return merge( {}, state, {
|
||||
[ action.endpoint ]: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE, SWAGGERNAMESPACE } from 'store/constants';
|
||||
|
||||
export default {
|
||||
// TODO: Use controls data plugin or fresh-data instead of async
|
||||
async getReportStats( ...args ) {
|
||||
// This is interim code to work with either 2.x or 3.x version of @wordpress/data
|
||||
// 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', 'taxes' ];
|
||||
|
||||
let apiPath = endpoint + stringifyQuery( query );
|
||||
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories' ];
|
||||
if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
apiPath = SWAGGERNAMESPACE + 'reports/' + endpoint + '/stats' + stringifyQuery( query );
|
||||
try {
|
||||
const response = await fetch( apiPath );
|
||||
|
||||
const report = await response.json();
|
||||
dispatch( 'wc-admin' ).setReportStats( endpoint, report, query );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setReportStatsError( endpoint, query );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( statEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
apiPath = NAMESPACE + 'reports/' + endpoint + '/stats' + stringifyQuery( query );
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiFetch( {
|
||||
path: apiPath,
|
||||
parse: false,
|
||||
} );
|
||||
|
||||
const report = await response.json();
|
||||
const totalResults = parseInt( response.headers.get( 'x-wp-total' ) );
|
||||
const totalPages = parseInt( response.headers.get( 'x-wp-totalpages' ) );
|
||||
dispatch( 'wc-admin' ).setReportStats( endpoint, report, query, totalResults, totalPages );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setReportStatsError( endpoint, query );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
/**
|
||||
* Returns report stats details for a specific endpoint query.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {String} endpoint Stats endpoint
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Object} Report details
|
||||
*/
|
||||
function getReportStats( state, endpoint, query = {} ) {
|
||||
const queries = get( state, [ 'reports', 'stats', endpoint ], {} );
|
||||
return queries[ getJsonString( query ) ] || null;
|
||||
}
|
||||
|
||||
export default {
|
||||
getReportStats,
|
||||
|
||||
/**
|
||||
* Returns true if a stats query is pending.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Boolean} True if the `getReportStats` request is pending, false otherwise
|
||||
*/
|
||||
isReportStatsRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getReportStats', args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a report stat request has returned an error.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {String} endpoint Stats endpoint
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Boolean} True if the `getReportStats` request has failed, false otherwise
|
||||
*/
|
||||
isReportStatsError( state, endpoint, query ) {
|
||||
return ERROR === getReportStats( state, endpoint, query );
|
||||
},
|
||||
};
|
|
@ -1,164 +0,0 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import reportStatsReducer from '../reducer';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
describe( 'reportStatsReducer()', () => {
|
||||
it( 'returns an empty data object by default', () => {
|
||||
const state = reportStatsReducer( undefined, {} );
|
||||
expect( state ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'returns with received report data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'day',
|
||||
};
|
||||
const report = {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
interval: [ 0, 1, 2 ],
|
||||
};
|
||||
const endpoint = 'revenue';
|
||||
|
||||
const state = reportStatsReducer( originalState, {
|
||||
type: 'SET_REPORT_STATS',
|
||||
endpoint,
|
||||
query,
|
||||
report,
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( {
|
||||
data: { ...report },
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple queries per endpoint in report data', () => {
|
||||
const otherQuery = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'week',
|
||||
};
|
||||
const otherQueryKey = getJsonString( otherQuery );
|
||||
const otherQueryState = {
|
||||
revenue: {
|
||||
[ otherQueryKey ]: { data: { totals: 1000 } },
|
||||
},
|
||||
};
|
||||
const originalState = deepFreeze( otherQueryState );
|
||||
const query = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'day',
|
||||
};
|
||||
const report = {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
interval: [ 0, 1, 2 ],
|
||||
};
|
||||
const endpoint = 'revenue';
|
||||
|
||||
const state = reportStatsReducer( originalState, {
|
||||
type: 'SET_REPORT_STATS',
|
||||
endpoint,
|
||||
query,
|
||||
report,
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( {
|
||||
data: { ...report },
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
expect( state[ endpoint ][ otherQueryKey ].data.totals ).toEqual( 1000 );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple endpoints in report data', () => {
|
||||
const productsQuery = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'week',
|
||||
};
|
||||
const productsQueryKey = getJsonString( productsQuery );
|
||||
const productsQueryState = {
|
||||
products: {
|
||||
[ productsQueryKey ]: { data: { totals: 1999 } },
|
||||
},
|
||||
};
|
||||
const originalState = deepFreeze( productsQueryState );
|
||||
const query = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'day',
|
||||
};
|
||||
const report = {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
interval: [ 0, 1, 2 ],
|
||||
};
|
||||
const endpoint = 'revenue';
|
||||
|
||||
const state = reportStatsReducer( originalState, {
|
||||
type: 'SET_REPORT_STATS',
|
||||
endpoint,
|
||||
query,
|
||||
report,
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( {
|
||||
data: { ...report },
|
||||
totalResults: 3,
|
||||
totalPages: 1,
|
||||
} );
|
||||
expect( state.products[ productsQueryKey ].data.totals ).toEqual( 1999 );
|
||||
} );
|
||||
|
||||
it( 'returns with received error data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
after: '2018-01-04T00:00:00+00:00',
|
||||
before: '2018-07-14T00:00:00+00:00',
|
||||
interval: 'day',
|
||||
};
|
||||
const endpoint = 'revenue';
|
||||
|
||||
const state = reportStatsReducer( originalState, {
|
||||
type: 'SET_REPORT_STATS_ERROR',
|
||||
endpoint,
|
||||
query,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ endpoint ][ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getReportStats } = resolvers;
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
dispatch: jest.fn().mockReturnValue( {
|
||||
setReportStats: jest.fn(),
|
||||
} ),
|
||||
} ) );
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getReportStats', () => {
|
||||
const REPORT_1 = {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
interval: [ 0, 1, 2 ],
|
||||
};
|
||||
const REPORT_1_TOTALS = {
|
||||
'x-wp-total': 10,
|
||||
'x-wp-totalpages': 2,
|
||||
};
|
||||
|
||||
const REPORT_2 = {
|
||||
totals: {
|
||||
orders_count: 5,
|
||||
items_sold: 5,
|
||||
gross_revenue: 999.99,
|
||||
},
|
||||
intervals: [
|
||||
{
|
||||
interval: 'week',
|
||||
subtotals: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
const REPORT_2_TOTALS = {
|
||||
'x-wp-total': 20,
|
||||
'x-wp-totalpages': 4,
|
||||
};
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v4/reports/revenue/stats' ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: header => REPORT_1_TOTALS[ header ],
|
||||
},
|
||||
json: () => Promise.resolve( REPORT_1 ),
|
||||
} );
|
||||
}
|
||||
if ( options.path === '/wc/v4/reports/products/stats?interval=week' ) {
|
||||
return Promise.resolve( {
|
||||
headers: {
|
||||
get: header => REPORT_2_TOTALS[ header ],
|
||||
},
|
||||
json: () => Promise.resolve( REPORT_2 ),
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data', async () => {
|
||||
expect.assertions( 1 );
|
||||
const endpoint = 'revenue';
|
||||
|
||||
await getReportStats( endpoint, undefined );
|
||||
|
||||
expect( dispatch().setReportStats ).toHaveBeenCalledWith(
|
||||
endpoint,
|
||||
REPORT_1,
|
||||
undefined,
|
||||
REPORT_1_TOTALS[ 'x-wp-total' ],
|
||||
REPORT_1_TOTALS[ 'x-wp-totalpages' ]
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'returns requested report data for a specific query', async () => {
|
||||
expect.assertions( 1 );
|
||||
const endpoint = 'products';
|
||||
const query = { interval: 'week' };
|
||||
|
||||
await getReportStats( endpoint, query );
|
||||
|
||||
expect( dispatch().setReportStats ).toHaveBeenCalledWith(
|
||||
endpoint,
|
||||
REPORT_2,
|
||||
query,
|
||||
REPORT_2_TOTALS[ 'x-wp-total' ],
|
||||
REPORT_2_TOTALS[ 'x-wp-totalpages' ]
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import selectors from '../selectors';
|
||||
|
||||
const { getReportStats, isReportStatsRequesting, isReportStatsError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const endpointName = 'revenue';
|
||||
|
||||
describe( 'getReportStats()', () => {
|
||||
it( 'returns null when no report data is available', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getReportStats( state, endpointName ) ).toEqual( null );
|
||||
} );
|
||||
it( 'returns stored report information by endpoint and query combination', () => {
|
||||
const report = {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
interval: [ 0, 1, 2 ],
|
||||
};
|
||||
const state = deepFreeze( {
|
||||
reports: {
|
||||
stats: {
|
||||
revenue: {
|
||||
'{}': { ...report },
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
expect( getReportStats( state, endpointName ) ).toEqual( report );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isReportStatsRequesting()', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
isResolving && reducerKey === 'wc-admin' && selectorName === 'getReportStats'
|
||||
);
|
||||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isReportStatsRequesting( endpointName );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isReportStatsRequesting( endpointName );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isReportStatsRequesting( endpointName );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isReportStatsError()', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isReportStatsError( state, endpointName ) ).toEqual( false );
|
||||
} );
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const state = deepFreeze( {
|
||||
reports: {
|
||||
stats: {
|
||||
revenue: {
|
||||
'{}': ERROR,
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
expect( isReportStatsError( state, endpointName ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
|
@ -1,598 +0,0 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
isReportDataEmpty,
|
||||
getReportChartData,
|
||||
getSummaryNumbers,
|
||||
getFilterQuery,
|
||||
getReportTableData,
|
||||
timeStampFilterDates,
|
||||
} from '../utils';
|
||||
import * as ordersConfig from 'analytics/report/orders/config';
|
||||
|
||||
jest.mock( '../utils', () => ( {
|
||||
...require.requireActual( '../utils' ),
|
||||
getReportTableQuery: () => ( {
|
||||
after: '2018-10-10',
|
||||
before: '2018-10-10',
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
describe( 'isReportDataEmpty()', () => {
|
||||
it( 'returns false if report is valid', () => {
|
||||
const report = {
|
||||
data: {
|
||||
totals: {
|
||||
orders_count: 10,
|
||||
num_items_sold: 9,
|
||||
},
|
||||
intervals: [ 0, 1, 2 ],
|
||||
},
|
||||
};
|
||||
expect( isReportDataEmpty( report ) ).toEqual( false );
|
||||
} );
|
||||
it( 'returns true if report object is undefined', () => {
|
||||
expect( isReportDataEmpty( undefined ) ).toEqual( true );
|
||||
} );
|
||||
it( 'returns true if data response object is missing', () => {
|
||||
expect( isReportDataEmpty( {} ) ).toEqual( true );
|
||||
} );
|
||||
it( 'returns true if totals response object is missing', () => {
|
||||
expect( isReportDataEmpty( { data: {} } ) ).toEqual( true );
|
||||
} );
|
||||
it( 'returns true if intervals response object is empty', () => {
|
||||
expect( isReportDataEmpty( { data: { intervals: [], totals: 2 } } ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getReportChartData()', () => {
|
||||
const select = jest.fn().mockReturnValue( {} );
|
||||
const response = {
|
||||
isEmpty: false,
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
data: {
|
||||
totals: null,
|
||||
intervals: [],
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportStats = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportStatsRequesting = 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' ).getReportStatsError.mockRestore();
|
||||
} );
|
||||
|
||||
function setGetReportStats( func ) {
|
||||
select( 'wc-api' ).getReportStats.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setIsReportStatsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setGetReportStatsError( func ) {
|
||||
select( 'wc-api' ).getReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if first request is in progress', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return true;
|
||||
} );
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isRequesting: true } );
|
||||
} );
|
||||
|
||||
it( 'returns isError if first request errors', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return { error: 'Error' };
|
||||
} );
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
} );
|
||||
|
||||
it( 'returns results after single page of data', () => {
|
||||
const data = {
|
||||
totals: {
|
||||
orders_count: 115,
|
||||
gross_revenue: 13966.92,
|
||||
},
|
||||
intervals: [
|
||||
{
|
||||
interval: 'day',
|
||||
date_start: '2018-07-01 00:00:00',
|
||||
subtotals: {
|
||||
orders_count: 115,
|
||||
gross_revenue: 13966.92,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
totalResults: 1,
|
||||
data,
|
||||
};
|
||||
} );
|
||||
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, data: { ...data } } );
|
||||
} );
|
||||
|
||||
it( 'returns combined results for multiple pages of data', () => {
|
||||
const totalResults = 110;
|
||||
const orders_count = 115;
|
||||
const gross_revenue = 13966.92;
|
||||
const intervals = [];
|
||||
for ( let i = 0; i < totalResults; i++ ) {
|
||||
intervals.push( {
|
||||
interval: 'day',
|
||||
date_start: '2018-07-01 00:00:00',
|
||||
subtotals: { orders_count, gross_revenue },
|
||||
} );
|
||||
}
|
||||
const totals = {
|
||||
orders_count: orders_count * totalResults,
|
||||
gross_revenue: gross_revenue * totalResults,
|
||||
};
|
||||
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( ( endpoint, query ) => {
|
||||
if ( 2 === query.page ) {
|
||||
return {
|
||||
totalResults,
|
||||
data: {
|
||||
totals,
|
||||
intervals: intervals.slice( 100, 110 ),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
totalResults,
|
||||
data: {
|
||||
totals,
|
||||
intervals: intervals.slice( 0, 100 ),
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
||||
const actualResponse = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
const expectedResponse = {
|
||||
...response,
|
||||
data: {
|
||||
totals,
|
||||
intervals,
|
||||
},
|
||||
};
|
||||
|
||||
expect( actualResponse ).toEqual( expectedResponse );
|
||||
} );
|
||||
|
||||
it( 'returns isRequesting if additional requests are in progress', () => {
|
||||
setIsReportStatsRequesting( ( endpoint, query ) => {
|
||||
if ( 2 === query.page ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isRequesting: true } );
|
||||
} );
|
||||
|
||||
it( 'returns isError if additional requests return an error', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( ( endpoint, query ) => {
|
||||
if ( 2 === query.page ) {
|
||||
return { error: 'Error' };
|
||||
}
|
||||
return undefined;
|
||||
} );
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
} );
|
||||
|
||||
it( 'returns empty state if a query returns no data', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
totalResults: undefined,
|
||||
data: {},
|
||||
};
|
||||
} );
|
||||
|
||||
const result = getReportChartData( 'revenue', 'primary', {}, select );
|
||||
expect( result ).toEqual( { ...response, isEmpty: true } );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getSummaryNumbers()', () => {
|
||||
const select = jest.fn().mockReturnValue( {} );
|
||||
const response = {
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
totals: {
|
||||
primary: null,
|
||||
secondary: null,
|
||||
},
|
||||
};
|
||||
|
||||
const query = {
|
||||
after: '2018-10-10',
|
||||
before: '2018-10-10',
|
||||
period: 'custom',
|
||||
compare: 'previous_period',
|
||||
};
|
||||
|
||||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportStats = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportStatsRequesting = 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' ).getReportStatsError.mockRestore();
|
||||
} );
|
||||
|
||||
function setGetReportStats( func ) {
|
||||
select( 'wc-api' ).getReportStats.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setIsReportStatsRequesting( func ) {
|
||||
select( 'wc-api' ).isReportStatsRequesting.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
function setGetReportStatsError( func ) {
|
||||
select( 'wc-api' ).getReportStatsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if a request is in progress', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return true;
|
||||
} );
|
||||
const result = getSummaryNumbers( 'revenue', query, select );
|
||||
expect( result ).toEqual( { ...response, isRequesting: true } );
|
||||
} );
|
||||
|
||||
it( 'returns isError if request errors', () => {
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return { error: 'Error' };
|
||||
} );
|
||||
const result = getSummaryNumbers( 'revenue', query, select );
|
||||
expect( result ).toEqual( { ...response, isError: true } );
|
||||
} );
|
||||
|
||||
it( 'returns results after queries finish', () => {
|
||||
const totals = {
|
||||
primary: {
|
||||
orders_count: 115,
|
||||
gross_revenue: 13966.92,
|
||||
},
|
||||
secondary: {
|
||||
orders_count: 85,
|
||||
gross_revenue: 10406.1,
|
||||
},
|
||||
};
|
||||
|
||||
setIsReportStatsRequesting( () => {
|
||||
return false;
|
||||
} );
|
||||
setGetReportStatsError( () => {
|
||||
return undefined;
|
||||
} );
|
||||
setGetReportStats( () => {
|
||||
return {
|
||||
totals,
|
||||
};
|
||||
} );
|
||||
|
||||
setGetReportStats( ( endpoint, _query ) => {
|
||||
if ( '2018-10-10T00:00:00+00:00' === _query.after ) {
|
||||
return {
|
||||
data: {
|
||||
totals: totals.primary,
|
||||
intervals: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
totals: totals.secondary,
|
||||
intervals: [],
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
||||
const result = getSummaryNumbers( 'revenue', query, select );
|
||||
expect( result ).toEqual( { ...response, totals } );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getFilterQuery', () => {
|
||||
/**
|
||||
* Mock the orders config
|
||||
*/
|
||||
const filters = [
|
||||
{
|
||||
param: 'filter',
|
||||
filters: [
|
||||
{ value: 'top_meal', query: { lunch: 'burritos' } },
|
||||
{ value: 'top_dessert', query: { dinner: 'ice_cream' } },
|
||||
{ value: 'compare-cuisines', settings: { param: 'region' } },
|
||||
{
|
||||
value: 'food_destination',
|
||||
subFilters: [
|
||||
{ value: 'choose_a_european_city', settings: { param: 'european_cities' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const advancedFilters = {
|
||||
filters: {
|
||||
mexican: {
|
||||
rules: [ { value: 'is' }, { value: 'is_not' } ],
|
||||
},
|
||||
french: {
|
||||
rules: [ { value: 'includes' }, { value: 'excludes' } ],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ordersConfig.filters = filters;
|
||||
ordersConfig.advancedFilters = advancedFilters;
|
||||
|
||||
it( 'should return an empty object if no filter param is given', () => {
|
||||
const query = {};
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'should return an empty object if filter parameter is not in configs', () => {
|
||||
const query = { filter: 'canned_meat' };
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'should return the query for an advanced filter', () => {
|
||||
const query = { filter: 'advanced', mexican_is: 'delicious' };
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( { mexican_is: 'delicious', match: 'all' } );
|
||||
} );
|
||||
|
||||
it( 'should ignore other queries not defined by filter configs', () => {
|
||||
const query = {
|
||||
filter: 'advanced',
|
||||
mexican_is_not: 'healthy',
|
||||
orderby: 'calories',
|
||||
topping: 'salsa-verde',
|
||||
};
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( { mexican_is_not: 'healthy', match: 'all' } );
|
||||
} );
|
||||
|
||||
it( 'should apply the match parameter advanced filters', () => {
|
||||
const query = {
|
||||
filter: 'advanced',
|
||||
french_includes: 'le-fromage',
|
||||
match: 'any',
|
||||
};
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( { french_includes: 'le-fromage', match: 'any' } );
|
||||
} );
|
||||
|
||||
it( 'should return the query for compare filters', () => {
|
||||
const query = { filter: 'compare-cuisines', region: 'vietnam,malaysia,thailand' };
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( { region: 'vietnam,malaysia,thailand' } );
|
||||
} );
|
||||
|
||||
it( 'should return the query for subFilters', () => {
|
||||
const query = { filter: 'choose_a_european_city', european_cities: 'paris,rome,barcelona' };
|
||||
const filterQuery = getFilterQuery( 'orders', query );
|
||||
|
||||
expect( filterQuery ).toEqual( { european_cities: 'paris,rome,barcelona' } );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getReportTableData()', () => {
|
||||
const select = jest.fn().mockReturnValue( {} );
|
||||
const response = {
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
items: {
|
||||
data: [],
|
||||
},
|
||||
};
|
||||
|
||||
const query = {
|
||||
after: '2018-10-10',
|
||||
before: '2018-10-10',
|
||||
};
|
||||
|
||||
beforeAll( () => {
|
||||
select( 'wc-api' ).getReportItems = jest.fn().mockReturnValue( {} );
|
||||
select( 'wc-api' ).isReportItemsRequesting = 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' ).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 setGetReportItemsError( func ) {
|
||||
select( 'wc-api' ).getReportItemsError.mockImplementation( ( ...args ) => func( ...args ) );
|
||||
}
|
||||
|
||||
it( 'returns isRequesting if a request is in progress', () => {
|
||||
setIsReportItemsRequesting( () => true );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
||||
expect( result ).toEqual( { ...response, query, isRequesting: true } );
|
||||
expect( select( 'wc-api' ).getReportItems ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
expect( select( 'wc-api' ).isReportItemsRequesting ).toHaveBeenLastCalledWith(
|
||||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenCalledTimes( 0 );
|
||||
} );
|
||||
|
||||
it( 'returns isError if request errors', () => {
|
||||
setIsReportItemsRequesting( () => false );
|
||||
setGetReportItemsError( () => ( { error: 'Error' } ) );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
||||
expect( result ).toEqual( { ...response, query, isError: true } );
|
||||
expect( select( 'wc-api' ).getReportItems ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
expect( select( 'wc-api' ).isReportItemsRequesting ).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 );
|
||||
setGetReportItemsError( () => undefined );
|
||||
setGetReportItems( () => items );
|
||||
|
||||
const result = getReportTableData( 'coupons', query, select );
|
||||
|
||||
expect( result ).toEqual( { ...response, query, items } );
|
||||
expect( select( 'wc-api' ).getReportItems ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
expect( select( 'wc-api' ).isReportItemsRequesting ).toHaveBeenLastCalledWith(
|
||||
'coupons',
|
||||
query
|
||||
);
|
||||
expect( select( 'wc-api' ).getReportItemsError ).toHaveBeenLastCalledWith( 'coupons', query );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'timeStampFilterDates', () => {
|
||||
const advancedFilters = {
|
||||
filters: {
|
||||
city: {
|
||||
input: { component: 'Search' },
|
||||
},
|
||||
my_date: {
|
||||
input: { component: 'Date' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it( 'should not change activeFilters not using the Date component', () => {
|
||||
const activeFilter = {
|
||||
key: 'name',
|
||||
rule: 'is',
|
||||
value: 'New York',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
|
||||
expect( timeStampedActiveFilter ).toMatchObject( activeFilter );
|
||||
} );
|
||||
|
||||
it( 'should append timestamps to activeFilters using the Date component', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'after',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T00:00:00+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should append start of day for "after" rule', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'after',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T00:00:00+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should append end of day for "before" rule', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'before',
|
||||
value: '2018-04-04',
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( timeStampedActiveFilter.value ).toBe( '2018-04-04T23:59:59+00:00' );
|
||||
} );
|
||||
|
||||
it( 'should handle "between" values', () => {
|
||||
const activeFilter = {
|
||||
key: 'my_date',
|
||||
rule: 'before',
|
||||
value: [ '2018-04-04', '2018-04-10' ],
|
||||
};
|
||||
const timeStampedActiveFilter = timeStampFilterDates( advancedFilters, activeFilter );
|
||||
expect( Array.isArray( timeStampedActiveFilter.value ) ).toBe( true );
|
||||
expect( timeStampedActiveFilter.value ).toHaveLength( 2 );
|
||||
expect( timeStampedActiveFilter.value[ 0 ] ).toContain( 'T00:00:00+00:00' );
|
||||
expect( timeStampedActiveFilter.value[ 1 ] ).toContain( 'T23:59:59+00:00' );
|
||||
} );
|
||||
} );
|
|
@ -1,11 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* Returns a string representation of a sorted query object.
|
||||
*
|
||||
* @param {Object} query Current state
|
||||
* @return {String} Query Key
|
||||
*/
|
||||
export function getJsonString( query = {} ) {
|
||||
return JSON.stringify( query, Object.keys( query ).sort() );
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isResourcePrefix, getResourceIdentifier, getResourceName } from '../utils';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'category-query' ) );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
const query = getResourceIdentifier( resourceName );
|
||||
const url = NAMESPACE + `/products/categories${ stringifyQuery( query ) }`;
|
||||
|
||||
try {
|
||||
const categories = await fetch( {
|
||||
path: url,
|
||||
} );
|
||||
|
||||
const ids = categories.map( category => category.id );
|
||||
const categoryResources = categories.reduce( ( resources, category ) => {
|
||||
resources[ getResourceName( 'category', category.id ) ] = { data: category };
|
||||
return resources;
|
||||
}, {} );
|
||||
|
||||
return {
|
||||
[ resourceName ]: {
|
||||
data: ids,
|
||||
totalCount: ids.length,
|
||||
},
|
||||
...categoryResources,
|
||||
};
|
||||
} catch ( error ) {
|
||||
return { [ resourceName ]: { error } };
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getCategories = ( getResource, requireResource ) => (
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
const resourceName = getResourceName( 'category-query', query );
|
||||
const ids = requireResource( requirement, resourceName ).data || [];
|
||||
const categories = ids.reduce(
|
||||
( acc, id ) => ( {
|
||||
...acc,
|
||||
[ id ]: getResource( getResourceName( 'category', id ) ).data || {},
|
||||
} ),
|
||||
{}
|
||||
);
|
||||
return categories;
|
||||
};
|
||||
|
||||
const getCategoriesTotalCount = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'category-query', query );
|
||||
return getResource( resourceName ).totalCount || 0;
|
||||
};
|
||||
|
||||
const getCategoriesError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'category-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isGetCategoriesRequesting = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'category-query', query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
||||
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getCategories,
|
||||
getCategoriesError,
|
||||
getCategoriesTotalCount,
|
||||
isGetCategoriesRequesting,
|
||||
};
|
|
@ -6,6 +6,9 @@ import { MINUTE } from '@fresh-data/framework';
|
|||
|
||||
export const NAMESPACE = '/wc/v4';
|
||||
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
export const SWAGGERNAMESPACE = 'https://virtserver.swaggerhub.com/peterfabian/wc-v3-api/1.0.0/';
|
||||
|
||||
export const DEFAULT_REQUIREMENT = {
|
||||
timeout: 1 * MINUTE,
|
||||
freshness: 5 * MINUTE,
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceIdentifier, getResourcePrefix, getResourceName } from '../utils';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
const typeEndpointMap = {
|
||||
'items-query-categories': 'products/categories',
|
||||
'items-query-customers': 'customers',
|
||||
'items-query-coupons': 'coupons',
|
||||
'items-query-orders': 'orders',
|
||||
'items-query-products': 'products',
|
||||
'items-query-taxes': 'taxes',
|
||||
};
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
const filteredNames = resourceNames.filter( name => {
|
||||
const prefix = getResourcePrefix( name );
|
||||
return Boolean( typeEndpointMap[ prefix ] );
|
||||
} );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
const prefix = getResourcePrefix( resourceName );
|
||||
const endpoint = typeEndpointMap[ prefix ];
|
||||
const query = getResourceIdentifier( resourceName );
|
||||
const url = NAMESPACE + `/${ endpoint }${ stringifyQuery( query ) }`;
|
||||
|
||||
try {
|
||||
const items = await fetch( {
|
||||
path: url,
|
||||
} );
|
||||
|
||||
const ids = items.map( item => item.id );
|
||||
const itemResources = items.reduce( ( resources, item ) => {
|
||||
resources[ getResourceName( `${ prefix }-item`, item.id ) ] = { data: item };
|
||||
return resources;
|
||||
}, {} );
|
||||
|
||||
return {
|
||||
[ resourceName ]: {
|
||||
data: ids,
|
||||
totalCount: ids.length,
|
||||
},
|
||||
...itemResources,
|
||||
};
|
||||
} catch ( error ) {
|
||||
return { [ resourceName ]: { error } };
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getItems = ( getResource, requireResource ) => (
|
||||
type,
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
const resourceName = getResourceName( `items-query-${ type }`, query );
|
||||
const ids = requireResource( requirement, resourceName ).data || [];
|
||||
const items = new Map();
|
||||
ids.forEach( id => {
|
||||
items.set( id, getResource( getResourceName( `items-query-${ type }-item`, id ) ).data );
|
||||
} );
|
||||
return items;
|
||||
};
|
||||
|
||||
const getItemsTotalCount = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `items-query-${ type }`, query );
|
||||
return getResource( resourceName ).totalCount || 0;
|
||||
};
|
||||
|
||||
const getItemsError = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `items-query-${ type }`, query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const isGetItemsRequesting = getResource => ( type, query = {} ) => {
|
||||
const resourceName = getResourceName( `items-query-${ type }`, query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
||||
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getItems,
|
||||
getItemsError,
|
||||
getItemsTotalCount,
|
||||
isGetItemsRequesting,
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns items based on a search query.
|
||||
*
|
||||
* @param {Object} select Instance of @wordpress/select
|
||||
* @param {String} endpoint Report API Endpoint
|
||||
* @param {String} search Search strings separated by commas.
|
||||
* @return {Object} Object Object containing the matching items.
|
||||
*/
|
||||
export function searchItemsByString( select, endpoint, search ) {
|
||||
const { getItems } = select( 'wc-api' );
|
||||
const searchWords = search.split( ',' );
|
||||
|
||||
const items = {};
|
||||
searchWords.forEach( searchWord => {
|
||||
const newItems = getItems( endpoint, {
|
||||
search: searchWord,
|
||||
per_page: 10,
|
||||
} );
|
||||
newItems.forEach( ( item, id ) => {
|
||||
items[ id ] = item;
|
||||
} );
|
||||
} );
|
||||
|
||||
return items;
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { stringifyQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isResourcePrefix, getResourceIdentifier, getResourceName } from '../utils';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
return [ ...readOrders( resourceNames, fetch ), ...readOrderQueries( resourceNames, fetch ) ];
|
||||
}
|
||||
|
||||
function readOrderQueries( resourceNames, fetch ) {
|
||||
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'order-query' ) );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
const query = getResourceIdentifier( resourceName );
|
||||
const url = `${ NAMESPACE }/orders${ stringifyQuery( query ) }`;
|
||||
|
||||
try {
|
||||
const response = await fetch( {
|
||||
parse: false,
|
||||
path: url,
|
||||
} );
|
||||
|
||||
const orders = await response.json();
|
||||
const totalCount = parseInt( response.headers.get( 'x-wp-total' ) );
|
||||
const ids = orders.map( order => order.id );
|
||||
const orderResources = orders.reduce( ( resources, order ) => {
|
||||
resources[ getResourceName( 'order', order.id ) ] = { data: order };
|
||||
return resources;
|
||||
}, {} );
|
||||
|
||||
return {
|
||||
[ resourceName ]: {
|
||||
data: ids,
|
||||
totalCount,
|
||||
},
|
||||
...orderResources,
|
||||
};
|
||||
} catch ( error ) {
|
||||
return { [ resourceName ]: { error } };
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
function readOrders( resourceNames, fetch ) {
|
||||
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'order' ) );
|
||||
return filteredNames.map( resourceName => readOrder( resourceName, fetch ) );
|
||||
}
|
||||
|
||||
function readOrder( resourceName, fetch ) {
|
||||
const id = getResourceIdentifier( resourceName );
|
||||
const url = `${ NAMESPACE }/orders/${ id }`;
|
||||
|
||||
return fetch( { path: url } )
|
||||
.then( order => {
|
||||
return { [ resourceName ]: { data: order } };
|
||||
} )
|
||||
.catch( error => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} );
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getOrders = ( getResource, requireResource ) => (
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
const ids = requireResource( requirement, resourceName ).data || [];
|
||||
const orders = ids.map( id => getResource( getResourceName( 'order', id ) ).data || {} );
|
||||
return orders;
|
||||
};
|
||||
|
||||
const getOrdersError = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
return getResource( resourceName ).error;
|
||||
};
|
||||
|
||||
const getOrdersTotalCount = ( getResource, requireResource ) => (
|
||||
query = {},
|
||||
requirement = DEFAULT_REQUIREMENT
|
||||
) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
return requireResource( requirement, resourceName ).totalCount || 0;
|
||||
};
|
||||
|
||||
const isGetOrdersRequesting = getResource => ( query = {} ) => {
|
||||
const resourceName = getResourceName( 'order-query', query );
|
||||
const { lastRequested, lastReceived } = getResource( resourceName );
|
||||
|
||||
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getOrders,
|
||||
getOrdersError,
|
||||
getOrdersTotalCount,
|
||||
isGetOrdersRequesting,
|
||||
};
|
|
@ -12,11 +12,18 @@ import { stringifyQuery } from '@woocommerce/navigation';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceIdentifier, getResourcePrefix } from '../../utils';
|
||||
import { NAMESPACE } from '../../constants';
|
||||
import { SWAGGERNAMESPACE } from 'store/constants';
|
||||
import { getResourceIdentifier, getResourcePrefix } from 'wc-api/utils';
|
||||
import { NAMESPACE, SWAGGERNAMESPACE } from 'wc-api/constants';
|
||||
|
||||
const statEndpoints = [ 'coupons', 'downloads', 'orders', 'products', 'revenue', 'taxes' ];
|
||||
const statEndpoints = [
|
||||
'coupons',
|
||||
'downloads',
|
||||
'orders',
|
||||
'products',
|
||||
'revenue',
|
||||
'taxes',
|
||||
'customers',
|
||||
];
|
||||
// TODO: Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories' ];
|
||||
|
||||
|
@ -28,6 +35,7 @@ const typeEndpointMap = {
|
|||
'report-stats-query-downloads': 'downloads',
|
||||
'report-stats-query-coupons': 'coupons',
|
||||
'report-stats-query-taxes': 'taxes',
|
||||
'report-stats-query-customers': 'customers',
|
||||
};
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
|
|
|
@ -16,7 +16,7 @@ import { formatCurrency } from '@woocommerce/currency';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { MAX_PER_PAGE, QUERY_DEFAULTS } from 'store/constants';
|
||||
import { MAX_PER_PAGE, QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import * as categoriesConfig from 'analytics/report/categories/config';
|
||||
import * as couponsConfig from 'analytics/report/coupons/config';
|
||||
import * as customersConfig from 'analytics/report/customers/config';
|
||||
|
@ -37,6 +37,12 @@ const reportConfigs = {
|
|||
};
|
||||
|
||||
export function getFilterQuery( endpoint, query ) {
|
||||
if ( query.search ) {
|
||||
return {
|
||||
[ endpoint ]: query[ endpoint ],
|
||||
};
|
||||
}
|
||||
|
||||
if ( reportConfigs[ endpoint ] ) {
|
||||
const { filters = [], advancedFilters = {} } = reportConfigs[ endpoint ];
|
||||
return filters
|
||||
|
@ -335,7 +341,7 @@ export function getReportTableQuery( endpoint, urlQuery, query ) {
|
|||
*
|
||||
* @param {String} endpoint Report API Endpoint
|
||||
* @param {Object} urlQuery Query parameters in the url
|
||||
* @param {object} select Instance of @wordpress/select
|
||||
* @param {Object} select Instance of @wordpress/select
|
||||
* @param {Object} query Query parameters specific for that endpoint
|
||||
* @return {Object} Object Table data response
|
||||
*/
|
|
@ -4,8 +4,10 @@
|
|||
*/
|
||||
import operations from './operations';
|
||||
import selectors from './selectors';
|
||||
import mutations from './mutations';
|
||||
|
||||
export default {
|
||||
operations,
|
||||
selectors,
|
||||
mutations,
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/** @format */
|
||||
|
||||
const updateSettings = operations => settingFields => {
|
||||
const resourceKey = 'settings';
|
||||
operations.update( [ resourceKey ], { [ resourceKey ]: settingFields } );
|
||||
};
|
||||
|
||||
export default {
|
||||
updateSettings,
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
function read( resourceNames, fetch = apiFetch ) {
|
||||
return [ ...readSettings( resourceNames, fetch ) ];
|
||||
}
|
||||
|
||||
function update( resourceNames, data, fetch = apiFetch ) {
|
||||
return [ ...updateSettings( resourceNames, data, fetch ) ];
|
||||
}
|
||||
|
||||
function readSettings( resourceNames, fetch ) {
|
||||
if ( resourceNames.includes( 'settings' ) ) {
|
||||
const url = NAMESPACE + '/settings/wc_admin';
|
||||
|
||||
return [
|
||||
fetch( { path: url } )
|
||||
.then( settingsToSettingsResource )
|
||||
.catch( error => {
|
||||
return { [ 'settings' ]: { error: String( error.message ) } };
|
||||
} ),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function updateSettings( resourceNames, data, fetch ) {
|
||||
const resourceName = 'settings';
|
||||
const settingsFields = [ 'woocommerce_excluded_report_order_statuses' ];
|
||||
|
||||
if ( resourceNames.includes( resourceName ) ) {
|
||||
const url = NAMESPACE + '/settings/wc_admin/';
|
||||
const settingsData = pick( data[ resourceName ], settingsFields );
|
||||
|
||||
const promises = Object.keys( settingsData ).map( setting => {
|
||||
return fetch( {
|
||||
path: url + setting,
|
||||
method: 'POST',
|
||||
data: { value: settingsData[ setting ] },
|
||||
} )
|
||||
.then( settingsToSettingsResource )
|
||||
.catch( error => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} );
|
||||
} );
|
||||
|
||||
return [ promises ];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function settingsToSettingsResource( settings ) {
|
||||
const settingsData = {};
|
||||
settings.forEach( setting => ( settingsData[ setting.id ] = setting.value ) );
|
||||
return { [ 'settings' ]: { data: settingsData } };
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
update,
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getSettings = ( getResource, requireResource ) => ( requirement = DEFAULT_REQUIREMENT ) => {
|
||||
return requireResource( requirement, 'settings' ).data;
|
||||
};
|
||||
|
||||
export default {
|
||||
getSettings,
|
||||
};
|
|
@ -3,42 +3,46 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import categories from './categories';
|
||||
import items from './items';
|
||||
import notes from './notes';
|
||||
import orders from './orders';
|
||||
import reportItems from './reports/items';
|
||||
import reportStats from './reports/stats';
|
||||
import reviews from './reviews';
|
||||
import settings from './settings';
|
||||
import user from './user';
|
||||
|
||||
function createWcApiSpec() {
|
||||
return {
|
||||
mutations: {
|
||||
...settings.mutations,
|
||||
...user.mutations,
|
||||
},
|
||||
selectors: {
|
||||
...categories.selectors,
|
||||
...items.selectors,
|
||||
...notes.selectors,
|
||||
...orders.selectors,
|
||||
...reportItems.selectors,
|
||||
...reportStats.selectors,
|
||||
...reviews.selectors,
|
||||
...settings.selectors,
|
||||
...user.selectors,
|
||||
},
|
||||
operations: {
|
||||
read( resourceNames ) {
|
||||
return [
|
||||
...categories.operations.read( resourceNames ),
|
||||
...items.operations.read( resourceNames ),
|
||||
...notes.operations.read( resourceNames ),
|
||||
...orders.operations.read( resourceNames ),
|
||||
...reportItems.operations.read( resourceNames ),
|
||||
...reportStats.operations.read( resourceNames ),
|
||||
...reviews.operations.read( resourceNames ),
|
||||
...settings.operations.read( resourceNames ),
|
||||
...user.operations.read( resourceNames ),
|
||||
];
|
||||
},
|
||||
update( resourceNames, data ) {
|
||||
return [ ...user.operations.update( resourceNames, data ) ];
|
||||
return [
|
||||
...settings.operations.update( resourceNames, data ),
|
||||
...user.operations.update( resourceNames, data ),
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
`Flag` (component)
|
||||
==================
|
||||
|
||||
Use the `Flag` component to display a country's flag.
|
||||
|
||||
Use the `Flag` component to display a country's flag using the operating system's emojis.
|
||||
|
||||
React component.
|
||||
|
||||
Props
|
||||
-----
|
||||
|
@ -22,27 +22,6 @@ Two letter, three letter or three digit country code.
|
|||
|
||||
An order can be passed instead of `code` and the code will automatically be pulled from the billing or shipping data.
|
||||
|
||||
### `round`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: `true`
|
||||
|
||||
True to display a rounded flag.
|
||||
|
||||
### `height`
|
||||
|
||||
- Type: Number
|
||||
- Default: `24`
|
||||
|
||||
Flag image height.
|
||||
|
||||
### `width`
|
||||
|
||||
- Type: Number
|
||||
- Default: `24`
|
||||
|
||||
Flag image width.
|
||||
|
||||
### `className`
|
||||
|
||||
- Type: String
|
||||
|
@ -50,3 +29,10 @@ Flag image width.
|
|||
|
||||
Additional CSS classes.
|
||||
|
||||
### `size`
|
||||
|
||||
- Type: Number
|
||||
- Default: null
|
||||
|
||||
Supply a font size to be applied to the emoji flag.
|
||||
|
||||
|
|
|
@ -12,17 +12,17 @@ Props
|
|||
### `children`
|
||||
|
||||
- **Required**
|
||||
- Type: ReactNode
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
A list of `<SummaryNumber />`s
|
||||
A function returning a list of `<SummaryNumber />`s
|
||||
|
||||
### `label`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
- Default: `__( 'Performance Indicators', 'wc-admin' )`
|
||||
|
||||
An optional label of this group, read to screen reader users. Defaults to "Performance Indicators".
|
||||
An optional label of this group, read to screen reader users.
|
||||
|
||||
`SummaryNumber` (component)
|
||||
===========================
|
||||
|
@ -46,7 +46,7 @@ If omitted, no change value will display.
|
|||
### `href`
|
||||
|
||||
- Type: String
|
||||
- Default: `'/analytics'`
|
||||
- Default: `''`
|
||||
|
||||
An internal link to the report focused on this number.
|
||||
|
||||
|
@ -109,6 +109,13 @@ A boolean used to show a highlight style on this number.
|
|||
|
||||
A string or number value to display - a string is allowed so we can accept currency formatting.
|
||||
|
||||
### `onLinkClickCallback`
|
||||
|
||||
- Type: Function
|
||||
- Default: `noop`
|
||||
|
||||
A function to be called after a SummaryNumber, rendered as a link, is clicked.
|
||||
|
||||
`SummaryListPlaceholder` (component)
|
||||
====================================
|
||||
|
||||
|
|
|
@ -61,9 +61,11 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
$args['last_order_before'] = $request['last_order_before'];
|
||||
$args['last_order_after'] = $request['last_order_after'];
|
||||
|
||||
$between_params = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||
$normalized = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params );
|
||||
$args = array_merge( $args, $normalized );
|
||||
$between_params_numeric = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||
$normalized_params_numeric = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_numeric, false );
|
||||
$between_params_date = array( 'last_active', 'registered' );
|
||||
$normalized_params_date = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_date, true );
|
||||
$args = array_merge( $args, $normalized_params_numeric, $normalized_params_date );
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
@ -296,14 +298,14 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['order'] = array(
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orderby'] = array(
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date_registered',
|
||||
|
@ -321,7 +323,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['match'] = array(
|
||||
$params['match'] = array(
|
||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'all',
|
||||
|
@ -331,7 +333,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['name'] = array(
|
||||
$params['name'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic customer name.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
|
@ -363,34 +365,44 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['registered_before'] = array(
|
||||
$params['last_active_between'] = array(
|
||||
'description' => __( 'Limit response to objects last active between two given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_date_arg' ),
|
||||
);
|
||||
$params['registered_before'] = array(
|
||||
'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['registered_after'] = array(
|
||||
$params['registered_after'] = array(
|
||||
'description' => __( 'Limit response to objects registered after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orders_count_min'] = array(
|
||||
$params['registered_between'] = array(
|
||||
'description' => __( 'Limit response to objects last active between two given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_date_arg' ),
|
||||
);
|
||||
$params['orders_count_min'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orders_count_max'] = array(
|
||||
$params['orders_count_max'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count less than or equal to given integer.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orders_count_between'] = array(
|
||||
$params['orders_count_between'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count between two given integers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ),
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['total_spend_min'] = array(
|
||||
'description' => __( 'Limit response to objects with a total order spend greater than or equal to given number.', 'wc-admin' ),
|
||||
|
@ -405,7 +417,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
$params['total_spend_between'] = array(
|
||||
'description' => __( 'Limit response to objects with a total order spend between two given numbers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ),
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['avg_order_value_min'] = array(
|
||||
'description' => __( 'Limit response to objects with an average order spend greater than or equal to given number.', 'wc-admin' ),
|
||||
|
@ -420,7 +432,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
$params['avg_order_value_between'] = array(
|
||||
'description' => __( 'Limit response to objects with an average order spend between two given numbers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ),
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['last_order_before'] = array(
|
||||
'description' => __( 'Limit response to objects with last order before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Reports customers stats controller
|
||||
*
|
||||
* Handles requests to the /reports/customers/stats endpoint.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST API Reports customers stats controller class.
|
||||
*
|
||||
* @package WooCommerce/API
|
||||
* @extends WC_REST_Reports_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'reports/customers/stats';
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
$args['registered_before'] = $request['registered_before'];
|
||||
$args['registered_after'] = $request['registered_after'];
|
||||
$args['match'] = $request['match'];
|
||||
$args['name'] = $request['name'];
|
||||
$args['username'] = $request['username'];
|
||||
$args['email'] = $request['email'];
|
||||
$args['country'] = $request['country'];
|
||||
$args['last_active_before'] = $request['last_active_before'];
|
||||
$args['last_active_after'] = $request['last_active_after'];
|
||||
$args['orders_count_min'] = $request['orders_count_min'];
|
||||
$args['orders_count_max'] = $request['orders_count_max'];
|
||||
$args['total_spend_min'] = $request['total_spend_min'];
|
||||
$args['total_spend_max'] = $request['total_spend_max'];
|
||||
$args['avg_order_value_min'] = $request['avg_order_value_min'];
|
||||
$args['avg_order_value_max'] = $request['avg_order_value_max'];
|
||||
$args['last_order_before'] = $request['last_order_before'];
|
||||
$args['last_order_after'] = $request['last_order_after'];
|
||||
|
||||
$between_params_numeric = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||
$normalized_params_numeric = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_numeric, false );
|
||||
$between_params_date = array( 'last_active', 'registered' );
|
||||
$normalized_params_date = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_date, true );
|
||||
$args = array_merge( $args, $normalized_params_numeric, $normalized_params_date );
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
$customers_query = new WC_Admin_Reports_Customers_Stats_Query( $query_args );
|
||||
$report_data = $customers_query->get_data();
|
||||
$out_data = array(
|
||||
'totals' => $report_data,
|
||||
// TODO: is this needed? the single element array tricks the isReportDataEmpty() selector.
|
||||
'intervals' => array( (object) array() ),
|
||||
);
|
||||
|
||||
return rest_ensure_response( $out_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param Array $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_item_for_response( $report, $request ) {
|
||||
$data = $report;
|
||||
|
||||
$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 );
|
||||
|
||||
/**
|
||||
* 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 object $report The original report object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_customers_stats', $response, $report, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
// TODO: should any of these be 'indicator's?
|
||||
$totals = array(
|
||||
'customers_count' => array(
|
||||
'description' => __( 'Number of customers.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'avg_orders_count' => array(
|
||||
'description' => __( 'Average number of orders.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'avg_total_spend' => array(
|
||||
'description' => __( 'Average total spend per customer.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'avg_avg_order_value' => array(
|
||||
'description' => __( 'Average AOV per customer.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
);
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'report_customers_stats',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'totals' => array(
|
||||
'description' => __( 'Totals data.', 'wc-admin' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
'intervals' => array( // TODO: remove this?
|
||||
'description' => __( 'Reports data grouped by intervals.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'interval' => array(
|
||||
'description' => __( 'Type of interval.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||
),
|
||||
'date_start' => array(
|
||||
'description' => __( "The date the report start, in the site's timezone.", 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_start_gmt' => array(
|
||||
'description' => __( 'The date the report start, as GMT.', 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end' => array(
|
||||
'description' => __( "The date the report end, in the site's timezone.", 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end_gmt' => array(
|
||||
'description' => __( 'The date the report end, as GMT.', 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotals' => array(
|
||||
'description' => __( 'Interval subtotals.', 'wc-admin' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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['registered_before'] = array(
|
||||
'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['registered_after'] = array(
|
||||
'description' => __( 'Limit response to objects registered after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['match'] = array(
|
||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'default' => 'all',
|
||||
'enum' => array(
|
||||
'all',
|
||||
'any',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['name'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic customer name.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['username'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic username.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['email'] = array(
|
||||
'description' => __( 'Limit response to objects equal to an email.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['country'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic country.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['last_active_before'] = array(
|
||||
'description' => __( 'Limit response to objects last active before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['last_active_after'] = array(
|
||||
'description' => __( 'Limit response to objects last active after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['last_active_between'] = array(
|
||||
'description' => __( 'Limit response to objects last active between two given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_date_arg' ),
|
||||
);
|
||||
$params['registered_before'] = array(
|
||||
'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['registered_after'] = array(
|
||||
'description' => __( 'Limit response to objects registered after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['registered_between'] = array(
|
||||
'description' => __( 'Limit response to objects last active between two given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_date_arg' ),
|
||||
);
|
||||
$params['orders_count_min'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orders_count_max'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count less than or equal to given integer.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orders_count_between'] = array(
|
||||
'description' => __( 'Limit response to objects with an order count between two given integers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['total_spend_min'] = array(
|
||||
'description' => __( 'Limit response to objects with a total order spend greater than or equal to given number.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['total_spend_max'] = array(
|
||||
'description' => __( 'Limit response to objects with a total order spend less than or equal to given number.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['total_spend_between'] = array(
|
||||
'description' => __( 'Limit response to objects with a total order spend between two given numbers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['avg_order_value_min'] = array(
|
||||
'description' => __( 'Limit response to objects with an average order spend greater than or equal to given number.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['avg_order_value_max'] = array(
|
||||
'description' => __( 'Limit response to objects with an average order spend less than or equal to given number.', 'wc-admin' ),
|
||||
'type' => 'number',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['avg_order_value_between'] = array(
|
||||
'description' => __( 'Limit response to objects with an average order spend between two given numbers.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_numeric_arg' ),
|
||||
);
|
||||
$params['last_order_before'] = array(
|
||||
'description' => __( 'Limit response to objects with last order before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['last_order_after'] = array(
|
||||
'description' => __( 'Limit response to objects with last order after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Setting Options Controller
|
||||
*
|
||||
* Handles requests to /settings/{option}
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Setting Options controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Setting_Options_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Setting_Options_Controller extends WC_REST_Setting_Options_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Taxes Controller
|
||||
*
|
||||
* Handles requests to /taxes/*
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Taxes controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Taxes_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Taxes_Controller extends WC_REST_Taxes_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue