This repository has been archived by the owner on Jul 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* data: Add @fresh-data/framework to package.json * data: Add fresh-data and replace orders in table This PR adds fresh-data with a WooCommerce API spec to fulfill order information. It then replaces the existing selectors for the orders table with the new selectors as a proof-of-concept. * wc-api: Add temporary code for `withSelect` This adds temporary code for a `withSelect` function outside of `@wordpress/data` until the context PR is merged: WordPress/gutenberg#11460 * wc-api: Update fresh-data to 0.5.0
- Loading branch information
1 parent
3f214cd
commit 3dbcbf7
Showing
15 changed files
with
5,193 additions
and
4,670 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** @format */ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { SECOND, MINUTE } from '@fresh-data/framework'; | ||
|
||
export const NAMESPACE = '/wc/v3'; | ||
|
||
export const DEFAULT_REQUIREMENT = { | ||
timeout: 5 * SECOND, | ||
freshness: 5 * MINUTE, | ||
}; | ||
|
||
// 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', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** @format */ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import operations from './operations'; | ||
import selectors from './selectors'; | ||
|
||
export default { | ||
operations, | ||
selectors, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** @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( resourceName => { | ||
const query = getResourceIdentifier( resourceName ); | ||
const url = `${ NAMESPACE }/orders${ stringifyQuery( query ) }`; | ||
|
||
return fetch( { path: url } ) | ||
.then( orders => { | ||
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 }, | ||
...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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** @format */ | ||
/** | ||
* 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 isGetOrdersRequesting = getResource => ( query = {} ) => { | ||
const resourceName = getResourceName( 'order-query', query ); | ||
const { lastRequested, lastReceived } = getResource( resourceName ); | ||
return lastRequested && lastRequested > lastReceived; | ||
}; | ||
|
||
const isGetOrdersError = getResource => ( query = {} ) => { | ||
const resourceName = getResourceName( 'order-query', query ); | ||
return getResource( resourceName ).error; | ||
}; | ||
|
||
export default { | ||
getOrders, | ||
isGetOrdersRequesting, | ||
isGetOrdersError, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** @format */ | ||
|
||
export function getResourceName( prefix, identifier ) { | ||
const identifierString = JSON.stringify( identifier, Object.keys( identifier ).sort() ); | ||
return `${ prefix }:${ identifierString }`; | ||
} | ||
|
||
export function isResourcePrefix( resourceName, prefix ) { | ||
const resourcePrefix = resourceName.substring( 0, resourceName.indexOf( ':' ) ); | ||
return resourcePrefix === prefix; | ||
} | ||
|
||
export function getResourceIdentifier( resourceName ) { | ||
const identifierString = resourceName.substring( resourceName.indexOf( ':' ) + 1 ); | ||
return JSON.parse( identifierString ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** @format */ | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import orders from './orders'; | ||
|
||
function createWcApiSpec() { | ||
return { | ||
selectors: { | ||
...orders.selectors, | ||
}, | ||
operations: { | ||
read( resourceNames ) { | ||
return [ ...orders.operations.read( resourceNames ) ]; | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
export default createWcApiSpec(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/** | ||
* NOTE: This is temporary code. It exists only until a version of `@wordpress/data` | ||
* is released which supports this functionality. | ||
* | ||
* TODO: Remove this and use `@wordpress/data` `withSelect` instead after | ||
* this PR is merged: https://github.com/WordPress/gutenberg/pull/11460 | ||
* | ||
* @format | ||
*/ | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import { isFunction } from 'lodash'; | ||
import { Component } from '@wordpress/element'; | ||
import isShallowEqual from '@wordpress/is-shallow-equal'; | ||
import { createHigherOrderComponent } from '@wordpress/compose'; | ||
import { RegistryConsumer } from '@wordpress/data'; | ||
|
||
/** | ||
* Higher-order component used to inject state-derived props using registered | ||
* selectors. | ||
* | ||
* @param {Function} mapSelectToProps Function called on every state change, | ||
* expected to return object of props to | ||
* merge with the component's own props. | ||
* | ||
* @return {Component} Enhanced component with merged state data props. | ||
*/ | ||
const withSelect = mapSelectToProps => | ||
createHigherOrderComponent( WrappedComponent => { | ||
/** | ||
* Default merge props. A constant value is used as the fallback since it | ||
* can be more efficiently shallow compared in case component is repeatedly | ||
* rendered without its own merge props. | ||
* | ||
* @type {Object} | ||
*/ | ||
const DEFAULT_MERGE_PROPS = {}; | ||
|
||
class ComponentWithSelect extends Component { | ||
constructor( props ) { | ||
super( props ); | ||
|
||
this.onStoreChange = this.onStoreChange.bind( this ); | ||
|
||
this.subscribe( props.registry ); | ||
|
||
this.mergeProps = this.getNextMergeProps( props ); | ||
} | ||
|
||
/** | ||
* Given a props object, returns the next merge props by mapSelectToProps. | ||
* | ||
* @param {Object} props Props to pass as argument to mapSelectToProps. | ||
* | ||
* @return {Object} Props to merge into rendered wrapped element. | ||
*/ | ||
getNextMergeProps( props ) { | ||
const context = { component: this }; | ||
const select = reducerKey => { | ||
const selectors = props.registry.select( reducerKey ); | ||
if ( isFunction( selectors ) ) { | ||
return selectors( context ); | ||
} | ||
return selectors; | ||
}; | ||
|
||
return mapSelectToProps( select, props.ownProps ) || DEFAULT_MERGE_PROPS; | ||
} | ||
|
||
componentDidMount() { | ||
this.canRunSelection = true; | ||
|
||
// A state change may have occurred between the constructor and | ||
// mount of the component (e.g. during the wrapped component's own | ||
// constructor), in which case selection should be rerun. | ||
if ( this.hasQueuedSelection ) { | ||
this.hasQueuedSelection = false; | ||
this.onStoreChange(); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
this.canRunSelection = false; | ||
this.unsubscribe(); | ||
} | ||
|
||
shouldComponentUpdate( nextProps, nextState ) { | ||
// Cycle subscription if registry changes. | ||
const hasRegistryChanged = nextProps.registry !== this.props.registry; | ||
if ( hasRegistryChanged ) { | ||
this.unsubscribe(); | ||
this.subscribe( nextProps.registry ); | ||
} | ||
|
||
// Treat a registry change as equivalent to `ownProps`, to reflect | ||
// `mergeProps` to rendered component if and only if updated. | ||
const hasPropsChanged = | ||
hasRegistryChanged || ! isShallowEqual( this.props.ownProps, nextProps.ownProps ); | ||
|
||
// Only render if props have changed or merge props have been updated | ||
// from the store subscriber. | ||
if ( this.state === nextState && ! hasPropsChanged ) { | ||
return false; | ||
} | ||
|
||
if ( hasPropsChanged ) { | ||
const nextMergeProps = this.getNextMergeProps( nextProps ); | ||
if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) { | ||
// If merge props change as a result of the incoming props, | ||
// they should be reflected as such in the upcoming render. | ||
// While side effects are discouraged in lifecycle methods, | ||
// this component is used heavily, and prior efforts to use | ||
// `getDerivedStateFromProps` had demonstrated miserable | ||
// performance. | ||
this.mergeProps = nextMergeProps; | ||
} | ||
|
||
// Regardless whether merge props are changing, fall through to | ||
// incur the render since the component will need to receive | ||
// the changed `ownProps`. | ||
} | ||
|
||
return true; | ||
} | ||
|
||
onStoreChange() { | ||
if ( ! this.canRunSelection ) { | ||
this.hasQueuedSelection = true; | ||
return; | ||
} | ||
|
||
const nextMergeProps = this.getNextMergeProps( this.props ); | ||
if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) { | ||
return; | ||
} | ||
|
||
this.mergeProps = nextMergeProps; | ||
|
||
// Schedule an update. Merge props are not assigned to state since | ||
// derivation of merge props from incoming props occurs within | ||
// shouldComponentUpdate, where setState is not allowed. setState | ||
// is used here instead of forceUpdate because forceUpdate bypasses | ||
// shouldComponentUpdate altogether, which isn't desireable if both | ||
// state and props change within the same render. Unfortunately, | ||
// this requires that next merge props are generated twice. | ||
this.setState( {} ); | ||
} | ||
|
||
subscribe( registry ) { | ||
this.unsubscribe = registry.subscribe( this.onStoreChange ); | ||
} | ||
|
||
render() { | ||
return <WrappedComponent { ...this.props.ownProps } { ...this.mergeProps } />; | ||
} | ||
} | ||
|
||
return ownProps => ( | ||
<RegistryConsumer> | ||
{ registry => <ComponentWithSelect ownProps={ ownProps } registry={ registry } /> } | ||
</RegistryConsumer> | ||
); | ||
}, 'withSelect' ); | ||
|
||
export default withSelect; |
Oops, something went wrong.