Items dataStore: Migrate from wc-api (https://github.com/woocommerce/woocommerce-admin/pull/5009)
* Create wp.data folder * order panel * fix isUnboundedRequest with Categories * products report * products table * indicators and leaderboards * orders and stock panels * utils * tests * save * updateStock * remove wc-api items * updateItems -> setItems
This commit is contained in:
parent
2f650b74a0
commit
74e8d7622e
|
@ -4,18 +4,21 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Card, EmptyTable, TableCard } from '@woocommerce/components';
|
||||
import { getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { getFilterQuery, SETTINGS_STORE_NAME } from '@woocommerce/data';
|
||||
import {
|
||||
getFilterQuery,
|
||||
getLeaderboard,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getLeaderboard } from '../../../wc-api/items/utils';
|
||||
import ReportError from '../report-error';
|
||||
import sanitizeHTML from '../../../lib/sanitize-html';
|
||||
import withSelect from '../../../wc-api/with-select';
|
||||
import './style.scss';
|
||||
|
||||
export class Leaderboard extends Component {
|
||||
|
|
|
@ -4,17 +4,18 @@
|
|||
import { __, _n } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import { map } from 'lodash';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { formatValue } from '@woocommerce/number';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CategoryBreacrumbs from './breadcrumbs';
|
||||
import ReportTable from '../../components/report-table';
|
||||
import withSelect from '../../../wc-api/with-select';
|
||||
import { CurrencyContext } from '../../../lib/currency-context';
|
||||
|
||||
class CategoriesReportTable extends Component {
|
||||
|
@ -226,8 +227,8 @@ export default compose(
|
|||
return {};
|
||||
}
|
||||
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
const { getItems, getItemsError, isResolving } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
const tableQuery = {
|
||||
per_page: -1,
|
||||
|
@ -237,10 +238,10 @@ export default compose(
|
|||
const isCategoriesError = Boolean(
|
||||
getItemsError( 'categories', tableQuery )
|
||||
);
|
||||
const isCategoriesRequesting = isGetItemsRequesting(
|
||||
const isCategoriesRequesting = isResolving( 'getItems', [
|
||||
'categories',
|
||||
tableQuery
|
||||
);
|
||||
tableQuery,
|
||||
] );
|
||||
|
||||
return {
|
||||
categories,
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
*/
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import PropTypes from 'prop-types';
|
||||
import { find } from 'lodash';
|
||||
import { getQuery, getSearchWords } from '@woocommerce/navigation';
|
||||
import { searchItemsByString } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import ReportError from '../components/report-error';
|
||||
import { searchItemsByString } from '../../wc-api/items/utils';
|
||||
import withSelect from '../../wc-api/with-select';
|
||||
import {
|
||||
CurrencyContext,
|
||||
getFilteredCurrencyInstance,
|
||||
|
|
|
@ -5,6 +5,8 @@ import { __ } from '@wordpress/i18n';
|
|||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -16,7 +18,6 @@ import ReportChart from '../../components/report-chart';
|
|||
import ReportError from '../../components/report-error';
|
||||
import ReportSummary from '../../components/report-summary';
|
||||
import VariationsReportTable from './table-variations';
|
||||
import withSelect from '../../../wc-api/with-select';
|
||||
import ReportFilters from '../../components/report-filters';
|
||||
|
||||
class ProductsReport extends Component {
|
||||
|
@ -151,8 +152,8 @@ export default compose(
|
|||
};
|
||||
}
|
||||
|
||||
const { getItems, isGetItemsRequesting, getItemsError } = select(
|
||||
'wc-api'
|
||||
const { getItems, isResolving, getItemsError } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
if ( isSingleProductView ) {
|
||||
const productId = parseInt( query.products, 10 );
|
||||
|
@ -163,10 +164,10 @@ export default compose(
|
|||
products &&
|
||||
products.get( productId ) &&
|
||||
products.get( productId ).type === 'variable';
|
||||
const isProductsRequesting = isGetItemsRequesting(
|
||||
const isProductsRequesting = isResolving( 'getItems', [
|
||||
'products',
|
||||
includeArgs
|
||||
);
|
||||
includeArgs,
|
||||
] );
|
||||
const isProductsError = Boolean(
|
||||
getItemsError( 'products', includeArgs )
|
||||
);
|
||||
|
|
|
@ -5,11 +5,13 @@ import { __, _n, _x, sprintf } from '@wordpress/i18n';
|
|||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import { map } from 'lodash';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { Link, Tag } from '@woocommerce/components';
|
||||
import { formatValue } from '@woocommerce/number';
|
||||
import { getAdminLink, getSetting } from '@woocommerce/wc-admin-settings';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -17,7 +19,6 @@ import { getAdminLink, getSetting } from '@woocommerce/wc-admin-settings';
|
|||
import CategoryBreacrumbs from '../categories/breadcrumbs';
|
||||
import { isLowStock } from './utils';
|
||||
import ReportTable from '../../components/report-table';
|
||||
import withSelect from '../../../wc-api/with-select';
|
||||
import { CurrencyContext } from '../../../lib/currency-context';
|
||||
import './style.scss';
|
||||
|
||||
|
@ -381,8 +382,8 @@ export default compose(
|
|||
return {};
|
||||
}
|
||||
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
const { getItems, getItemsError, isResolving } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
const tableQuery = {
|
||||
per_page: -1,
|
||||
|
@ -390,7 +391,10 @@ export default compose(
|
|||
|
||||
const categories = getItems( 'categories', tableQuery );
|
||||
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
|
||||
const isLoading = isGetItemsRequesting( 'categories', tableQuery );
|
||||
const isLoading = isResolving( 'getItems', [
|
||||
'categories',
|
||||
tableQuery,
|
||||
] );
|
||||
|
||||
return { categories, isError, isRequesting: isLoading };
|
||||
} )
|
||||
|
|
|
@ -6,14 +6,14 @@ import { Fragment, useState } from '@wordpress/element';
|
|||
import { compose } from '@wordpress/compose';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import {
|
||||
EllipsisMenu,
|
||||
MenuItem,
|
||||
MenuTitle,
|
||||
SectionHeader,
|
||||
} from '@woocommerce/components';
|
||||
import { useUserPreferences } from '@woocommerce/data';
|
||||
import { useUserPreferences, ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
import { getSetting } from '@woocommerce/wc-admin-settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
|
@ -21,7 +21,6 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import Leaderboard from '../../analytics/components/leaderboard';
|
||||
import withSelect from '../../wc-api/with-select';
|
||||
import './style.scss';
|
||||
|
||||
const renderLeaderboardToggles = ( {
|
||||
|
@ -179,9 +178,7 @@ Leaderboards.propTypes = {
|
|||
|
||||
export default compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
);
|
||||
const { getItems, getItemsError } = select( ITEMS_STORE_NAME );
|
||||
const { leaderboards: allLeaderboards } = getSetting( 'dataEndpoints', {
|
||||
leaderboards: [],
|
||||
} );
|
||||
|
@ -190,7 +187,6 @@ export default compose(
|
|||
allLeaderboards,
|
||||
getItems,
|
||||
getItemsError,
|
||||
isGetItemsRequesting,
|
||||
};
|
||||
} )
|
||||
)( Leaderboards );
|
||||
|
|
|
@ -5,6 +5,7 @@ import { __, _n, sprintf } from '@wordpress/i18n';
|
|||
import { Button } from '@wordpress/components';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import Gridicon from 'gridicons';
|
||||
import PropTypes from 'prop-types';
|
||||
import interpolateComponents from 'interpolate-components';
|
||||
|
@ -19,7 +20,12 @@ import {
|
|||
} from '@woocommerce/components';
|
||||
import { getNewPath } from '@woocommerce/navigation';
|
||||
import { getAdminLink, getSetting } from '@woocommerce/wc-admin-settings';
|
||||
import { SETTINGS_STORE_NAME, REPORTS_STORE_NAME } from '@woocommerce/data';
|
||||
import {
|
||||
SETTINGS_STORE_NAME,
|
||||
REPORTS_STORE_NAME,
|
||||
ITEMS_STORE_NAME,
|
||||
QUERY_DEFAULTS,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
|
@ -28,9 +34,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||
import ActivityHeader from '../activity-header';
|
||||
import ActivityOutboundLink from '../activity-outbound-link';
|
||||
import { QUERY_DEFAULTS } from '../../../wc-api/constants';
|
||||
import { DEFAULT_ACTIONABLE_STATUSES } from '../../../analytics/settings/config';
|
||||
import withSelect from '../../../wc-api/with-select';
|
||||
import { CurrencyContext } from '../../../lib/currency-context';
|
||||
|
||||
class OrdersPanel extends Component {
|
||||
|
@ -330,12 +334,9 @@ OrdersPanel.contextType = CurrencyContext;
|
|||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { hasActionableOrders } = props;
|
||||
const {
|
||||
getItems,
|
||||
getItemsError,
|
||||
getItemsTotalCount,
|
||||
isGetItemsRequesting,
|
||||
} = select( 'wc-api' );
|
||||
const { getItems, getItemsError, getItemsTotalCount } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
const { getReportItems, getReportItemsError, isResolving } = select(
|
||||
REPORTS_STORE_NAME
|
||||
);
|
||||
|
@ -363,10 +364,10 @@ export default compose(
|
|||
const actionableOrders = Array.from(
|
||||
getItems( 'orders', actionableOrdersQuery ).values()
|
||||
);
|
||||
const isRequestingActionable = isGetItemsRequesting(
|
||||
const isRequestingActionable = isResolving( 'getItems', [
|
||||
'orders',
|
||||
actionableOrdersQuery
|
||||
);
|
||||
actionableOrdersQuery,
|
||||
] );
|
||||
|
||||
if ( isRequestingActionable ) {
|
||||
return {
|
||||
|
@ -434,7 +435,10 @@ export default compose(
|
|||
allOrdersQuery
|
||||
);
|
||||
const isError = Boolean( getItemsError( 'orders', allOrdersQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'orders', allOrdersQuery );
|
||||
const isRequesting = isResolving( 'getItems', [
|
||||
'orders',
|
||||
allOrdersQuery,
|
||||
] );
|
||||
|
||||
return {
|
||||
hasNonActionableOrders: totalNonActionableOrders > 0,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { compose } from '@wordpress/compose';
|
|||
import { ESCAPE } from '@wordpress/keycodes';
|
||||
import { get } from 'lodash';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
import { Link, ProductImage } from '@woocommerce/components';
|
||||
import { getSetting } from '@woocommerce/wc-admin-settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
@ -75,13 +76,32 @@ class ProductStockCard extends Component {
|
|||
this.setState( { quantity: event.target.value } );
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const { product, updateProductStock } = this.props;
|
||||
async onSubmit() {
|
||||
const { product, updateProductStock, createNotice } = this.props;
|
||||
const { quantity } = this.state;
|
||||
|
||||
this.setState( { editing: false, edited: true } );
|
||||
|
||||
updateProductStock( product, quantity );
|
||||
const results = await updateProductStock( product, quantity );
|
||||
|
||||
if ( results.success ) {
|
||||
createNotice(
|
||||
'success',
|
||||
sprintf(
|
||||
__( '%s stock updated.', 'woocommerce-admin' ),
|
||||
product.name
|
||||
)
|
||||
);
|
||||
} else {
|
||||
createNotice(
|
||||
'error',
|
||||
sprintf(
|
||||
__( '%s stock could not be updated.', 'woocommerce-admin' ),
|
||||
product.name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.recordStockEvent( 'save', {
|
||||
quantity,
|
||||
} );
|
||||
|
@ -226,10 +246,12 @@ class ProductStockCard extends Component {
|
|||
|
||||
export default compose(
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { updateProductStock } = dispatch( 'wc-api' );
|
||||
const { createNotice } = dispatch( 'core/notices' );
|
||||
const { updateProductStock } = dispatch( ITEMS_STORE_NAME );
|
||||
|
||||
return {
|
||||
updateProductStock,
|
||||
createNotice,
|
||||
};
|
||||
} )
|
||||
)( ProductStockCard );
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import PropTypes from 'prop-types';
|
||||
import Gridicon from 'gridicons';
|
||||
import { EmptyContent, Section } from '@woocommerce/components';
|
||||
import { QUERY_DEFAULTS, ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -14,8 +16,6 @@ import { EmptyContent, Section } from '@woocommerce/components';
|
|||
import { ActivityCard, ActivityCardPlaceholder } from '../../activity-card';
|
||||
import ActivityHeader from '../../activity-header';
|
||||
import ProductStockCard from './card';
|
||||
import { QUERY_DEFAULTS } from '../../../../wc-api/constants';
|
||||
import withSelect from '../../../../wc-api/with-select';
|
||||
|
||||
class StockPanel extends Component {
|
||||
renderEmptyCard() {
|
||||
|
@ -111,8 +111,8 @@ StockPanel.defaultProps = {
|
|||
|
||||
export default compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
const { getItems, getItemsError, isResolving } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
|
||||
const productsQuery = {
|
||||
|
@ -126,7 +126,10 @@ export default compose(
|
|||
getItems( 'products', productsQuery ).values()
|
||||
);
|
||||
const isError = Boolean( getItemsError( 'products', productsQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'products', productsQuery );
|
||||
const isRequesting = isResolving( 'getItems', [
|
||||
'products',
|
||||
productsQuery,
|
||||
] );
|
||||
|
||||
return { products, isError, isRequesting };
|
||||
} )
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
REVIEWS_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
USER_STORE_NAME,
|
||||
ITEMS_STORE_NAME,
|
||||
QUERY_DEFAULTS,
|
||||
} from '@woocommerce/data';
|
||||
import { getSetting } from '@woocommerce/wc-admin-settings';
|
||||
|
||||
|
@ -13,7 +15,6 @@ import { getSetting } from '@woocommerce/wc-admin-settings';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_ACTIONABLE_STATUSES } from '../../analytics/settings/config';
|
||||
import { QUERY_DEFAULTS } from '../../wc-api/constants';
|
||||
import { getUnreadNotesCount } from './panels/inbox/utils';
|
||||
|
||||
export function getUnreadNotes( select ) {
|
||||
|
@ -60,12 +61,9 @@ export function getUnreadNotes( select ) {
|
|||
}
|
||||
|
||||
export function getUnreadOrders( select ) {
|
||||
const {
|
||||
getItems,
|
||||
getItemsTotalCount,
|
||||
getItemsError,
|
||||
isGetItemsRequesting,
|
||||
} = select( 'wc-api' );
|
||||
const { getItems, getItemsTotalCount, getItemsError, isResolving } = select(
|
||||
ITEMS_STORE_NAME
|
||||
);
|
||||
const { getSetting: getMutableSetting } = select( SETTINGS_STORE_NAME );
|
||||
const {
|
||||
woocommerce_actionable_order_statuses: orderStatuses = DEFAULT_ACTIONABLE_STATUSES,
|
||||
|
@ -89,7 +87,7 @@ export function getUnreadOrders( select ) {
|
|||
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
|
||||
const totalOrders = getItemsTotalCount( 'orders', ordersQuery );
|
||||
const isError = Boolean( getItemsError( 'orders', ordersQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'orders', ordersQuery );
|
||||
const isRequesting = isResolving( 'getItems', [ 'orders', ordersQuery ] );
|
||||
|
||||
if ( isError || isRequesting ) {
|
||||
return null;
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import mutations from './mutations';
|
||||
import operations from './operations';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
mutations,
|
||||
operations,
|
||||
selectors,
|
||||
};
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
|
||||
const updateProductStock = ( operations ) => async ( product, newStock ) => {
|
||||
const { createNotice } = dispatch( 'core/notices' );
|
||||
const oldStockQuantity = product.stock_quantity;
|
||||
const resourceName = getResourceName(
|
||||
'items-query-products-item',
|
||||
product.id
|
||||
);
|
||||
|
||||
// Optimistically update product stock
|
||||
operations.updateLocally( [ resourceName ], {
|
||||
[ resourceName ]: { ...product, stock_quantity: newStock },
|
||||
} );
|
||||
|
||||
const result = await operations.update( [ resourceName ], {
|
||||
[ resourceName ]: {
|
||||
id: product.id,
|
||||
type: product.type,
|
||||
parent_id: product.parent_id,
|
||||
stock_quantity: newStock,
|
||||
},
|
||||
} );
|
||||
const response = result[ 0 ][ resourceName ];
|
||||
if ( response && response.data ) {
|
||||
createNotice(
|
||||
'success',
|
||||
sprintf(
|
||||
__( '%s stock updated.', 'woocommerce-admin' ),
|
||||
product.name
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( response && response.error ) {
|
||||
createNotice(
|
||||
'error',
|
||||
sprintf(
|
||||
__( '%s stock could not be updated.', 'woocommerce-admin' ),
|
||||
product.name
|
||||
)
|
||||
);
|
||||
// Revert local changes if the operation failed in the server
|
||||
operations.updateLocally( [ resourceName ], {
|
||||
[ resourceName ]: { ...product, stock_quantity: oldStockQuantity },
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
updateProductStock,
|
||||
};
|
|
@ -1,149 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* 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-leaderboards': 'leaderboards',
|
||||
'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 = addQueryArgs( `${ NAMESPACE }/${ endpoint }`, query );
|
||||
const isUnboundedRequest = query.per_page === -1;
|
||||
|
||||
try {
|
||||
const response = await fetch( {
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* A false parse flag allows a full response including headers which are useful
|
||||
* to determine totalCount. However, this invalidates an unbounded request, ie
|
||||
* `per_page: -1` by skipping middleware in apiFetch.
|
||||
*
|
||||
* See the Gutenberg code for more:
|
||||
* https://github.com/WordPress/gutenberg/blob/dee3dcf49028717b4af3164e3096bfe747c41ed2/packages/api-fetch/src/middlewares/fetch-all-middleware.js#L39-L45
|
||||
*/
|
||||
/* eslint-enable max-len */
|
||||
parse: isUnboundedRequest,
|
||||
path: url,
|
||||
} );
|
||||
|
||||
let items;
|
||||
let totalCount;
|
||||
|
||||
if ( isUnboundedRequest ) {
|
||||
items = response;
|
||||
totalCount = items.length;
|
||||
} else {
|
||||
items = await response.json();
|
||||
totalCount = parseInt(
|
||||
response.headers.get( 'x-wp-total' ),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
...itemResources,
|
||||
};
|
||||
} catch ( error ) {
|
||||
return { [ resourceName ]: { error } };
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
function update( resourceNames, data, fetch = apiFetch ) {
|
||||
const updateableTypes = [ 'items-query-products-item' ];
|
||||
const filteredNames = resourceNames.filter( ( name ) => {
|
||||
return updateableTypes.includes( getResourcePrefix( name ) );
|
||||
} );
|
||||
|
||||
return filteredNames.map( async ( resourceName ) => {
|
||||
const { id, parent_id: parentId, type, ...itemData } = data[
|
||||
resourceName
|
||||
];
|
||||
let url = NAMESPACE;
|
||||
|
||||
switch ( type ) {
|
||||
case 'variation':
|
||||
url += `/products/${ parentId }/variations/${ id }`;
|
||||
break;
|
||||
case 'variable':
|
||||
case 'simple':
|
||||
default:
|
||||
url += `/products/${ id }`;
|
||||
}
|
||||
|
||||
return fetch( { path: url, method: 'PUT', data: itemData } )
|
||||
.then( ( item ) => {
|
||||
return { [ resourceName ]: { data: item } };
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
function updateLocally( resourceNames, data ) {
|
||||
const updateableTypes = [ 'items-query-products-item' ];
|
||||
const filteredNames = resourceNames.filter( ( name ) => {
|
||||
return updateableTypes.includes( getResourcePrefix( name ) );
|
||||
} );
|
||||
|
||||
const lowStockResourceName = getResourceName( 'items-query-products', {
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
low_in_stock: true,
|
||||
status: 'publish',
|
||||
} );
|
||||
|
||||
return filteredNames.map( async ( resourceName ) => {
|
||||
return {
|
||||
[ resourceName ]: { data: data[ resourceName ] },
|
||||
// Force low stock products to be re-fetched after updating an item.
|
||||
[ lowStockResourceName ]: { lastReceived: null },
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
update,
|
||||
updateLocally,
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
};
|
|
@ -1,18 +1,14 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import items from './items';
|
||||
import imports from './imports';
|
||||
|
||||
function createWcApiSpec() {
|
||||
return {
|
||||
name: 'wcApi',
|
||||
mutations: {
|
||||
...items.mutations,
|
||||
},
|
||||
mutations: {},
|
||||
selectors: {
|
||||
...imports.selectors,
|
||||
...items.selectors,
|
||||
},
|
||||
operations: {
|
||||
read( resourceNames ) {
|
||||
|
@ -21,18 +17,13 @@ function createWcApiSpec() {
|
|||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
...imports.operations.read( resourceNames ),
|
||||
...items.operations.read( resourceNames ),
|
||||
];
|
||||
return [ ...imports.operations.read( resourceNames ) ];
|
||||
},
|
||||
update( resourceNames, data ) {
|
||||
return [ ...items.operations.update( resourceNames, data ) ];
|
||||
update() {
|
||||
return [];
|
||||
},
|
||||
updateLocally( resourceNames, data ) {
|
||||
return [
|
||||
...items.operations.updateLocally( resourceNames, data ),
|
||||
];
|
||||
updateLocally() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,6 +21,10 @@ export { REVIEWS_STORE_NAME } from './reviews';
|
|||
export { NOTES_STORE_NAME } from './notes';
|
||||
|
||||
export { REPORTS_STORE_NAME } from './reports';
|
||||
|
||||
export { ITEMS_STORE_NAME } from './items';
|
||||
export { getLeaderboard, searchItemsByString } from './items/utils';
|
||||
|
||||
export {
|
||||
getFilterQuery,
|
||||
getSummaryNumbers,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const TYPES = {
|
||||
SET_ITEMS: 'SET_ITEMS',
|
||||
SET_ERROR: 'SET_ERROR',
|
||||
};
|
||||
|
||||
export default TYPES;
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { NAMESPACE } from '../constants';
|
||||
|
||||
export function setItems( itemType, query, items, totalCount ) {
|
||||
return {
|
||||
type: TYPES.SET_ITEMS,
|
||||
items,
|
||||
itemType,
|
||||
query,
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function setError( itemType, query, error ) {
|
||||
return {
|
||||
type: TYPES.SET_ERROR,
|
||||
itemType,
|
||||
query,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function* updateProductStock( product, quantity ) {
|
||||
const updatedProduct = { ...product, stock_quantity: quantity };
|
||||
const { id, parent_id: parentId, type } = updatedProduct;
|
||||
|
||||
// Optimistically update product stock.
|
||||
yield setItems( 'products', id, [ updatedProduct ], 1 );
|
||||
|
||||
let url = NAMESPACE;
|
||||
|
||||
switch ( type ) {
|
||||
case 'variation':
|
||||
url += `/products/${ parentId }/variations/${ id }`;
|
||||
break;
|
||||
case 'variable':
|
||||
case 'simple':
|
||||
default:
|
||||
url += `/products/${ id }`;
|
||||
}
|
||||
|
||||
try {
|
||||
const results = yield apiFetch( {
|
||||
path: url,
|
||||
method: 'PUT',
|
||||
data: updatedProduct,
|
||||
} );
|
||||
return { success: true, ...results };
|
||||
} catch ( error ) {
|
||||
// Update failed, return product back to original state.
|
||||
yield setItems( 'products', id, [ product ], 1 );
|
||||
yield setError( id, error );
|
||||
return { success: false, ...error };
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const STORE_NAME = 'wc/admin/items';
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { registerStore, select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_NAME } from './constants';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import controls from '../controls';
|
||||
import reducer from './reducer';
|
||||
|
||||
const storeSelectors = select( STORE_NAME );
|
||||
|
||||
// @todo This is used to prevent double registration of the store due to webpack chunks.
|
||||
// The `storeSelectors` condition can be removed once this is fixed.
|
||||
// See https://github.com/woocommerce/woocommerce-admin/issues/4443.
|
||||
if ( ! storeSelectors ) {
|
||||
registerStore( STORE_NAME, {
|
||||
reducer,
|
||||
actions,
|
||||
controls,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
}
|
||||
|
||||
export const ITEMS_STORE_NAME = STORE_NAME;
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { getResourceName } from '../utils';
|
||||
|
||||
const reducer = (
|
||||
state = {
|
||||
items: {},
|
||||
errors: {},
|
||||
data: {},
|
||||
},
|
||||
{ type, itemType, query, items, totalCount, error }
|
||||
) => {
|
||||
switch ( type ) {
|
||||
case TYPES.SET_ITEMS:
|
||||
const ids = [];
|
||||
const nextItems = items.reduce( ( result, item ) => {
|
||||
ids.push( item.id );
|
||||
result[ item.id ] = item;
|
||||
return result;
|
||||
}, {} );
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return {
|
||||
...state,
|
||||
items: {
|
||||
...state.items,
|
||||
[ resourceName ]: { data: ids, totalCount },
|
||||
},
|
||||
data: {
|
||||
...state.data,
|
||||
[ itemType ]: {
|
||||
...state.data[ itemType ],
|
||||
...nextItems,
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ERROR:
|
||||
return {
|
||||
...state,
|
||||
errors: {
|
||||
...state.errors,
|
||||
[ getResourceName( itemType, query ) ]: error,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { NAMESPACE } from '../constants';
|
||||
import { setError, setItems } from './actions';
|
||||
import { fetchWithHeaders } from '../controls';
|
||||
|
||||
export function* getItems( itemType, query ) {
|
||||
const endpoint =
|
||||
itemType === 'categories' ? 'products/categories' : itemType;
|
||||
try {
|
||||
const url = addQueryArgs( `${ NAMESPACE }/${ endpoint }`, query );
|
||||
const isUnboundedRequest = query.per_page === -1;
|
||||
const fetch = isUnboundedRequest ? apiFetch : fetchWithHeaders;
|
||||
const response = yield fetch( {
|
||||
path: url,
|
||||
method: 'GET',
|
||||
} );
|
||||
|
||||
if ( isUnboundedRequest ) {
|
||||
yield setItems( itemType, query, response, response.length );
|
||||
} else {
|
||||
const totalCount = parseInt(
|
||||
response.headers.get( 'x-wp-total' ),
|
||||
10
|
||||
);
|
||||
yield setItems( itemType, query, response.data, totalCount );
|
||||
}
|
||||
} catch ( error ) {
|
||||
yield setError( query, error );
|
||||
}
|
||||
}
|
||||
|
||||
export function* getReviewsTotalCount( itemType, query ) {
|
||||
yield getItems( itemType, query );
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
|
||||
export const getItems = ( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
const ids =
|
||||
( state.items[ resourceName ] && state.items[ resourceName ].data ) ||
|
||||
[];
|
||||
return ids.reduce( ( map, id ) => {
|
||||
map.set( id, state.data[ itemType ][ id ] );
|
||||
return map;
|
||||
}, new Map() );
|
||||
};
|
||||
|
||||
export const getItemsTotalCount = ( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return (
|
||||
( state.items[ resourceName ] &&
|
||||
state.items[ resourceName ].totalCount ) ||
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
export const getItemsError = ( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return state.errors[ resourceName ];
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from '../reducer';
|
||||
import TYPES from '../action-types';
|
||||
import { getResourceName } from '../../utils';
|
||||
|
||||
const defaultState = {
|
||||
items: {},
|
||||
errors: {},
|
||||
data: {},
|
||||
};
|
||||
|
||||
describe( 'items reducer', () => {
|
||||
it( 'should return a default state', () => {
|
||||
const state = reducer( undefined, {} );
|
||||
expect( state ).toEqual( defaultState );
|
||||
expect( state ).not.toBe( defaultState );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_ITEMS', () => {
|
||||
const items = [
|
||||
{ id: 1, title: 'Yum!' },
|
||||
{ id: 2, title: 'Dynamite!' },
|
||||
];
|
||||
const totalCount = 45;
|
||||
const query = { status: 'flavortown' };
|
||||
const itemType = 'BBQ';
|
||||
const state = reducer( defaultState, {
|
||||
type: TYPES.SET_ITEMS,
|
||||
items,
|
||||
itemType,
|
||||
query,
|
||||
totalCount,
|
||||
} );
|
||||
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
|
||||
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
||||
expect( state.items[ resourceName ].data.includes( 1 ) ).toBeTruthy();
|
||||
expect( state.items[ resourceName ].data.includes( 2 ) ).toBeTruthy();
|
||||
|
||||
expect( state.items[ resourceName ].totalCount ).toBe( 45 );
|
||||
expect( state.data[ itemType ][ '1' ] ).toBe( items[ 0 ] );
|
||||
expect( state.data[ itemType ][ '2' ] ).toBe( items[ 1 ] );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_ERROR', () => {
|
||||
const query = { status: 'flavortown' };
|
||||
const itemType = 'BBQ';
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
const error = 'Baaam!';
|
||||
const state = reducer( defaultState, {
|
||||
type: TYPES.SET_ERROR,
|
||||
itemType,
|
||||
query,
|
||||
error,
|
||||
} );
|
||||
|
||||
expect( state.errors[ resourceName ] ).toBe( error );
|
||||
} );
|
||||
} );
|
|
@ -3,6 +3,11 @@
|
|||
*/
|
||||
import { appendTimestamp, getCurrentDates } from '@woocommerce/date';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* Returns leaderboard data to render a leaderboard table.
|
||||
*
|
||||
|
@ -24,9 +29,7 @@ export function getLeaderboard( options ) {
|
|||
select,
|
||||
filterQuery,
|
||||
} = options;
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
);
|
||||
const { getItems, getItemsError, isResolving } = select( STORE_NAME );
|
||||
const response = {
|
||||
isRequesting: false,
|
||||
isError: false,
|
||||
|
@ -47,7 +50,7 @@ export function getLeaderboard( options ) {
|
|||
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
|
||||
const leaderboards = getItems( endpoint, leaderboardQuery );
|
||||
|
||||
if ( isGetItemsRequesting( endpoint, leaderboardQuery ) ) {
|
||||
if ( isResolving( 'getItems', [ endpoint, leaderboardQuery ] ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
} else if ( getItemsError( endpoint, leaderboardQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
|
@ -66,9 +69,7 @@ export function getLeaderboard( options ) {
|
|||
* @return {Object} Object containing API request information and the matching items.
|
||||
*/
|
||||
export function searchItemsByString( select, endpoint, search ) {
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select(
|
||||
'wc-api'
|
||||
);
|
||||
const { getItems, getItemsError, isResolving } = select( STORE_NAME );
|
||||
|
||||
const items = {};
|
||||
let isRequesting = false;
|
||||
|
@ -82,7 +83,7 @@ export function searchItemsByString( select, endpoint, search ) {
|
|||
newItems.forEach( ( item, id ) => {
|
||||
items[ id ] = item;
|
||||
} );
|
||||
if ( isGetItemsRequesting( endpoint, query ) ) {
|
||||
if ( isResolving( 'getItems', [ endpoint, query ] ) ) {
|
||||
isRequesting = true;
|
||||
}
|
||||
if ( getItemsError( endpoint, query ) ) {
|
Loading…
Reference in New Issue