* Add allowed leaderboards endpoint to prefetch list

* Hook up leadboards endpoint to dashboard

* Skip data fetch for leaderboard if per page is less than 1

* Add tests to check for rendered html display
This commit is contained in:
Joshua T Flowers 2019-04-11 10:53:05 +08:00 committed by GitHub
parent e190d513c2
commit eb0b137020
11 changed files with 253 additions and 625 deletions

View File

@ -5,36 +5,50 @@
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { get } from 'lodash';
import PropTypes from 'prop-types';
/**
* WooCommerce dependencies
*/
import { Card, EmptyTable, TableCard } from '@woocommerce/components';
import { getPersistedQuery } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import { getLeaderboard } from 'wc-api/items/utils';
import ReportError from 'analytics/components/report-error';
import { getReportTableData } from 'wc-api/reports/utils';
import sanitizeHTML from 'lib/sanitize-html';
import withSelect from 'wc-api/with-select';
import './style.scss';
export class Leaderboard extends Component {
getFormattedHeaders() {
return this.props.headers.map( ( header, i ) => {
return {
isLeftAligned: 0 === i,
hiddenByDefault: false,
isSortable: false,
key: header.label,
label: header.label,
};
} );
}
getFormattedRows() {
return this.props.rows.map( row => {
return row.map( column => {
return {
display: <div dangerouslySetInnerHTML={ sanitizeHTML( column.display ) } />,
value: column.value,
};
} );
} );
}
render() {
const {
getHeadersContent,
getRowsContent,
isRequesting,
isError,
items,
tableQuery,
title,
} = this.props;
const data = get( items, [ 'data' ], [] );
const rows = getRowsContent( data );
const totalRows = tableQuery ? tableQuery.per_page : 5;
const { isRequesting, isError, totalRows, title } = this.props;
const rows = this.getFormattedRows();
if ( isError ) {
return <ReportError className="woocommerce-leaderboard" isError />;
@ -53,7 +67,7 @@ export class Leaderboard extends Component {
return (
<TableCard
className="woocommerce-leaderboard"
headers={ getHeadersContent() }
headers={ this.getFormattedHeaders() }
isLoading={ isRequesting }
rows={ rows }
rowsPerPage={ totalRows }
@ -67,45 +81,60 @@ export class Leaderboard extends Component {
Leaderboard.propTypes = {
/**
* The endpoint to use in API calls to populate the table rows and summary.
* For example, if `taxes` is provided, data will be fetched from the report
* `taxes` endpoint (ie: `/wc/v4/reports/taxes` and `/wc/v4/reports/taxes/stats`).
* If the provided endpoint doesn't exist, an error will be shown to the user
* with `ReportError`.
* An array of column headers.
*/
endpoint: PropTypes.string,
headers: PropTypes.arrayOf(
PropTypes.shape( {
label: PropTypes.string,
} )
),
/**
* A function that returns the headers object to build the table.
* String of leaderboard ID to display.
*/
getHeadersContent: PropTypes.func.isRequired,
/**
* A function that returns the rows array to build the table.
*/
getRowsContent: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
/**
* Query args added to the report table endpoint request.
*/
query: PropTypes.object,
/**
* Properties to be added to the query sent to the report table endpoint.
* Which column should be the row header, defaults to the first item (`0`) (see `Table` props).
*/
tableQuery: PropTypes.object,
rows: PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.shape( {
display: PropTypes.node,
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.bool ] ),
} )
)
).isRequired,
/**
* String to display as the title of the table.
*/
title: PropTypes.string.isRequired,
/**
* Number of table rows.
*/
totalRows: PropTypes.number.isRequired,
};
Leaderboard.defaultProps = {
rows: [],
isError: false,
isRequesting: false,
};
export default compose(
withSelect( ( select, props ) => {
const { endpoint, tableQuery, query } = props;
const tableData = getReportTableData( {
endpoint,
const { id, query, totalRows } = props;
const leaderboardQuery = {
id,
per_page: totalRows,
persisted_query: getPersistedQuery( query ),
query,
select,
tableQuery,
} );
};
const leaderboardData = getLeaderboard( leaderboardQuery );
return { ...tableData };
return leaderboardData;
} )
)( Leaderboard );

View File

@ -3,10 +3,7 @@
*
* @format
*/
import TestRenderer from 'react-test-renderer';
import { map, noop } from 'lodash';
import { shallow } from 'enzyme';
import { createRegistry, RegistryProvider } from '@wordpress/data';
import { mount, shallow } from 'enzyme';
/**
* WooCommerce dependencies
@ -17,101 +14,72 @@ import { numberFormat } from '@woocommerce/number';
/**
* Internal dependencies
*/
import LeaderboardWithSelect, { Leaderboard } from '../';
import { NAMESPACE } from 'wc-api/constants';
import { Leaderboard } from '../';
import mockData from '../__mocks__/top-selling-products-mock-data';
// Mock <Table> to avoid tests failing due to it using DOM properties that
// are not available on TestRenderer.
jest.mock( '@woocommerce/components', () => ( {
...jest.requireActual( '../../../../../packages/components' ),
TableCard: () => null,
} ) );
const rows = mockData.map( row => {
const { name, items_sold, net_revenue, orders_count } = row;
return [
{
display: '<a href="#">' + name + '</a>',
value: name,
},
{
display: numberFormat( items_sold ),
value: items_sold,
},
{
display: numberFormat( orders_count ),
value: orders_count,
},
{
display: formatCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
},
];
} );
const getRowsContent = data => {
return map( data, row => {
const { name, items_sold, net_revenue, orders_count } = row;
return [
{
display: name,
value: name,
},
{
display: numberFormat( items_sold ),
value: items_sold,
},
{
display: numberFormat( orders_count ),
value: orders_count,
},
{
display: formatCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
},
];
} );
};
const headers = [
{
label: 'Name',
},
{
label: 'Items Sold',
},
{
label: 'Orders Count',
},
{
label: 'Net Revenue',
},
];
describe( 'Leaderboard', () => {
test( 'should render empty message when there are no rows', () => {
const leaderboard = shallow(
<Leaderboard title={ '' } getHeadersContent={ noop } getRowsContent={ getRowsContent } />
<Leaderboard id="products" title={ '' } headers={ [] } rows={ [] } totalRows={ 5 } />
);
expect( leaderboard.find( 'EmptyTable' ).length ).toBe( 1 );
} );
test( 'should render correct data in the table', () => {
const leaderboard = shallow(
<Leaderboard
title={ '' }
getHeadersContent={ noop }
getRowsContent={ getRowsContent }
items={ { data: { ...mockData } } }
/>
const leaderboard = mount(
<Leaderboard id="products" title={ '' } headers={ headers } rows={ rows } totalRows={ 5 } />
);
const table = leaderboard.find( 'TableCard' );
const firstRow = table.props().rows[ 0 ];
const tableItems = leaderboard.find( '.woocommerce-table__item' );
expect( firstRow[ 0 ].value ).toBe( mockData[ 0 ].name );
expect( firstRow[ 1 ].display ).toBe( numberFormat( mockData[ 0 ].items_sold ) );
expect( firstRow[ 1 ].value ).toBe( mockData[ 0 ].items_sold );
expect( firstRow[ 2 ].display ).toBe( numberFormat( mockData[ 0 ].orders_count ) );
expect( firstRow[ 2 ].value ).toBe( mockData[ 0 ].orders_count );
expect( firstRow[ 3 ].display ).toBe( formatCurrency( mockData[ 0 ].net_revenue ) );
expect( firstRow[ 3 ].value ).toBe( getCurrencyFormatDecimal( mockData[ 0 ].net_revenue ) );
} );
// @todo Since this now uses fresh-data / wc-api, the API testing needs to be revisted.
xtest( 'should load report stats from API', () => {
const getReportStatsMock = jest.fn().mockReturnValue( { data: mockData } );
const isReportStatsRequestingMock = jest.fn().mockReturnValue( false );
const isReportStatsErrorMock = jest.fn().mockReturnValue( false );
const registry = createRegistry();
registry.registerStore( 'wc-api', {
reducer: () => {},
selectors: {
getReportStats: getReportStatsMock,
isReportStatsRequesting: isReportStatsRequestingMock,
isReportStatsError: isReportStatsErrorMock,
},
} );
const leaderboardWrapper = TestRenderer.create(
<RegistryProvider value={ registry }>
<LeaderboardWithSelect />
</RegistryProvider>
);
const leaderboard = leaderboardWrapper.root.findByType( Leaderboard );
const endpoint = NAMESPACE + '/reports/products';
const query = { orderby: 'items_sold', per_page: 5, extended_info: 1 };
expect( getReportStatsMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
expect( getReportStatsMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
expect( isReportStatsRequestingMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
expect( isReportStatsRequestingMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
expect( isReportStatsErrorMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
expect( isReportStatsErrorMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );
expect( leaderboard.props.data ).toBe( mockData );
expect( leaderboard.render().find( '.woocommerce-table__item a' ).length ).toBe( 5 );
expect( tableItems.at( 0 ).text() ).toBe( mockData[ 0 ].name );
expect( tableItems.at( 1 ).text() ).toBe( numberFormat( mockData[ 0 ].items_sold ) );
expect( tableItems.at( 2 ).text() ).toBe( numberFormat( mockData[ 0 ].orders_count ) );
expect( tableItems.at( 3 ).text() ).toBe( formatCurrency( mockData[ 0 ].net_revenue ) );
} );
} );

View File

@ -18,18 +18,15 @@ import { EllipsisMenu, MenuItem, MenuTitle, SectionHeader } from '@woocommerce/c
/**
* Internal dependencies
*/
import Leaderboard from 'analytics/components/leaderboard';
import withSelect from 'wc-api/with-select';
import TopSellingCategories from './top-selling-categories';
import TopSellingProducts from './top-selling-products';
import TopCoupons from './top-coupons';
import TopCustomers from './top-customers';
import './style.scss';
class Leaderboards extends Component {
constructor( props ) {
super( ...arguments );
this.state = {
hiddenLeaderboardKeys: props.userPrefLeaderboards || [ 'top-coupons', 'top-customers' ],
hiddenLeaderboardKeys: props.userPrefLeaderboards || [ 'coupons', 'customers' ],
rowsPerTable: parseInt( props.userPrefLeaderboardRows ) || 5,
};
@ -57,24 +54,8 @@ class Leaderboards extends Component {
renderMenu() {
const { hiddenLeaderboardKeys, rowsPerTable } = this.state;
const allLeaderboards = [
{
key: 'top-products',
label: __( 'Top Products - Items Sold', 'woocommerce-admin' ),
},
{
key: 'top-categories',
label: __( 'Top Categories - Items Sold', 'woocommerce-admin' ),
},
{
key: 'top-coupons',
label: __( 'Top Coupons', 'woocommerce-admin' ),
},
{
key: 'top-customers',
label: __( 'Top Customers', 'woocommerce-admin' ),
},
];
const { allLeaderboards } = this.props;
return (
<EllipsisMenu
label={ __(
@ -87,11 +68,11 @@ class Leaderboards extends Component {
{ allLeaderboards.map( leaderboard => {
return (
<MenuItem
checked={ ! hiddenLeaderboardKeys.includes( leaderboard.key ) }
checked={ ! hiddenLeaderboardKeys.includes( leaderboard.id ) }
isCheckbox
isClickable
key={ leaderboard.key }
onInvoke={ this.toggle( leaderboard.key ) }
key={ leaderboard.id }
onInvoke={ this.toggle( leaderboard.id ) }
>
{ leaderboard.label }
</MenuItem>
@ -112,9 +93,29 @@ class Leaderboards extends Component {
);
}
render() {
renderLeaderboards() {
const { hiddenLeaderboardKeys, rowsPerTable } = this.state;
const { query } = this.props;
const { allLeaderboards, query } = this.props;
return allLeaderboards.map( leaderboard => {
if ( hiddenLeaderboardKeys.includes( leaderboard.id ) ) {
return;
}
return (
<Leaderboard
headers={ leaderboard.headers }
id={ leaderboard.id }
key={ leaderboard.id }
query={ query }
title={ leaderboard.label }
totalRows={ rowsPerTable }
/>
);
} );
}
render() {
return (
<Fragment>
<div className="woocommerce-dashboard__dashboard-leaderboards">
@ -122,20 +123,7 @@ class Leaderboards extends Component {
title={ __( 'Leaderboards', 'woocommerce-admin' ) }
menu={ this.renderMenu() }
/>
<div className="woocommerce-dashboard__columns">
{ ! hiddenLeaderboardKeys.includes( 'top-products' ) && (
<TopSellingProducts query={ query } totalRows={ rowsPerTable } />
) }
{ ! hiddenLeaderboardKeys.includes( 'top-categories' ) && (
<TopSellingCategories query={ query } totalRows={ rowsPerTable } />
) }
{ ! hiddenLeaderboardKeys.includes( 'top-coupons' ) && (
<TopCoupons query={ query } totalRows={ rowsPerTable } />
) }
{ ! hiddenLeaderboardKeys.includes( 'top-customers' ) && (
<TopCustomers query={ query } totalRows={ rowsPerTable } />
) }
</div>
<div className="woocommerce-dashboard__columns">{ this.renderLeaderboards() }</div>
</div>
</Fragment>
);
@ -148,10 +136,17 @@ Leaderboards.propTypes = {
export default compose(
withSelect( select => {
const { getCurrentUserData } = select( 'wc-api' );
const { getCurrentUserData, getItems, getItemsError, isGetItemsRequesting } = select(
'wc-api'
);
const userData = getCurrentUserData();
const allLeaderboards = wcSettings.dataEndpoints.leaderboards;
return {
allLeaderboards,
getItems,
getItemsError,
isGetItemsRequesting,
userPrefLeaderboards: userData.dashboard_leaderboards,
userPrefLeaderboardRows: userData.dashboard_leaderboard_rows,
};

View File

@ -1,122 +0,0 @@
/** @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', 'woocommerce-admin' ),
key: 'code',
required: true,
isLeftAligned: true,
isSortable: false,
},
{
label: __( 'Orders', 'woocommerce-admin' ),
key: 'orders_count',
required: true,
defaultSort: true,
isSortable: false,
isNumeric: true,
},
{
label: __( 'Amount Discounted', 'woocommerce-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', 'woocommerce-admin' ) }
/>
);
}
}
export default TopCoupons;

View File

@ -1,111 +0,0 @@
/** @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 TopCustomers extends Component {
constructor( props ) {
super( props );
this.getRowsContent = this.getRowsContent.bind( this );
this.getHeadersContent = this.getHeadersContent.bind( this );
}
getHeadersContent() {
return [
{
label: __( 'Customer Name', 'woocommerce-admin' ),
key: 'name',
required: true,
isLeftAligned: true,
isSortable: false,
},
{
label: __( 'Orders', 'woocommerce-admin' ),
key: 'orders_count',
required: true,
defaultSort: true,
isSortable: false,
isNumeric: true,
},
{
label: __( 'Total Spend', 'woocommerce-admin' ),
key: 'total_spend',
isSortable: false,
isNumeric: true,
},
];
}
getRowsContent( data ) {
const { query } = this.props;
const persistedQuery = getPersistedQuery( query );
return map( data, row => {
const { id, total_spend, name, orders_count } = row;
const customerUrl = getNewPath( persistedQuery, 'analytics/customers', {
filter: 'single_customer',
customers: id,
} );
const customerLink = (
<Link href={ customerUrl } type="wc-admin">
{ name }
</Link>
);
return [
{
display: customerLink,
value: name,
},
{
display: numberFormat( orders_count ),
value: orders_count,
},
{
display: formatCurrency( total_spend ),
value: getCurrencyFormatDecimal( total_spend ),
},
];
} );
}
render() {
const { query, totalRows } = this.props;
const tableQuery = {
orderby: 'total_spend',
order: 'desc',
per_page: totalRows,
extended_info: false,
};
return (
<Leaderboard
endpoint="customers"
getHeadersContent={ this.getHeadersContent }
getRowsContent={ this.getRowsContent }
query={ query }
tableQuery={ tableQuery }
title={ __( 'Top Customers', 'woocommerce-admin' ) }
/>
);
}
}
export default TopCustomers;

View File

@ -1,111 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { get, 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 TopSellingCategories extends Component {
constructor( props ) {
super( props );
this.getRowsContent = this.getRowsContent.bind( this );
this.getHeadersContent = this.getHeadersContent.bind( this );
}
getHeadersContent() {
return [
{
label: __( 'Category', 'woocommerce-admin' ),
key: 'category',
required: true,
isLeftAligned: true,
isSortable: false,
},
{
label: __( 'Items Sold', 'woocommerce-admin' ),
key: 'items_sold',
required: false,
isSortable: false,
isNumeric: true,
},
{
label: __( 'Net Revenue', 'woocommerce-admin' ),
key: 'net_revenue',
required: true,
isSortable: false,
isNumeric: true,
},
];
}
getRowsContent( data ) {
const { query } = this.props;
const persistedQuery = getPersistedQuery( query );
return map( data, row => {
const { category_id, items_sold, net_revenue } = row;
const extended_info = row.extended_info || {};
const name = get( extended_info, [ 'name' ] );
const categoryUrl = getNewPath( persistedQuery, 'analytics/categories', {
filter: 'single_category',
categories: category_id,
} );
const categoryLink = (
<Link href={ categoryUrl } type="wc-admin">
{ name }
</Link>
);
return [
{
display: categoryLink,
value: name,
},
{
display: numberFormat( items_sold ),
value: items_sold,
},
{
display: formatCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
},
];
} );
}
render() {
const { query, totalRows } = this.props;
const tableQuery = {
orderby: 'items_sold',
order: 'desc',
per_page: totalRows,
extended_info: true,
};
return (
<Leaderboard
endpoint="categories"
getHeadersContent={ this.getHeadersContent }
getRowsContent={ this.getRowsContent }
query={ query }
tableQuery={ tableQuery }
title={ __( 'Top Categories - Items Sold', 'woocommerce-admin' ) }
/>
);
}
}
export default TopSellingCategories;

View File

@ -1,113 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { get, 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 TopSellingProducts extends Component {
constructor( props ) {
super( props );
this.getRowsContent = this.getRowsContent.bind( this );
this.getHeadersContent = this.getHeadersContent.bind( this );
}
getHeadersContent() {
return [
{
label: __( 'Product', 'woocommerce-admin' ),
key: 'product',
required: true,
isLeftAligned: true,
isSortable: false,
},
{
label: __( 'Items Sold', 'woocommerce-admin' ),
key: 'items_sold',
required: false,
isSortable: false,
isNumeric: true,
},
{
label: __( 'Net Revenue', 'woocommerce-admin' ),
key: 'net_revenue',
required: true,
isSortable: false,
isNumeric: true,
},
];
}
getRowsContent( data ) {
const { query } = this.props;
const persistedQuery = getPersistedQuery( query );
return map( data, row => {
const { product_id, items_sold, net_revenue } = row;
const extended_info = row.extended_info || {};
const name = get( extended_info, [ 'name' ] );
const productUrl = getNewPath( persistedQuery, 'analytics/products', {
filter: 'single_product',
products: product_id,
} );
const productLink = (
<Link href={ productUrl } type="wc-admin">
{ name }
</Link>
);
return [
{
display: productLink,
value: name,
},
{
display: numberFormat( items_sold ),
value: items_sold,
},
{
display: formatCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
},
];
} );
}
render() {
const { query, totalRows } = this.props;
const tableQuery = {
orderby: 'items_sold',
order: 'desc',
per_page: totalRows,
extended_info: true,
};
return (
<Leaderboard
endpoint="products"
getHeadersContent={ this.getHeadersContent }
getRowsContent={ this.getRowsContent }
query={ query }
tableQuery={ tableQuery }
title={ __( 'Top Products - Items Sold', 'woocommerce-admin' ) }
/>
);
}
}
export default TopSellingProducts;

View File

@ -20,6 +20,7 @@ const typeEndpointMap = {
'items-query-categories': 'products/categories',
'items-query-customers': 'customers',
'items-query-coupons': 'coupons',
'items-query-leaderboards': 'leaderboards',
'items-query-orders': 'orders',
'items-query-products': 'products',
'items-query-taxes': 'taxes',

View File

@ -4,6 +4,51 @@
* External dependencies
*/
/**
* WooCommerce dependencies
*/
import { appendTimestamp, getCurrentDates } from '@woocommerce/date';
/**
* Returns leaderboard data to render a leaderboard table.
*
* @param {Objedt} options arguments
* @param {String} options.id Leaderboard ID
* @param {Integer} options.per_page Per page limit
* @param {Object} options.persisted_query Persisted query passed to endpoint
* @param {Object} options.query Query parameters in the url
* @param {Object} options.select Instance of @wordpress/select
* @return {Object} Object containing leaderboard responses.
*/
export function getLeaderboard( options ) {
const endpoint = 'leaderboards';
const { per_page, persisted_query, query, select } = options;
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
const response = {
isRequesting: false,
isError: false,
rows: [],
};
const datesFromQuery = getCurrentDates( query );
const leaderboardQuery = {
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
before: appendTimestamp( datesFromQuery.primary.before, 'end' ),
per_page,
persisted_query,
};
const leaderboards = getItems( endpoint, leaderboardQuery );
const leaderboard = leaderboards.get( options.id );
if ( isGetItemsRequesting( endpoint, leaderboardQuery ) ) {
return { ...response, isRequesting: true };
} else if ( getItemsError( endpoint, leaderboardQuery ) ) {
return { ...response, isError: true };
}
return { ...response, rows: leaderboard.rows };
}
/**
* Returns items based on a search query.
*

View File

@ -47,6 +47,19 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/allowed',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_allowed_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_allowed_item_schema' ),
)
);
}
/**
@ -59,7 +72,7 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
*/
public function get_coupons_leaderboard( $per_page, $after, $before, $persisted_query ) {
$coupons_data_store = new WC_Admin_Reports_Coupons_Data_Store();
$coupons_data = $coupons_data_store->get_data(
$coupons_data = $per_page > 0 ? $coupons_data_store->get_data(
array(
'orderby' => 'orders_count',
'order' => 'desc',
@ -68,10 +81,10 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
'per_page' => $per_page,
'extended_info' => true,
)
);
)->data : array();
$rows = array();
foreach ( $coupons_data->data as $coupon ) {
foreach ( $coupons_data as $coupon ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_coupon',
@ -125,7 +138,7 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
*/
public function get_categories_leaderboard( $per_page, $after, $before, $persisted_query ) {
$categories_data_store = new WC_Admin_Reports_Categories_Data_Store();
$categories_data = $categories_data_store->get_data(
$categories_data = $per_page > 0 ? $categories_data_store->get_data(
array(
'orderby' => 'items_sold',
'order' => 'desc',
@ -134,10 +147,10 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
'per_page' => $per_page,
'extended_info' => true,
)
);
)->data : array();
$rows = array();
foreach ( $categories_data->data as $category ) {
foreach ( $categories_data as $category ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_category',
@ -191,16 +204,16 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
*/
public function get_customers_leaderboard( $per_page, $after, $before, $persisted_query ) {
$customers_data_store = new WC_Admin_Reports_Customers_Data_Store();
$customers_data = $customers_data_store->get_data(
$customers_data = $per_page > 0 ? $customers_data_store->get_data(
array(
'orderby' => 'total_spend',
'order' => 'desc',
'per_page' => $per_page,
)
);
)->data : array();
$rows = array();
foreach ( $customers_data->data as $customer ) {
foreach ( $customers_data as $customer ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_customer',
@ -253,7 +266,7 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
*/
public function get_products_leaderboard( $per_page, $after, $before, $persisted_query ) {
$products_data_store = new WC_Admin_Reports_Products_Data_Store();
$products_data = $products_data_store->get_data(
$products_data = $per_page > 0 ? $products_data_store->get_data(
array(
'orderby' => 'items_sold',
'order' => 'desc',
@ -262,10 +275,10 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
'per_page' => $per_page,
'extended_info' => true,
)
);
)->data : array();
$rows = array();
foreach ( $products_data->data as $product ) {
foreach ( $products_data as $product ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_product',
@ -351,6 +364,39 @@ class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
return rest_ensure_response( $data );
}
/**
* Returns a list of allowed leaderboards.
*
* @param WP_REST_Request $request Request data.
* @return array|WP_Error
*/
public function get_allowed_items( $request ) {
$leaderboards = $this->get_leaderboards( 0, null, null, null );
$data = array();
foreach ( $leaderboards as $leaderboard ) {
$data[] = (object) array(
'id' => $leaderboard['id'],
'label' => $leaderboard['label'],
'headers' => $leaderboard['headers'],
);
}
$objects = array();
foreach ( $data as $item ) {
$prepared = $this->prepare_item_for_response( $item, $request );
$objects[] = $this->prepare_response_for_collection( $prepared );
}
$response = rest_ensure_response( $objects );
$response->header( 'X-WP-Total', count( $data ) );
$response->header( 'X-WP-TotalPages', 1 );
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
return $response;
}
/**
* Prepare the data object for response.
*

View File

@ -154,6 +154,7 @@ function wc_admin_print_script_settings() {
$preload_data_endpoints = array(
'countries' => '/wc/v4/data/countries',
'performanceIndicators' => '/wc/v4/reports/performance-indicators/allowed',
'leaderboards' => '/wc/v4/leaderboards/allowed',
);
if ( function_exists( 'gutenberg_preload_api_request' ) ) {