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 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
|
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
|
# 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
|
php wp-cli.phar plugin install gutenberg --activate
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Install WooCommerce
|
# Install WooCommerce
|
||||||
cd "wp-content/plugins/"
|
cd "wp-content/plugins/"
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { Card, EmptyTable, TableCard } from '@woocommerce/components';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportError from 'analytics/components/report-error';
|
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 withSelect from 'wc-api/with-select';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,13 @@ import { createRegistry, RegistryProvider } from '@wordpress/data';
|
||||||
* WooCommerce dependencies
|
* WooCommerce dependencies
|
||||||
*/
|
*/
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import LeaderboardWithSelect, { Leaderboard } from '../';
|
import LeaderboardWithSelect, { Leaderboard } from '../';
|
||||||
import { NAMESPACE } from 'store/constants';
|
import { NAMESPACE } from 'wc-api/constants';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import mockData from '../__mocks__/top-selling-products-mock-data';
|
import mockData from '../__mocks__/top-selling-products-mock-data';
|
||||||
|
|
||||||
// Mock <Table> to avoid tests failing due to it using DOM properties that
|
// Mock <Table> to avoid tests failing due to it using DOM properties that
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Chart } from '@woocommerce/components';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* 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 ReportError from 'analytics/components/report-error';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,14 @@ import PropTypes from 'prop-types';
|
||||||
import { getDateParamsFromQuery } from '@woocommerce/date';
|
import { getDateParamsFromQuery } from '@woocommerce/date';
|
||||||
import { getNewPath } from '@woocommerce/navigation';
|
import { getNewPath } from '@woocommerce/navigation';
|
||||||
import { SummaryList, SummaryListPlaceholder, SummaryNumber } from '@woocommerce/components';
|
import { SummaryList, SummaryListPlaceholder, SummaryNumber } from '@woocommerce/components';
|
||||||
|
import { calculateDelta, formatValue } from '@woocommerce/number';
|
||||||
|
import { formatCurrency } from '@woocommerce/currency';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { getSummaryNumbers } from 'store/reports/utils';
|
import { getSummaryNumbers } from 'wc-api/reports/utils';
|
||||||
import ReportError from 'analytics/components/report-error';
|
import ReportError from 'analytics/components/report-error';
|
||||||
import { calculateDelta, formatValue } from 'lib/number';
|
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,11 +46,16 @@ export class ReportSummary extends Component {
|
||||||
const renderSummaryNumbers = ( { onToggle } ) =>
|
const renderSummaryNumbers = ( { onToggle } ) =>
|
||||||
charts.map( chart => {
|
charts.map( chart => {
|
||||||
const { key, label, type } = chart;
|
const { key, label, type } = chart;
|
||||||
|
const isCurrency = 'currency' === type;
|
||||||
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
||||||
const href = getNewPath( { chart: 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 isSelected = selectedChart.key === key;
|
||||||
const value = formatValue( type, primaryTotals[ key ] );
|
const value = isCurrency
|
||||||
|
? formatCurrency( primaryTotals[ key ] )
|
||||||
|
: formatValue( type, primaryTotals[ key ] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SummaryNumber
|
<SummaryNumber
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { onQueryChange } from '@woocommerce/navigation';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportError from 'analytics/components/report-error';
|
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 withSelect from 'wc-api/with-select';
|
||||||
import { extendTableData } from './utils';
|
import { extendTableData } from './utils';
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default class CategoryBreadcrumbs extends Component {
|
||||||
let parent = category.parent;
|
let parent = category.parent;
|
||||||
while ( parent ) {
|
while ( parent ) {
|
||||||
ancestors.unshift( parent );
|
ancestors.unshift( parent );
|
||||||
parent = categories[ parent ].parent;
|
parent = categories.get( parent ).parent;
|
||||||
}
|
}
|
||||||
return ancestors;
|
return ancestors;
|
||||||
}
|
}
|
||||||
|
@ -30,20 +30,20 @@ export default class CategoryBreadcrumbs extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( ancestorIds.length === 1 ) {
|
if ( ancestorIds.length === 1 ) {
|
||||||
return categories[ first( ancestorIds ) ].name + ' › ';
|
return categories.get( first( ancestorIds ) ).name + ' › ';
|
||||||
}
|
}
|
||||||
if ( ancestorIds.length === 2 ) {
|
if ( ancestorIds.length === 2 ) {
|
||||||
return (
|
return (
|
||||||
categories[ first( ancestorIds ) ].name +
|
categories.get( first( ancestorIds ) ).name +
|
||||||
' › ' +
|
' › ' +
|
||||||
categories[ last( ancestorIds ) ].name +
|
categories.get( last( ancestorIds ) ).name +
|
||||||
' › '
|
' › '
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
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 { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import CategoryBreacrumbs from './breadcrumbs';
|
import CategoryBreacrumbs from './breadcrumbs';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
|
@ -68,11 +68,10 @@ class CategoriesReportTable extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowsContent( categoryStats ) {
|
getRowsContent( categoryStats ) {
|
||||||
const { query } = this.props;
|
|
||||||
return map( categoryStats, categoryStat => {
|
return map( categoryStats, categoryStat => {
|
||||||
const { category_id, items_sold, net_revenue, products_count, orders_count } = categoryStat;
|
const { category_id, items_sold, net_revenue, products_count, orders_count } = categoryStat;
|
||||||
const { categories, query } = this.props;
|
const { categories, query } = this.props;
|
||||||
const category = categories[ category_id ];
|
const category = categories.get( category_id );
|
||||||
const persistedQuery = getPersistedQuery( query );
|
const persistedQuery = getPersistedQuery( query );
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -154,6 +153,7 @@ class CategoriesReportTable extends Component {
|
||||||
getSummary={ this.getSummary }
|
getSummary={ this.getSummary }
|
||||||
itemIdField="category_id"
|
itemIdField="category_id"
|
||||||
query={ query }
|
query={ query }
|
||||||
|
searchBy="categories"
|
||||||
labels={ labels }
|
labels={ labels }
|
||||||
tableQuery={ {
|
tableQuery={ {
|
||||||
orderby: query.orderby || 'items_sold',
|
orderby: query.orderby || 'items_sold',
|
||||||
|
@ -169,14 +169,14 @@ class CategoriesReportTable extends Component {
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSelect( select => {
|
withSelect( select => {
|
||||||
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
|
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||||
const tableQuery = {
|
const tableQuery = {
|
||||||
per_page: -1,
|
per_page: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const categories = getCategories( tableQuery );
|
const categories = getItems( 'categories', tableQuery );
|
||||||
const isError = Boolean( getCategoriesError( tableQuery ) );
|
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
|
||||||
const isRequesting = isGetCategoriesRequesting( tableQuery );
|
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
|
||||||
|
|
||||||
return { categories, isError, isRequesting };
|
return { categories, isError, isRequesting };
|
||||||
} )
|
} )
|
||||||
|
|
|
@ -12,12 +12,13 @@ import { map } from 'lodash';
|
||||||
import { Date, Link } from '@woocommerce/components';
|
import { Date, Link } from '@woocommerce/components';
|
||||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
|
|
||||||
export default class CouponsReportTable extends Component {
|
export default class CouponsReportTable extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -67,22 +68,29 @@ export default class CouponsReportTable extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowsContent( coupons ) {
|
getRowsContent( coupons ) {
|
||||||
|
const { query } = this.props;
|
||||||
|
const persistedQuery = getPersistedQuery( query );
|
||||||
|
|
||||||
return map( coupons, coupon => {
|
return map( coupons, coupon => {
|
||||||
const { amount, coupon_id, extended_info, orders_count } = coupon;
|
const { amount, coupon_id, extended_info, orders_count } = coupon;
|
||||||
const { code, date_created, date_expires, discount_type } = extended_info;
|
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 = (
|
const couponLink = (
|
||||||
<Link href="" type="wc-admin">
|
<Link href={ couponUrl } type="wc-admin">
|
||||||
{ code }
|
{ code }
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ordersUrl = getNewPath( persistedQuery, '/analytics/orders', {
|
||||||
|
filter: 'advanced',
|
||||||
|
coupon_includes: coupon_id,
|
||||||
|
} );
|
||||||
const ordersLink = (
|
const ordersLink = (
|
||||||
<Link
|
<Link href={ ordersUrl } type="wc-admin">
|
||||||
href={ '/analytics/orders?filter=advanced&code_includes=' + coupon_id }
|
|
||||||
type="wc-admin"
|
|
||||||
>
|
|
||||||
{ numberFormat( orders_count ) }
|
{ numberFormat( orders_count ) }
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -161,9 +169,10 @@ export default class CouponsReportTable extends Component {
|
||||||
getSummary={ this.getSummary }
|
getSummary={ this.getSummary }
|
||||||
itemIdField="coupon_id"
|
itemIdField="coupon_id"
|
||||||
query={ query }
|
query={ query }
|
||||||
|
searchBy="coupons"
|
||||||
tableQuery={ {
|
tableQuery={ {
|
||||||
orderby: query.orderby || 'coupon_id',
|
orderby: query.orderby || 'orders_count',
|
||||||
order: query.order || 'asc',
|
order: query.order || 'desc',
|
||||||
extended_info: true,
|
extended_info: true,
|
||||||
} }
|
} }
|
||||||
title={ __( 'Coupons', 'wc-admin' ) }
|
title={ __( 'Coupons', 'wc-admin' ) }
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { decodeEntities } from '@wordpress/html-entities';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { getCustomerLabels, getRequestByIdString } from 'lib/async-requests';
|
import { getCustomerLabels, getRequestByIdString } from 'lib/async-requests';
|
||||||
import { NAMESPACE } from 'store/constants';
|
import { NAMESPACE } from 'wc-api/constants';
|
||||||
|
|
||||||
export const filters = [
|
export const filters = [
|
||||||
{
|
{
|
||||||
|
@ -163,7 +163,7 @@ export const advancedFilters = {
|
||||||
} ) ),
|
} ) ),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
order_count: {
|
orders_count: {
|
||||||
labels: {
|
labels: {
|
||||||
add: __( 'No. of Orders', 'wc-admin' ),
|
add: __( 'No. of Orders', 'wc-admin' ),
|
||||||
remove: __( 'Remove order filter', 'wc-admin' ),
|
remove: __( 'Remove order filter', 'wc-admin' ),
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default class CustomersReport extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { query, path } = this.props;
|
const { query, path } = this.props;
|
||||||
const tableQuery = {
|
const tableQuery = {
|
||||||
orderby: 'date_registered',
|
orderby: 'date_last_active',
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { Tooltip } from '@wordpress/components';
|
||||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { Date, Link } from '@woocommerce/components';
|
import { Date, Link } from '@woocommerce/components';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
|
|
||||||
export default class CustomersReportTable extends Component {
|
export default class CustomersReportTable extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -25,6 +25,7 @@ export default class CustomersReportTable extends Component {
|
||||||
|
|
||||||
this.getHeadersContent = this.getHeadersContent.bind( this );
|
this.getHeadersContent = this.getHeadersContent.bind( this );
|
||||||
this.getRowsContent = this.getRowsContent.bind( this );
|
this.getRowsContent = this.getRowsContent.bind( this );
|
||||||
|
this.getSummary = this.getSummary.bind( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeadersContent() {
|
getHeadersContent() {
|
||||||
|
@ -41,10 +42,15 @@ export default class CustomersReportTable extends Component {
|
||||||
key: 'username',
|
key: 'username',
|
||||||
hiddenByDefault: true,
|
hiddenByDefault: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: __( 'Last Active', 'wc-admin' ),
|
||||||
|
key: 'date_last_active',
|
||||||
|
defaultSort: true,
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: __( 'Sign Up', 'wc-admin' ),
|
label: __( 'Sign Up', 'wc-admin' ),
|
||||||
key: 'date_registered',
|
key: 'date_registered',
|
||||||
defaultSort: true,
|
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -58,7 +64,7 @@ export default class CustomersReportTable extends Component {
|
||||||
isNumeric: true,
|
isNumeric: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __( 'Lifetime Spend', 'wc-admin' ),
|
label: __( 'Total Spend', 'wc-admin' ),
|
||||||
key: 'total_spend',
|
key: 'total_spend',
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isNumeric: true,
|
isNumeric: true,
|
||||||
|
@ -69,11 +75,6 @@ export default class CustomersReportTable extends Component {
|
||||||
key: 'avg_order_value',
|
key: 'avg_order_value',
|
||||||
isNumeric: true,
|
isNumeric: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: __( 'Last Active', 'wc-admin' ),
|
|
||||||
key: 'date_last_active',
|
|
||||||
isSortable: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: __( 'Country', 'wc-admin' ),
|
label: __( 'Country', 'wc-admin' ),
|
||||||
key: 'country',
|
key: 'country',
|
||||||
|
@ -147,6 +148,12 @@ export default class CustomersReportTable extends Component {
|
||||||
display: username,
|
display: username,
|
||||||
value: username,
|
value: username,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: date_last_active && (
|
||||||
|
<Date date={ date_last_active } visibleFormat={ defaultTableDateFormat } />
|
||||||
|
),
|
||||||
|
value: date_last_active,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
display: dateRegistered,
|
display: dateRegistered,
|
||||||
value: date_registered,
|
value: date_registered,
|
||||||
|
@ -167,10 +174,6 @@ export default class CustomersReportTable extends Component {
|
||||||
display: formatCurrency( avg_order_value ),
|
display: formatCurrency( avg_order_value ),
|
||||||
value: getCurrencyFormatDecimal( avg_order_value ),
|
value: getCurrencyFormatDecimal( avg_order_value ),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
display: <Date date={ date_last_active } visibleFormat={ defaultTableDateFormat } />,
|
|
||||||
value: date_last_active,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
display: countryDisplay,
|
display: countryDisplay,
|
||||||
value: country,
|
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() {
|
render() {
|
||||||
const { query } = this.props;
|
const { query } = this.props;
|
||||||
|
|
||||||
|
@ -195,11 +222,11 @@ export default class CustomersReportTable extends Component {
|
||||||
endpoint="customers"
|
endpoint="customers"
|
||||||
getHeadersContent={ this.getHeadersContent }
|
getHeadersContent={ this.getHeadersContent }
|
||||||
getRowsContent={ this.getRowsContent }
|
getRowsContent={ this.getRowsContent }
|
||||||
|
getSummary={ this.getSummary }
|
||||||
itemIdField="id"
|
itemIdField="id"
|
||||||
query={ query }
|
query={ query }
|
||||||
labels={ { placeholder: __( 'Search by customer name', 'wc-admin' ) } }
|
labels={ { placeholder: __( 'Search by customer name', 'wc-admin' ) } }
|
||||||
searchBy="customers"
|
searchBy="customers"
|
||||||
searchParam="name_includes"
|
|
||||||
title={ __( 'Customers', 'wc-admin' ) }
|
title={ __( 'Customers', 'wc-admin' ) }
|
||||||
columnPrefsKey="customers_report_columns"
|
columnPrefsKey="customers_report_columns"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,12 +13,12 @@ import moment from 'moment';
|
||||||
import { defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
import { defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
||||||
import { Date, Link } from '@woocommerce/components';
|
import { Date, Link } from '@woocommerce/components';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
|
|
||||||
export default class CouponsReportTable extends Component {
|
export default class CouponsReportTable extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { applyFilters } from '@wordpress/hooks';
|
import { applyFilters } from '@wordpress/hooks';
|
||||||
import { Component, Fragment } from '@wordpress/element';
|
import { Component, Fragment } from '@wordpress/element';
|
||||||
|
import { compose } from '@wordpress/compose';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { find } from 'lodash';
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ import { find } from 'lodash';
|
||||||
* WooCommerce dependencies
|
* WooCommerce dependencies
|
||||||
*/
|
*/
|
||||||
import { useFilters } from '@woocommerce/components';
|
import { useFilters } from '@woocommerce/components';
|
||||||
|
import { getQuery } from '@woocommerce/navigation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -27,6 +29,8 @@ import TaxesReport from './taxes';
|
||||||
import DownloadsReport from './downloads';
|
import DownloadsReport from './downloads';
|
||||||
import StockReport from './stock';
|
import StockReport from './stock';
|
||||||
import CustomersReport from './customers';
|
import CustomersReport from './customers';
|
||||||
|
import { searchItemsByString } from 'wc-api/items/utils';
|
||||||
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
const REPORTS_FILTER = 'woocommerce-reports-list';
|
const REPORTS_FILTER = 'woocommerce-reports-list';
|
||||||
|
|
||||||
|
@ -131,4 +135,27 @@ Report.propTypes = {
|
||||||
params: PropTypes.object.isRequired,
|
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 { Date, Link, OrderStatus, ViewMoreList } from '@woocommerce/components';
|
||||||
import { defaultTableDateFormat } from '@woocommerce/date';
|
import { defaultTableDateFormat } from '@woocommerce/date';
|
||||||
import { formatCurrency } from '@woocommerce/currency';
|
import { formatCurrency } from '@woocommerce/currency';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
|
@ -24,7 +24,7 @@ import VariationsReportTable from './table-variations';
|
||||||
export default class ProductsReport extends Component {
|
export default class ProductsReport extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { path, query } = this.props;
|
const { path, query } = this.props;
|
||||||
const isProductDetailsView = query.products && 1 === query.products.split( ',' ).length;
|
const isProductDetailsView = query.filter === 'single_product';
|
||||||
|
|
||||||
const itemsLabel = isProductDetailsView
|
const itemsLabel = isProductDetailsView
|
||||||
? __( '%s variations', 'wc-admin' )
|
? __( '%s variations', 'wc-admin' )
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { map, get } from 'lodash';
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import { isLowStock } from './utils';
|
import { isLowStock } from './utils';
|
||||||
|
|
||||||
export default class VariationsReportTable extends Component {
|
export default class VariationsReportTable extends Component {
|
||||||
|
@ -36,6 +36,12 @@ export default class VariationsReportTable extends Component {
|
||||||
required: true,
|
required: true,
|
||||||
isLeftAligned: true,
|
isLeftAligned: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: __( 'SKU', 'wc-admin' ),
|
||||||
|
key: 'sku',
|
||||||
|
hiddenByDefault: true,
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: __( 'Items Sold', 'wc-admin' ),
|
label: __( 'Items Sold', 'wc-admin' ),
|
||||||
key: 'items_sold',
|
key: 'items_sold',
|
||||||
|
@ -77,7 +83,7 @@ export default class VariationsReportTable extends Component {
|
||||||
|
|
||||||
return map( data, row => {
|
return map( data, row => {
|
||||||
const { items_sold, net_revenue, orders_count, extended_info, product_id } = 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 name = get( row, [ 'extended_info', 'name' ], '' ).replace( ' - ', ' / ' );
|
||||||
const ordersLink = getNewPath( persistedQuery, 'orders', {
|
const ordersLink = getNewPath( persistedQuery, 'orders', {
|
||||||
filter: 'advanced',
|
filter: 'advanced',
|
||||||
|
@ -94,6 +100,10 @@ export default class VariationsReportTable extends Component {
|
||||||
),
|
),
|
||||||
value: name,
|
value: name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: sku,
|
||||||
|
value: sku,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
display: items_sold,
|
display: items_sold,
|
||||||
value: items_sold,
|
value: items_sold,
|
||||||
|
@ -172,6 +182,7 @@ export default class VariationsReportTable extends Component {
|
||||||
labels={ labels }
|
labels={ labels }
|
||||||
query={ query }
|
query={ query }
|
||||||
getSummary={ this.getSummary }
|
getSummary={ this.getSummary }
|
||||||
|
searchBy="variations"
|
||||||
tableQuery={ {
|
tableQuery={ {
|
||||||
orderby: query.orderby || 'items_sold',
|
orderby: query.orderby || 'items_sold',
|
||||||
order: query.order || 'desc',
|
order: query.order || 'desc',
|
||||||
|
|
|
@ -13,13 +13,13 @@ import { map } from 'lodash';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
import { Link, Tag } from '@woocommerce/components';
|
import { Link, Tag } from '@woocommerce/components';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import CategoryBreacrumbs from '../categories/breadcrumbs';
|
import CategoryBreacrumbs from '../categories/breadcrumbs';
|
||||||
import { isLowStock } from './utils';
|
import { isLowStock } from './utils';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
@ -119,11 +119,11 @@ class ProductsReportTable extends Component {
|
||||||
filter: 'single_product',
|
filter: 'single_product',
|
||||||
products: product_id,
|
products: product_id,
|
||||||
} );
|
} );
|
||||||
const categories = this.props.categories;
|
const { categories } = this.props;
|
||||||
|
|
||||||
const productCategories =
|
const productCategories =
|
||||||
( category_ids &&
|
( category_ids &&
|
||||||
category_ids.map( category_id => categories[ category_id ] ).filter( Boolean ) ) ||
|
category_ids.map( category_id => categories.get( category_id ) ).filter( Boolean ) ) ||
|
||||||
[];
|
[];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -245,6 +245,7 @@ class ProductsReportTable extends Component {
|
||||||
itemIdField="product_id"
|
itemIdField="product_id"
|
||||||
labels={ labels }
|
labels={ labels }
|
||||||
query={ query }
|
query={ query }
|
||||||
|
searchBy="products"
|
||||||
tableQuery={ {
|
tableQuery={ {
|
||||||
orderby: query.orderby || 'items_sold',
|
orderby: query.orderby || 'items_sold',
|
||||||
order: query.order || 'desc',
|
order: query.order || 'desc',
|
||||||
|
@ -259,14 +260,14 @@ class ProductsReportTable extends Component {
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSelect( select => {
|
withSelect( select => {
|
||||||
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
|
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||||
const tableQuery = {
|
const tableQuery = {
|
||||||
per_page: -1,
|
per_page: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const categories = getCategories( tableQuery );
|
const categories = getItems( 'categories', tableQuery );
|
||||||
const isError = Boolean( getCategoriesError( tableQuery ) );
|
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
|
||||||
const isRequesting = isGetCategoriesRequesting( tableQuery );
|
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
|
||||||
|
|
||||||
return { categories, isError, isRequesting };
|
return { categories, isError, isRequesting };
|
||||||
} )
|
} )
|
||||||
|
|
|
@ -14,12 +14,12 @@ import { get } from 'lodash';
|
||||||
import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date';
|
||||||
import { Date, Link } from '@woocommerce/components';
|
import { Date, Link } from '@woocommerce/components';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { QUERY_DEFAULTS } from 'store/constants';
|
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,12 @@ import { Component } from '@wordpress/element';
|
||||||
*/
|
*/
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
|
|
||||||
export default class StockReportTable extends Component {
|
export default class StockReportTable extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n';
|
||||||
*/
|
*/
|
||||||
import { getRequestByIdString } from 'lib/async-requests';
|
import { getRequestByIdString } from 'lib/async-requests';
|
||||||
import { getTaxCode } from './utils';
|
import { getTaxCode } from './utils';
|
||||||
import { NAMESPACE } from 'store/constants';
|
import { NAMESPACE } from 'wc-api/constants';
|
||||||
|
|
||||||
export const charts = [
|
export const charts = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { map } from 'lodash';
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getTaxCode } from './utils';
|
import { getTaxCode } from './utils';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import ReportTable from 'analytics/components/report-table';
|
import ReportTable from 'analytics/components/report-table';
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
|
|
||||||
export default class TaxesReportTable extends Component {
|
export default class TaxesReportTable extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -150,6 +150,7 @@ export default class TaxesReportTable extends Component {
|
||||||
getSummary={ this.getSummary }
|
getSummary={ this.getSummary }
|
||||||
itemIdField="tax_rate_id"
|
itemIdField="tax_rate_id"
|
||||||
query={ query }
|
query={ query }
|
||||||
|
searchBy="taxes"
|
||||||
tableQuery={ {
|
tableQuery={ {
|
||||||
orderby: query.orderby || 'tax_rate_id',
|
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 withSelect from 'wc-api/with-select';
|
||||||
import TopSellingCategories from './top-selling-categories';
|
import TopSellingCategories from './top-selling-categories';
|
||||||
import TopSellingProducts from './top-selling-products';
|
import TopSellingProducts from './top-selling-products';
|
||||||
|
import TopCoupons from './top-coupons';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Leaderboards extends Component {
|
class Leaderboards extends Component {
|
||||||
|
@ -134,10 +135,12 @@ class Leaderboards extends Component {
|
||||||
{ ! hiddenLeaderboardKeys.includes( 'top-products' ) && (
|
{ ! hiddenLeaderboardKeys.includes( 'top-products' ) && (
|
||||||
<TopSellingProducts query={ query } totalRows={ rowsPerTable } />
|
<TopSellingProducts query={ query } totalRows={ rowsPerTable } />
|
||||||
) }
|
) }
|
||||||
|
|
||||||
{ ! hiddenLeaderboardKeys.includes( 'top-categories' ) && (
|
{ ! hiddenLeaderboardKeys.includes( 'top-categories' ) && (
|
||||||
<TopSellingCategories query={ query } totalRows={ rowsPerTable } />
|
<TopSellingCategories query={ query } totalRows={ rowsPerTable } />
|
||||||
) }
|
) }
|
||||||
|
{ ! hiddenLeaderboardKeys.includes( 'top-coupons' ) && (
|
||||||
|
<TopCoupons query={ query } totalRows={ rowsPerTable } />
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</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 { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import Leaderboard from 'analytics/components/leaderboard';
|
import Leaderboard from 'analytics/components/leaderboard';
|
||||||
|
|
||||||
export class TopSellingCategories extends Component {
|
export class TopSellingCategories extends Component {
|
||||||
|
|
|
@ -12,11 +12,11 @@ import { get, map } from 'lodash';
|
||||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
import { Link } from '@woocommerce/components';
|
import { Link } from '@woocommerce/components';
|
||||||
|
import { numberFormat } from '@woocommerce/number';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { numberFormat } from 'lib/number';
|
|
||||||
import Leaderboard from 'analytics/components/leaderboard';
|
import Leaderboard from 'analytics/components/leaderboard';
|
||||||
|
|
||||||
export class TopSellingProducts extends Component {
|
export class TopSellingProducts extends Component {
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { find } from 'lodash';
|
||||||
*/
|
*/
|
||||||
import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@woocommerce/date';
|
import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@woocommerce/date';
|
||||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||||
|
import { calculateDelta, formatValue } from '@woocommerce/number';
|
||||||
|
import { formatCurrency } from '@woocommerce/currency';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -31,7 +33,6 @@ import {
|
||||||
} from '@woocommerce/components';
|
} from '@woocommerce/components';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
import { calculateDelta, formatValue } from 'lib/number';
|
|
||||||
|
|
||||||
class StorePerformance extends Component {
|
class StorePerformance extends Component {
|
||||||
constructor( props ) {
|
constructor( props ) {
|
||||||
|
@ -128,9 +129,15 @@ class StorePerformance extends Component {
|
||||||
'';
|
'';
|
||||||
const reportUrl =
|
const reportUrl =
|
||||||
( href && getNewPath( persistedQuery, href, { chart: primaryItem.chart } ) ) || '';
|
( href && getNewPath( persistedQuery, href, { chart: primaryItem.chart } ) ) || '';
|
||||||
|
const isCurrency = 'currency' === primaryItem.format;
|
||||||
|
|
||||||
const delta = calculateDelta( primaryItem.value, secondaryItem.value );
|
const delta = calculateDelta( primaryItem.value, secondaryItem.value );
|
||||||
const primaryValue = formatValue( primaryItem.format, primaryItem.value );
|
const primaryValue = isCurrency
|
||||||
const secondaryValue = formatValue( secondaryItem.format, secondaryItem.value );
|
? formatCurrency( primaryItem.value )
|
||||||
|
: formatValue( primaryItem.format, primaryItem.value );
|
||||||
|
const secondaryValue = isCurrency
|
||||||
|
? formatCurrency( secondaryItem.value )
|
||||||
|
: formatValue( secondaryItem.format, secondaryItem.value );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SummaryNumber
|
<SummaryNumber
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
||||||
*/
|
*/
|
||||||
import './stylesheets/_embedded.scss';
|
import './stylesheets/_embedded.scss';
|
||||||
import { EmbedLayout } from './layout';
|
import { EmbedLayout } from './layout';
|
||||||
import 'store';
|
|
||||||
import 'wc-api/wp-data-store';
|
import 'wc-api/wp-data-store';
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||||
import ActivityHeader from '../activity-header';
|
import ActivityHeader from '../activity-header';
|
||||||
import { EmptyContent, Section } from '@woocommerce/components';
|
import { EmptyContent, Section } from '@woocommerce/components';
|
||||||
import sanitizeHTML from 'lib/sanitize-html';
|
import sanitizeHTML from 'lib/sanitize-html';
|
||||||
import { QUERY_DEFAULTS } from 'store/constants';
|
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||||
|
|
||||||
class InboxPanel extends Component {
|
class InboxPanel extends Component {
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||||
import ActivityHeader from '../activity-header';
|
import ActivityHeader from '../activity-header';
|
||||||
import ActivityOutboundLink from '../activity-outbound-link';
|
import ActivityOutboundLink from '../activity-outbound-link';
|
||||||
import { getOrderRefundTotal } from 'lib/order-values';
|
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';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
function OrdersPanel( { orders, isRequesting, isError } ) {
|
function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||||
|
@ -85,41 +85,26 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||||
orderLink: <Link href={ 'post.php?action=edit&post=' + order.id } type="wp-admin" />,
|
orderLink: <Link href={ 'post.php?action=edit&post=' + order.id } type="wp-admin" />,
|
||||||
// @TODO: Hook up customer name link
|
// @TODO: Hook up customer name link
|
||||||
customerLink: <Link href={ '#' } type="wp-admin" />,
|
customerLink: <Link href={ '#' } type="wp-admin" />,
|
||||||
destinationFlag: <Flag order={ order } round={ false } height={ 9 } width={ 12 } />,
|
destinationFlag: <Flag order={ order } round={ false } />,
|
||||||
},
|
},
|
||||||
} ) }
|
} ) }
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const cards = [];
|
||||||
<Fragment>
|
orders.forEach( ( order, id ) => {
|
||||||
<ActivityHeader title={ __( 'Orders', 'wc-admin' ) } menu={ menu } />
|
|
||||||
<Section>
|
|
||||||
{ isRequesting ? (
|
|
||||||
<ActivityCardPlaceholder
|
|
||||||
className="woocommerce-order-activity-card"
|
|
||||||
hasAction
|
|
||||||
hasDate
|
|
||||||
lines={ 2 }
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
{ orders.map( ( order, i ) => {
|
|
||||||
// We want the billing address, but shipping can be used as a fallback.
|
// We want the billing address, but shipping can be used as a fallback.
|
||||||
const address = { ...order.shipping, ...order.billing };
|
const address = { ...order.shipping, ...order.billing };
|
||||||
const productsCount = order.line_items.reduce(
|
const productsCount = order.line_items.reduce( ( total, line ) => total + line.quantity, 0 );
|
||||||
( total, line ) => total + line.quantity,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
const total = order.total;
|
const total = order.total;
|
||||||
const refundValue = getOrderRefundTotal( order );
|
const refundValue = getOrderRefundTotal( order );
|
||||||
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
|
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
|
||||||
|
|
||||||
return (
|
cards.push(
|
||||||
<ActivityCard
|
<ActivityCard
|
||||||
key={ i }
|
key={ id }
|
||||||
className="woocommerce-order-activity-card"
|
className="woocommerce-order-activity-card"
|
||||||
title={ orderCardTitle( order, address ) }
|
title={ orderCardTitle( order, address ) }
|
||||||
date={ order.date_created }
|
date={ order.date_created }
|
||||||
|
@ -142,10 +127,7 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<Button
|
<Button isDefault href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }>
|
||||||
isDefault
|
|
||||||
href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }
|
|
||||||
>
|
|
||||||
{ __( 'Begin fulfillment' ) }
|
{ __( 'Begin fulfillment' ) }
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
@ -153,7 +135,22 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||||
<OrderStatus order={ order } />
|
<OrderStatus order={ order } />
|
||||||
</ActivityCard>
|
</ActivityCard>
|
||||||
);
|
);
|
||||||
} ) }
|
} );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<ActivityHeader title={ __( 'Orders', 'wc-admin' ) } menu={ menu } />
|
||||||
|
<Section>
|
||||||
|
{ isRequesting ? (
|
||||||
|
<ActivityCardPlaceholder
|
||||||
|
className="woocommerce-order-activity-card"
|
||||||
|
hasAction
|
||||||
|
hasDate
|
||||||
|
lines={ 2 }
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
{ cards }
|
||||||
<ActivityOutboundLink href={ 'edit.php?post_type=shop_order' }>
|
<ActivityOutboundLink href={ 'edit.php?post_type=shop_order' }>
|
||||||
{ __( 'Manage all orders' ) }
|
{ __( 'Manage all orders' ) }
|
||||||
</ActivityOutboundLink>
|
</ActivityOutboundLink>
|
||||||
|
@ -165,29 +162,29 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
OrdersPanel.propTypes = {
|
OrdersPanel.propTypes = {
|
||||||
orders: PropTypes.array.isRequired,
|
orders: PropTypes.instanceOf( Map ).isRequired,
|
||||||
isError: PropTypes.bool,
|
isError: PropTypes.bool,
|
||||||
isRequesting: PropTypes.bool,
|
isRequesting: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
OrdersPanel.defaultProps = {
|
OrdersPanel.defaultProps = {
|
||||||
orders: [],
|
orders: new Map(),
|
||||||
isError: false,
|
isError: false,
|
||||||
isRequesting: false,
|
isRequesting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSelect( select => {
|
withSelect( select => {
|
||||||
const { getOrders, getOrdersError, isGetOrdersRequesting } = select( 'wc-api' );
|
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||||
const ordersQuery = {
|
const ordersQuery = {
|
||||||
page: 1,
|
page: 1,
|
||||||
per_page: QUERY_DEFAULTS.pageSize,
|
per_page: QUERY_DEFAULTS.pageSize,
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
};
|
};
|
||||||
|
|
||||||
const orders = getOrders( ordersQuery );
|
const orders = getItems( 'orders', ordersQuery );
|
||||||
const isError = Boolean( getOrdersError( ordersQuery ) );
|
const isError = Boolean( getItemsError( 'orders', ordersQuery ) );
|
||||||
const isRequesting = isGetOrdersRequesting( ordersQuery );
|
const isRequesting = isGetItemsRequesting( 'orders', ordersQuery );
|
||||||
|
|
||||||
return { orders, isError, isRequesting };
|
return { orders, isError, isRequesting };
|
||||||
} )
|
} )
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
*/
|
*/
|
||||||
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||||
import ActivityHeader from '../activity-header';
|
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 sanitizeHTML from 'lib/sanitize-html';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
||||||
*/
|
*/
|
||||||
import './stylesheets/_index.scss';
|
import './stylesheets/_index.scss';
|
||||||
import { PageLayout } from './layout';
|
import { PageLayout } from './layout';
|
||||||
import 'store';
|
|
||||||
import 'wc-api/wp-data-store';
|
import 'wc-api/wp-data-store';
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { getPersistedQuery, stringifyQuery } from '@woocommerce/navigation';
|
||||||
*/
|
*/
|
||||||
import Analytics from 'analytics';
|
import Analytics from 'analytics';
|
||||||
import AnalyticsReport from 'analytics/report';
|
import AnalyticsReport from 'analytics/report';
|
||||||
|
import AnalyticsSettings from 'analytics/settings';
|
||||||
import Dashboard from 'dashboard';
|
import Dashboard from 'dashboard';
|
||||||
import DevDocs from 'devdocs';
|
import DevDocs from 'devdocs';
|
||||||
|
|
||||||
|
@ -33,6 +34,12 @@ const getPages = () => {
|
||||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
container: AnalyticsSettings,
|
||||||
|
path: '/analytics/settings',
|
||||||
|
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||||
|
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
container: AnalyticsReport,
|
container: AnalyticsReport,
|
||||||
path: '/analytics/:report',
|
path: '/analytics/:report',
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { getIdsFromQuery, stringifyQuery } from '@woocommerce/navigation';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* 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
|
* 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';
|
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 = {
|
export const DEFAULT_REQUIREMENT = {
|
||||||
timeout: 1 * MINUTE,
|
timeout: 1 * MINUTE,
|
||||||
freshness: 5 * 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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { getResourceIdentifier, getResourcePrefix } from '../../utils';
|
import { getResourceIdentifier, getResourcePrefix } from 'wc-api/utils';
|
||||||
import { NAMESPACE } from '../../constants';
|
import { NAMESPACE, SWAGGERNAMESPACE } from 'wc-api/constants';
|
||||||
import { SWAGGERNAMESPACE } from 'store/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.
|
// TODO: Remove once swagger endpoints are phased out.
|
||||||
const swaggerEndpoints = [ 'categories' ];
|
const swaggerEndpoints = [ 'categories' ];
|
||||||
|
|
||||||
|
@ -28,6 +35,7 @@ const typeEndpointMap = {
|
||||||
'report-stats-query-downloads': 'downloads',
|
'report-stats-query-downloads': 'downloads',
|
||||||
'report-stats-query-coupons': 'coupons',
|
'report-stats-query-coupons': 'coupons',
|
||||||
'report-stats-query-taxes': 'taxes',
|
'report-stats-query-taxes': 'taxes',
|
||||||
|
'report-stats-query-customers': 'customers',
|
||||||
};
|
};
|
||||||
|
|
||||||
function read( resourceNames, fetch = apiFetch ) {
|
function read( resourceNames, fetch = apiFetch ) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { formatCurrency } from '@woocommerce/currency';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* 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 categoriesConfig from 'analytics/report/categories/config';
|
||||||
import * as couponsConfig from 'analytics/report/coupons/config';
|
import * as couponsConfig from 'analytics/report/coupons/config';
|
||||||
import * as customersConfig from 'analytics/report/customers/config';
|
import * as customersConfig from 'analytics/report/customers/config';
|
||||||
|
@ -37,6 +37,12 @@ const reportConfigs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFilterQuery( endpoint, query ) {
|
export function getFilterQuery( endpoint, query ) {
|
||||||
|
if ( query.search ) {
|
||||||
|
return {
|
||||||
|
[ endpoint ]: query[ endpoint ],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if ( reportConfigs[ endpoint ] ) {
|
if ( reportConfigs[ endpoint ] ) {
|
||||||
const { filters = [], advancedFilters = {} } = reportConfigs[ endpoint ];
|
const { filters = [], advancedFilters = {} } = reportConfigs[ endpoint ];
|
||||||
return filters
|
return filters
|
||||||
|
@ -335,7 +341,7 @@ export function getReportTableQuery( endpoint, urlQuery, query ) {
|
||||||
*
|
*
|
||||||
* @param {String} endpoint Report API Endpoint
|
* @param {String} endpoint Report API Endpoint
|
||||||
* @param {Object} urlQuery Query parameters in the url
|
* @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
|
* @param {Object} query Query parameters specific for that endpoint
|
||||||
* @return {Object} Object Table data response
|
* @return {Object} Object Table data response
|
||||||
*/
|
*/
|
|
@ -4,8 +4,10 @@
|
||||||
*/
|
*/
|
||||||
import operations from './operations';
|
import operations from './operations';
|
||||||
import selectors from './selectors';
|
import selectors from './selectors';
|
||||||
|
import mutations from './mutations';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
operations,
|
operations,
|
||||||
selectors,
|
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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import categories from './categories';
|
import items from './items';
|
||||||
import notes from './notes';
|
import notes from './notes';
|
||||||
import orders from './orders';
|
|
||||||
import reportItems from './reports/items';
|
import reportItems from './reports/items';
|
||||||
import reportStats from './reports/stats';
|
import reportStats from './reports/stats';
|
||||||
import reviews from './reviews';
|
import reviews from './reviews';
|
||||||
|
import settings from './settings';
|
||||||
import user from './user';
|
import user from './user';
|
||||||
|
|
||||||
function createWcApiSpec() {
|
function createWcApiSpec() {
|
||||||
return {
|
return {
|
||||||
mutations: {
|
mutations: {
|
||||||
|
...settings.mutations,
|
||||||
...user.mutations,
|
...user.mutations,
|
||||||
},
|
},
|
||||||
selectors: {
|
selectors: {
|
||||||
...categories.selectors,
|
...items.selectors,
|
||||||
...notes.selectors,
|
...notes.selectors,
|
||||||
...orders.selectors,
|
|
||||||
...reportItems.selectors,
|
...reportItems.selectors,
|
||||||
...reportStats.selectors,
|
...reportStats.selectors,
|
||||||
...reviews.selectors,
|
...reviews.selectors,
|
||||||
|
...settings.selectors,
|
||||||
...user.selectors,
|
...user.selectors,
|
||||||
},
|
},
|
||||||
operations: {
|
operations: {
|
||||||
read( resourceNames ) {
|
read( resourceNames ) {
|
||||||
return [
|
return [
|
||||||
...categories.operations.read( resourceNames ),
|
...items.operations.read( resourceNames ),
|
||||||
...notes.operations.read( resourceNames ),
|
...notes.operations.read( resourceNames ),
|
||||||
...orders.operations.read( resourceNames ),
|
|
||||||
...reportItems.operations.read( resourceNames ),
|
...reportItems.operations.read( resourceNames ),
|
||||||
...reportStats.operations.read( resourceNames ),
|
...reportStats.operations.read( resourceNames ),
|
||||||
...reviews.operations.read( resourceNames ),
|
...reviews.operations.read( resourceNames ),
|
||||||
|
...settings.operations.read( resourceNames ),
|
||||||
...user.operations.read( resourceNames ),
|
...user.operations.read( resourceNames ),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
update( resourceNames, data ) {
|
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)
|
`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
|
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.
|
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`
|
### `className`
|
||||||
|
|
||||||
- Type: String
|
- Type: String
|
||||||
|
@ -50,3 +29,10 @@ Flag image width.
|
||||||
|
|
||||||
Additional CSS classes.
|
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`
|
### `children`
|
||||||
|
|
||||||
- **Required**
|
- **Required**
|
||||||
- Type: ReactNode
|
- Type: Function
|
||||||
- Default: null
|
- Default: null
|
||||||
|
|
||||||
A list of `<SummaryNumber />`s
|
A function returning a list of `<SummaryNumber />`s
|
||||||
|
|
||||||
### `label`
|
### `label`
|
||||||
|
|
||||||
- Type: String
|
- 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)
|
`SummaryNumber` (component)
|
||||||
===========================
|
===========================
|
||||||
|
@ -46,7 +46,7 @@ If omitted, no change value will display.
|
||||||
### `href`
|
### `href`
|
||||||
|
|
||||||
- Type: String
|
- Type: String
|
||||||
- Default: `'/analytics'`
|
- Default: `''`
|
||||||
|
|
||||||
An internal link to the report focused on this number.
|
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.
|
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)
|
`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_before'] = $request['last_order_before'];
|
||||||
$args['last_order_after'] = $request['last_order_after'];
|
$args['last_order_after'] = $request['last_order_after'];
|
||||||
|
|
||||||
$between_params = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
$between_params_numeric = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||||
$normalized = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params );
|
$normalized_params_numeric = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_numeric, false );
|
||||||
$args = array_merge( $args, $normalized );
|
$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;
|
return $args;
|
||||||
}
|
}
|
||||||
|
@ -363,6 +365,11 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
||||||
'format' => 'date-time',
|
'format' => 'date-time',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'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(
|
$params['registered_before'] = array(
|
||||||
'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
@ -375,6 +382,11 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
||||||
'format' => 'date-time',
|
'format' => 'date-time',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'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(
|
$params['orders_count_min'] = array(
|
||||||
'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'wc-admin' ),
|
'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'wc-admin' ),
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
@ -390,7 +402,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
||||||
$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' ),
|
'description' => __( 'Limit response to objects with an order count between two given integers.', 'wc-admin' ),
|
||||||
'type' => 'array',
|
'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(
|
$params['total_spend_min'] = array(
|
||||||
'description' => __( 'Limit response to objects with a total order spend greater than or equal to given number.', 'wc-admin' ),
|
'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(
|
$params['total_spend_between'] = array(
|
||||||
'description' => __( 'Limit response to objects with a total order spend between two given numbers.', 'wc-admin' ),
|
'description' => __( 'Limit response to objects with a total order spend between two given numbers.', 'wc-admin' ),
|
||||||
'type' => 'array',
|
'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(
|
$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' ),
|
'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(
|
$params['avg_order_value_between'] = array(
|
||||||
'description' => __( 'Limit response to objects with an average order spend between two given numbers.', 'wc-admin' ),
|
'description' => __( 'Limit response to objects with an average order spend between two given numbers.', 'wc-admin' ),
|
||||||
'type' => 'array',
|
'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(
|
$params['last_order_before'] = array(
|
||||||
'description' => __( 'Limit response to objects with last order before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ),
|
'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