Add data store attribute terms (#33721)
* Add product attribute terms store * Add ability to add params to URL * Allow IdQuery for all crud functions * Add tests around item parent keys * Add tests around new utils * Throw error when not all params are replaced in REST URL * Add require attributes for terms * Allow urlParameters to be specified * Use namespace to detect url parameters * Fix up selector return after applying namespace * Clean queries to prevent sending URL param data to endpoint * Add tests around new utils * Remove unused method import * Remove urlParameters argument no longer being used * Add changelog entry
This commit is contained in:
parent
ecd17484bb
commit
4c1a82dc26
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add product attribute terms data store
|
|
@ -1,15 +1,15 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
|
||||||
import { apiFetch } from '@wordpress/data-controls';
|
import { apiFetch } from '@wordpress/data-controls';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
import { cleanQuery, getUrlParameters, getRestPath, parseId } from './utils';
|
||||||
import CRUD_ACTIONS from './crud-actions';
|
import CRUD_ACTIONS from './crud-actions';
|
||||||
import TYPES from './action-types';
|
import TYPES from './action-types';
|
||||||
import { IdType, Item, ItemQuery } from './types';
|
import { IdType, IdQuery, Item, ItemQuery } from './types';
|
||||||
|
|
||||||
type ResolverOptions = {
|
type ResolverOptions = {
|
||||||
resourceName: string;
|
resourceName: string;
|
||||||
|
@ -25,50 +25,53 @@ export function createItemError( query: Partial< ItemQuery >, error: unknown ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createItemSuccess( id: IdType, item: Item ) {
|
export function createItemSuccess( key: IdType, item: Item ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.CREATE_ITEM_SUCCESS as const,
|
type: TYPES.CREATE_ITEM_SUCCESS as const,
|
||||||
id,
|
key,
|
||||||
item,
|
item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteItemError( id: IdType, error: unknown ) {
|
export function deleteItemError( key: IdType, error: unknown ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.DELETE_ITEM_ERROR as const,
|
type: TYPES.DELETE_ITEM_ERROR as const,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.DELETE_ITEM,
|
errorType: CRUD_ACTIONS.DELETE_ITEM,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteItemSuccess( id: IdType, force: boolean, item: Item ) {
|
export function deleteItemSuccess( key: IdQuery, force: boolean, item: Item ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.DELETE_ITEM_SUCCESS as const,
|
type: TYPES.DELETE_ITEM_SUCCESS as const,
|
||||||
id,
|
key,
|
||||||
force,
|
force,
|
||||||
item,
|
item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemError( id: unknown, error: unknown ) {
|
export function getItemError( key: IdType, error: unknown ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.GET_ITEM_ERROR as const,
|
type: TYPES.GET_ITEM_ERROR as const,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.GET_ITEM,
|
errorType: CRUD_ACTIONS.GET_ITEM,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemSuccess( id: IdType, item: Item ) {
|
export function getItemSuccess( key: IdType, item: Item ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.GET_ITEM_SUCCESS as const,
|
type: TYPES.GET_ITEM_SUCCESS as const,
|
||||||
id,
|
key,
|
||||||
item,
|
item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemsError( query: unknown, error: unknown ) {
|
export function getItemsError(
|
||||||
|
query: Partial< ItemQuery > | undefined,
|
||||||
|
error: unknown
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.GET_ITEMS_ERROR as const,
|
type: TYPES.GET_ITEMS_ERROR as const,
|
||||||
query,
|
query,
|
||||||
|
@ -77,27 +80,32 @@ export function getItemsError( query: unknown, error: unknown ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemsSuccess( query: unknown, items: Item[] ) {
|
export function getItemsSuccess(
|
||||||
|
query: Partial< ItemQuery > | undefined,
|
||||||
|
items: Item[],
|
||||||
|
urlParameters: IdType[]
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.GET_ITEMS_SUCCESS as const,
|
type: TYPES.GET_ITEMS_SUCCESS as const,
|
||||||
items,
|
items,
|
||||||
query,
|
query,
|
||||||
|
urlParameters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateItemError( id: unknown, error: unknown ) {
|
export function updateItemError( key: IdType, error: unknown ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.UPDATE_ITEM_ERROR as const,
|
type: TYPES.UPDATE_ITEM_ERROR as const,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.UPDATE_ITEM,
|
errorType: CRUD_ACTIONS.UPDATE_ITEM,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateItemSuccess( id: IdType, item: Item ) {
|
export function updateItemSuccess( key: IdType, item: Item ) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.UPDATE_ITEM_SUCCESS as const,
|
type: TYPES.UPDATE_ITEM_SUCCESS as const,
|
||||||
id,
|
key,
|
||||||
item,
|
item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -107,13 +115,20 @@ export const createDispatchActions = ( {
|
||||||
resourceName,
|
resourceName,
|
||||||
}: ResolverOptions ) => {
|
}: ResolverOptions ) => {
|
||||||
const createItem = function* ( query: Partial< ItemQuery > ) {
|
const createItem = function* ( query: Partial< ItemQuery > ) {
|
||||||
|
const urlParameters = getUrlParameters( namespace, query );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item: Item = yield apiFetch( {
|
const item: Item = yield apiFetch( {
|
||||||
path: addQueryArgs( namespace, query ),
|
path: getRestPath(
|
||||||
|
namespace,
|
||||||
|
cleanQuery( query, namespace ),
|
||||||
|
urlParameters
|
||||||
|
),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
} );
|
} );
|
||||||
|
const { key } = parseId( item.id, urlParameters );
|
||||||
|
|
||||||
yield createItemSuccess( item.id, item );
|
yield createItemSuccess( key, item );
|
||||||
return item;
|
return item;
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield createItemError( query, error );
|
yield createItemError( query, error );
|
||||||
|
@ -121,32 +136,49 @@ export const createDispatchActions = ( {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteItem = function* ( id: IdType, force = true ) {
|
const deleteItem = function* ( idQuery: IdQuery, force = true ) {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { id, key } = parseId( idQuery, urlParameters );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item: Item = yield apiFetch( {
|
const item: Item = yield apiFetch( {
|
||||||
path: addQueryArgs( `${ namespace }/${ id }`, { force } ),
|
path: getRestPath(
|
||||||
|
`${ namespace }/${ id }`,
|
||||||
|
{ force },
|
||||||
|
urlParameters
|
||||||
|
),
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
yield deleteItemSuccess( id, force, item );
|
yield deleteItemSuccess( key, force, item );
|
||||||
return item;
|
return item;
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield deleteItemError( id, error );
|
yield deleteItemError( key, error );
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateItem = function* ( id: IdType, query: Partial< ItemQuery > ) {
|
const updateItem = function* (
|
||||||
|
idQuery: IdQuery,
|
||||||
|
query: Partial< ItemQuery >
|
||||||
|
) {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { id, key } = parseId( idQuery, urlParameters );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item: Item = yield apiFetch( {
|
const item: Item = yield apiFetch( {
|
||||||
path: addQueryArgs( `${ namespace }/${ id }`, query ),
|
path: getRestPath(
|
||||||
|
`${ namespace }/${ id }`,
|
||||||
|
cleanQuery( query, namespace ),
|
||||||
|
urlParameters
|
||||||
|
),
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
yield updateItemSuccess( item.id, item );
|
yield updateItemSuccess( key, item );
|
||||||
return item;
|
return item;
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield updateItemError( query, error );
|
yield updateItemError( key, error );
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,11 @@ export const createCrudDataStore = ( {
|
||||||
pluralResourceName,
|
pluralResourceName,
|
||||||
namespace,
|
namespace,
|
||||||
} );
|
} );
|
||||||
const selectors = createSelectors( { resourceName, pluralResourceName } );
|
const selectors = createSelectors( {
|
||||||
|
resourceName,
|
||||||
|
pluralResourceName,
|
||||||
|
namespace,
|
||||||
|
} );
|
||||||
|
|
||||||
registerStore( storeName, {
|
registerStore( storeName, {
|
||||||
reducer: reducer as Reducer< ResourceState >,
|
reducer: reducer as Reducer< ResourceState >,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Reducer } from 'redux';
|
||||||
*/
|
*/
|
||||||
import { Actions } from './actions';
|
import { Actions } from './actions';
|
||||||
import CRUD_ACTIONS from './crud-actions';
|
import CRUD_ACTIONS from './crud-actions';
|
||||||
|
import { getKey } from './utils';
|
||||||
import { getResourceName } from '../utils';
|
import { getResourceName } from '../utils';
|
||||||
import { IdType, Item, ItemQuery } from './types';
|
import { IdType, Item, ItemQuery } from './types';
|
||||||
import { TYPES } from './action-types';
|
import { TYPES } from './action-types';
|
||||||
|
@ -56,25 +57,25 @@ export const createReducer = () => {
|
||||||
...state,
|
...state,
|
||||||
data: {
|
data: {
|
||||||
...itemData,
|
...itemData,
|
||||||
[ payload.id ]: {
|
[ payload.key ]: {
|
||||||
...( itemData[ payload.id ] || {} ),
|
...( itemData[ payload.key ] || {} ),
|
||||||
...payload.item,
|
...payload.item,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
case TYPES.DELETE_ITEM_SUCCESS:
|
case TYPES.DELETE_ITEM_SUCCESS:
|
||||||
const itemIds = Object.keys( state.data );
|
const itemKeys = Object.keys( state.data );
|
||||||
const nextData = itemIds.reduce< Data >(
|
const nextData = itemKeys.reduce< Data >(
|
||||||
( items: Data, id: string ) => {
|
( items: Data, key: string ) => {
|
||||||
if ( id !== payload.id.toString() ) {
|
if ( key !== payload.key.toString() ) {
|
||||||
items[ id ] = state.data[ id ];
|
items[ key ] = state.data[ key ];
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
if ( payload.force ) {
|
if ( payload.force ) {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
items[ id ] = payload.item;
|
items[ key ] = payload.item;
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
{} as Data
|
{} as Data
|
||||||
|
@ -93,7 +94,7 @@ export const createReducer = () => {
|
||||||
errors: {
|
errors: {
|
||||||
...state.errors,
|
...state.errors,
|
||||||
[ getResourceName( payload.errorType, {
|
[ getResourceName( payload.errorType, {
|
||||||
id: payload.id,
|
key: payload.key,
|
||||||
} ) ]: payload.error,
|
} ) ]: payload.error,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -104,9 +105,10 @@ export const createReducer = () => {
|
||||||
const nextResources = payload.items.reduce<
|
const nextResources = payload.items.reduce<
|
||||||
Record< string, Item >
|
Record< string, Item >
|
||||||
>( ( result, item ) => {
|
>( ( result, item ) => {
|
||||||
ids.push( item.id );
|
const key = getKey( item.id, payload.urlParameters );
|
||||||
result[ item.id ] = {
|
ids.push( key );
|
||||||
...( state.data[ item.id ] || {} ),
|
result[ key ] = {
|
||||||
|
...( state.data[ key ] || {} ),
|
||||||
...item,
|
...item,
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -6,14 +6,15 @@ import { apiFetch } from '@wordpress/data-controls';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
import { cleanQuery, getUrlParameters, getRestPath, parseId } from './utils';
|
||||||
import {
|
import {
|
||||||
getItemError,
|
getItemError,
|
||||||
getItemSuccess,
|
getItemSuccess,
|
||||||
getItemsError,
|
getItemsError,
|
||||||
getItemsSuccess,
|
getItemsSuccess,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
import { IdQuery, Item, ItemQuery } from './types';
|
||||||
import { request } from '../utils';
|
import { request } from '../utils';
|
||||||
import { Item, ItemQuery } from './types';
|
|
||||||
|
|
||||||
type ResolverOptions = {
|
type ResolverOptions = {
|
||||||
resourceName: string;
|
resourceName: string;
|
||||||
|
@ -26,25 +27,32 @@ export const createResolvers = ( {
|
||||||
pluralResourceName,
|
pluralResourceName,
|
||||||
namespace,
|
namespace,
|
||||||
}: ResolverOptions ) => {
|
}: ResolverOptions ) => {
|
||||||
const getItem = function* ( id: number ) {
|
const getItem = function* ( idQuery: IdQuery ) {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { id, key } = parseId( idQuery, urlParameters );
|
||||||
try {
|
try {
|
||||||
const item: Item = yield apiFetch( {
|
const item: Item = yield apiFetch( {
|
||||||
path: `${ namespace }/${ id }`,
|
path: getRestPath(
|
||||||
|
`${ namespace }/${ id }`,
|
||||||
|
{},
|
||||||
|
urlParameters
|
||||||
|
),
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
yield getItemSuccess( item.id, item );
|
yield getItemSuccess( key, item );
|
||||||
return item;
|
return item;
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield getItemError( id, error );
|
yield getItemError( key, error );
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getItems = function* ( query?: Partial< ItemQuery > ) {
|
const getItems = function* ( query?: Partial< ItemQuery > ) {
|
||||||
// Require ID when requesting specific fields to later update the resource data.
|
const urlParameters = getUrlParameters( namespace, query || {} );
|
||||||
const resourceQuery = query ? { ...query } : {};
|
const resourceQuery = cleanQuery( query || {}, namespace );
|
||||||
|
|
||||||
|
// Require ID when requesting specific fields to later update the resource data.
|
||||||
if (
|
if (
|
||||||
resourceQuery &&
|
resourceQuery &&
|
||||||
resourceQuery._fields &&
|
resourceQuery._fields &&
|
||||||
|
@ -54,12 +62,13 @@ export const createResolvers = ( {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const path = getRestPath( namespace, {}, urlParameters );
|
||||||
const { items }: { items: Item[] } = yield request<
|
const { items }: { items: Item[] } = yield request<
|
||||||
ItemQuery,
|
ItemQuery,
|
||||||
Item
|
Item
|
||||||
>( namespace, resourceQuery );
|
>( path, resourceQuery );
|
||||||
|
|
||||||
yield getItemsSuccess( query, items );
|
yield getItemsSuccess( query, items, urlParameters );
|
||||||
return items;
|
return items;
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield getItemsError( query, error );
|
yield getItemsError( query, error );
|
||||||
|
|
|
@ -6,14 +6,16 @@ import createSelector from 'rememo';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
import { applyNamespace, getUrlParameters, parseId } from './utils';
|
||||||
import { getResourceName } from '../utils';
|
import { getResourceName } from '../utils';
|
||||||
import { IdType, Item, ItemQuery } from './types';
|
import { IdQuery, IdType, Item, ItemQuery } from './types';
|
||||||
import { ResourceState } from './reducer';
|
import { ResourceState } from './reducer';
|
||||||
import CRUD_ACTIONS from './crud-actions';
|
import CRUD_ACTIONS from './crud-actions';
|
||||||
|
|
||||||
type SelectorOptions = {
|
type SelectorOptions = {
|
||||||
resourceName: string;
|
resourceName: string;
|
||||||
pluralResourceName: string;
|
pluralResourceName: string;
|
||||||
|
namespace: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemCreateError = (
|
export const getItemCreateError = (
|
||||||
|
@ -24,17 +26,35 @@ export const getItemCreateError = (
|
||||||
return state.errors[ itemQuery ];
|
return state.errors[ itemQuery ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemDeleteError = ( state: ResourceState, id: IdType ) => {
|
export const getItemDeleteError = (
|
||||||
const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { id } );
|
state: ResourceState,
|
||||||
|
idQuery: IdQuery,
|
||||||
|
namespace: string
|
||||||
|
) => {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { key } = parseId( idQuery, urlParameters );
|
||||||
|
const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { key } );
|
||||||
return state.errors[ itemQuery ];
|
return state.errors[ itemQuery ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItem = ( state: ResourceState, id: IdType ) => {
|
export const getItem = (
|
||||||
return state.data[ id ];
|
state: ResourceState,
|
||||||
|
idQuery: IdQuery,
|
||||||
|
namespace: string
|
||||||
|
) => {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { key } = parseId( idQuery, urlParameters );
|
||||||
|
return state.data[ key ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemError = ( state: ResourceState, id: IdType ) => {
|
export const getItemError = (
|
||||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
|
state: ResourceState,
|
||||||
|
idQuery: IdQuery,
|
||||||
|
namespace: string
|
||||||
|
) => {
|
||||||
|
const urlParameters = getUrlParameters( namespace, idQuery );
|
||||||
|
const { key } = parseId( idQuery, urlParameters );
|
||||||
|
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { key } );
|
||||||
return state.errors[ itemQuery ];
|
return state.errors[ itemQuery ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,11 +87,13 @@ export const getItems = createSelector(
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids
|
const data = ids
|
||||||
.map( ( id: IdType ) => {
|
.map( ( id: IdType ) => {
|
||||||
return state.data[ id ];
|
return state.data[ id ];
|
||||||
} )
|
} )
|
||||||
.filter( ( item ) => item !== undefined );
|
.filter( ( item ) => item !== undefined );
|
||||||
|
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
( state, query ) => {
|
( state, query ) => {
|
||||||
const itemQuery = getResourceName(
|
const itemQuery = getResourceName(
|
||||||
|
@ -81,6 +103,7 @@ export const getItems = createSelector(
|
||||||
const ids = state.items[ itemQuery ]
|
const ids = state.items[ itemQuery ]
|
||||||
? state.items[ itemQuery ].data
|
? state.items[ itemQuery ].data
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
state.items[ itemQuery ],
|
state.items[ itemQuery ],
|
||||||
...( ids || [] ).map( ( id: string ) => {
|
...( ids || [] ).map( ( id: string ) => {
|
||||||
|
@ -95,22 +118,47 @@ export const getItemsError = ( state: ResourceState, query?: ItemQuery ) => {
|
||||||
return state.errors[ itemQuery ];
|
return state.errors[ itemQuery ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getItemUpdateError = ( state: ResourceState, id: IdType ) => {
|
export const getItemUpdateError = (
|
||||||
const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, { id } );
|
state: ResourceState,
|
||||||
|
idQuery: IdQuery,
|
||||||
|
urlParameters: IdType[]
|
||||||
|
) => {
|
||||||
|
const params = parseId( idQuery, urlParameters );
|
||||||
|
const { key } = params;
|
||||||
|
const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, {
|
||||||
|
key,
|
||||||
|
params,
|
||||||
|
} );
|
||||||
return state.errors[ itemQuery ];
|
return state.errors[ itemQuery ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSelectors = ( {
|
export const createSelectors = ( {
|
||||||
resourceName,
|
resourceName,
|
||||||
pluralResourceName,
|
pluralResourceName,
|
||||||
|
namespace,
|
||||||
}: SelectorOptions ) => {
|
}: SelectorOptions ) => {
|
||||||
return {
|
return {
|
||||||
[ `get${ resourceName }` ]: getItem,
|
[ `get${ resourceName }` ]: applyNamespace( getItem, namespace ),
|
||||||
[ `get${ resourceName }Error` ]: getItemError,
|
[ `get${ resourceName }Error` ]: applyNamespace(
|
||||||
[ `get${ pluralResourceName }` ]: getItems,
|
getItemError,
|
||||||
[ `get${ pluralResourceName }Error` ]: getItemsError,
|
namespace
|
||||||
[ `get${ resourceName }CreateError` ]: getItemCreateError,
|
),
|
||||||
[ `get${ resourceName }DeleteError` ]: getItemDeleteError,
|
[ `get${ pluralResourceName }` ]: applyNamespace( getItems, namespace ),
|
||||||
[ `get${ resourceName }UpdateError` ]: getItemUpdateError,
|
[ `get${ pluralResourceName }Error` ]: applyNamespace(
|
||||||
|
getItemsError,
|
||||||
|
namespace
|
||||||
|
),
|
||||||
|
[ `get${ resourceName }CreateError` ]: applyNamespace(
|
||||||
|
getItemCreateError,
|
||||||
|
namespace
|
||||||
|
),
|
||||||
|
[ `get${ resourceName }DeleteError` ]: applyNamespace(
|
||||||
|
getItemDeleteError,
|
||||||
|
namespace
|
||||||
|
),
|
||||||
|
[ `get${ resourceName }UpdateError` ]: applyNamespace(
|
||||||
|
getItemUpdateError,
|
||||||
|
namespace
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,7 +44,7 @@ describe( 'crud reducer', () => {
|
||||||
|
|
||||||
const state = reducer( initialState, {
|
const state = reducer( initialState, {
|
||||||
type: TYPES.GET_ITEM_SUCCESS,
|
type: TYPES.GET_ITEM_SUCCESS,
|
||||||
id: update.id,
|
key: update.id,
|
||||||
item: update,
|
item: update,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ describe( 'crud reducer', () => {
|
||||||
type: TYPES.GET_ITEMS_SUCCESS,
|
type: TYPES.GET_ITEMS_SUCCESS,
|
||||||
items,
|
items,
|
||||||
query,
|
query,
|
||||||
|
urlParameters: [],
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||||
|
@ -79,6 +80,26 @@ describe( 'crud reducer', () => {
|
||||||
expect( state.data[ 2 ] ).toEqual( items[ 1 ] );
|
expect( state.data[ 2 ] ).toEqual( items[ 1 ] );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
it( 'should handle GET_ITEMS_SUCCESS with urlParameters', () => {
|
||||||
|
const items: Item[] = [
|
||||||
|
{ id: 1, name: 'Yum!' },
|
||||||
|
{ id: 2, name: 'Dynamite!' },
|
||||||
|
];
|
||||||
|
const query: Partial< ItemQuery > = { status: 'draft' };
|
||||||
|
const state = reducer( defaultState, {
|
||||||
|
type: TYPES.GET_ITEMS_SUCCESS,
|
||||||
|
items,
|
||||||
|
query,
|
||||||
|
urlParameters: [ 5 ],
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||||
|
|
||||||
|
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
||||||
|
expect( state.data[ '5/1' ] ).toEqual( items[ 0 ] );
|
||||||
|
expect( state.data[ '5/2' ] ).toEqual( items[ 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
it( 'GET_ITEMS_SUCCESS should not remove previously added fields, only update new ones', () => {
|
it( 'GET_ITEMS_SUCCESS should not remove previously added fields, only update new ones', () => {
|
||||||
const initialState: ResourceState = {
|
const initialState: ResourceState = {
|
||||||
...defaultState,
|
...defaultState,
|
||||||
|
@ -102,6 +123,7 @@ describe( 'crud reducer', () => {
|
||||||
type: TYPES.GET_ITEMS_SUCCESS,
|
type: TYPES.GET_ITEMS_SUCCESS,
|
||||||
items,
|
items,
|
||||||
query,
|
query,
|
||||||
|
urlParameters: [],
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||||
|
@ -133,12 +155,12 @@ describe( 'crud reducer', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should handle GET_ITEM_ERROR', () => {
|
it( 'should handle GET_ITEM_ERROR', () => {
|
||||||
const id = 3;
|
const key = 3;
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
|
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEM, { key } );
|
||||||
const error = 'Baaam!';
|
const error = 'Baaam!';
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.GET_ITEM_ERROR,
|
type: TYPES.GET_ITEM_ERROR,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.GET_ITEM,
|
errorType: CRUD_ACTIONS.GET_ITEM,
|
||||||
} );
|
} );
|
||||||
|
@ -147,12 +169,12 @@ describe( 'crud reducer', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should handle GET_ITEM_ERROR', () => {
|
it( 'should handle GET_ITEM_ERROR', () => {
|
||||||
const id = 3;
|
const key = 3;
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
|
const resourceName = getResourceName( CRUD_ACTIONS.GET_ITEM, { key } );
|
||||||
const error = 'Baaam!';
|
const error = 'Baaam!';
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.GET_ITEM_ERROR,
|
type: TYPES.GET_ITEM_ERROR,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.GET_ITEM,
|
errorType: CRUD_ACTIONS.GET_ITEM,
|
||||||
} );
|
} );
|
||||||
|
@ -175,14 +197,14 @@ describe( 'crud reducer', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should handle UPDATE_ITEM_ERROR', () => {
|
it( 'should handle UPDATE_ITEM_ERROR', () => {
|
||||||
const id = 2;
|
const key = 2;
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, {
|
const resourceName = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, {
|
||||||
id,
|
key,
|
||||||
} );
|
} );
|
||||||
const error = 'Baaam!';
|
const error = 'Baaam!';
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.UPDATE_ITEM_ERROR,
|
type: TYPES.UPDATE_ITEM_ERROR,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.UPDATE_ITEM,
|
errorType: CRUD_ACTIONS.UPDATE_ITEM,
|
||||||
} );
|
} );
|
||||||
|
@ -212,7 +234,7 @@ describe( 'crud reducer', () => {
|
||||||
|
|
||||||
const state = reducer( initialState, {
|
const state = reducer( initialState, {
|
||||||
type: TYPES.UPDATE_ITEM_SUCCESS,
|
type: TYPES.UPDATE_ITEM_SUCCESS,
|
||||||
id: item.id,
|
key: item.id,
|
||||||
item,
|
item,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -234,7 +256,7 @@ describe( 'crud reducer', () => {
|
||||||
|
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.CREATE_ITEM_SUCCESS,
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
id: item.id,
|
key: item.id,
|
||||||
item,
|
item,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -267,13 +289,13 @@ describe( 'crud reducer', () => {
|
||||||
|
|
||||||
let state = reducer( initialState, {
|
let state = reducer( initialState, {
|
||||||
type: TYPES.DELETE_ITEM_SUCCESS,
|
type: TYPES.DELETE_ITEM_SUCCESS,
|
||||||
id: item1Updated.id,
|
key: item1Updated.id,
|
||||||
item: item1Updated,
|
item: item1Updated,
|
||||||
force: true,
|
force: true,
|
||||||
} );
|
} );
|
||||||
state = reducer( state, {
|
state = reducer( state, {
|
||||||
type: TYPES.DELETE_ITEM_SUCCESS,
|
type: TYPES.DELETE_ITEM_SUCCESS,
|
||||||
id: item2Updated.id,
|
key: item2Updated.id,
|
||||||
item: item2Updated,
|
item: item2Updated,
|
||||||
force: false,
|
force: false,
|
||||||
} );
|
} );
|
||||||
|
@ -284,14 +306,14 @@ describe( 'crud reducer', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should handle DELETE_ITEM_ERROR', () => {
|
it( 'should handle DELETE_ITEM_ERROR', () => {
|
||||||
const id = 2;
|
const key = 2;
|
||||||
const resourceName = getResourceName( CRUD_ACTIONS.DELETE_ITEM, {
|
const resourceName = getResourceName( CRUD_ACTIONS.DELETE_ITEM, {
|
||||||
id,
|
key,
|
||||||
} );
|
} );
|
||||||
const error = 'Baaam!';
|
const error = 'Baaam!';
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.DELETE_ITEM_ERROR,
|
type: TYPES.DELETE_ITEM_ERROR,
|
||||||
id,
|
key,
|
||||||
error,
|
error,
|
||||||
errorType: CRUD_ACTIONS.DELETE_ITEM,
|
errorType: CRUD_ACTIONS.DELETE_ITEM,
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { createSelectors } from '../selectors';
|
||||||
const selectors = createSelectors( {
|
const selectors = createSelectors( {
|
||||||
resourceName: 'Product',
|
resourceName: 'Product',
|
||||||
pluralResourceName: 'Products',
|
pluralResourceName: 'Products',
|
||||||
|
namespace: '',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
describe( 'crud selectors', () => {
|
describe( 'crud selectors', () => {
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
applyNamespace,
|
||||||
|
cleanQuery,
|
||||||
|
getKey,
|
||||||
|
getNamespaceKeys,
|
||||||
|
getRestPath,
|
||||||
|
getUrlParameters,
|
||||||
|
parseId,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
|
describe( 'utils', () => {
|
||||||
|
it( 'should get the rest path when no parameters are given', () => {
|
||||||
|
const path = getRestPath( 'test/path', {}, [] );
|
||||||
|
expect( path ).toEqual( 'test/path' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should replace the rest path parameters when provided', () => {
|
||||||
|
const path = getRestPath( 'test/{parent}/path', {}, [ 'insert' ] );
|
||||||
|
expect( path ).toEqual( 'test/insert/path' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should replace the rest path parameters when multiple are provided', () => {
|
||||||
|
const path = getRestPath( 'test/{parent}/{other}/path', {}, [
|
||||||
|
'insert',
|
||||||
|
'next',
|
||||||
|
] );
|
||||||
|
expect( path ).toEqual( 'test/insert/next/path' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should throw an error when not all parameters are replaced', () => {
|
||||||
|
expect( () =>
|
||||||
|
getRestPath( 'test/{parent}/{other}/path', {}, [ 'insert' ] )
|
||||||
|
).toThrow( Error );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the key when no parent is provided', () => {
|
||||||
|
const key = getKey( 3, [] );
|
||||||
|
expect( key ).toEqual( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the key when a parent is provided', () => {
|
||||||
|
const key = getKey( 3, [ 5 ] );
|
||||||
|
expect( key ).toEqual( '5/3' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the correct ID information when only an ID is given', () => {
|
||||||
|
const parsed = parseId( 3 );
|
||||||
|
expect( parsed.key ).toEqual( 3 );
|
||||||
|
expect( parsed.id ).toEqual( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the correct ID information when an object is given', () => {
|
||||||
|
const parsed = parseId( { id: 3, parent_id: 5 }, [ 5 ] );
|
||||||
|
expect( parsed.key ).toEqual( '5/3' );
|
||||||
|
expect( parsed.id ).toEqual( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should apply the namespace as an argument to a given function', () => {
|
||||||
|
const namespace = 'test/namespace';
|
||||||
|
const mockedCallback = jest.fn();
|
||||||
|
const wrappedFunction = applyNamespace( mockedCallback, namespace );
|
||||||
|
wrappedFunction( 'a' );
|
||||||
|
|
||||||
|
expect( mockedCallback ).toBeCalledTimes( 1 );
|
||||||
|
expect( mockedCallback ).toBeCalledWith( 'a', 'test/namespace' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the keys from a namespace', () => {
|
||||||
|
const namespace = 'test/{first}/namespace/{second}';
|
||||||
|
const keys = getNamespaceKeys( namespace );
|
||||||
|
expect( keys.length ).toBe( 2 );
|
||||||
|
expect( keys[ 0 ] ).toBe( 'first' );
|
||||||
|
expect( keys[ 1 ] ).toBe( 'second' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return an empty array from a namespace without params', () => {
|
||||||
|
const namespace = 'test/namespace';
|
||||||
|
const keys = getNamespaceKeys( namespace );
|
||||||
|
expect( keys.length ).toBe( 0 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should get the URL parameters given a namespace and query', () => {
|
||||||
|
const namespace = 'test/{my_attribute}/namespace/{other_attribute}';
|
||||||
|
const params = getUrlParameters( namespace, {
|
||||||
|
id: 5,
|
||||||
|
my_attribute: 'flavortown',
|
||||||
|
other_attribute: 'donkeysauce',
|
||||||
|
} );
|
||||||
|
expect( params.length ).toBe( 2 );
|
||||||
|
expect( params[ 0 ] ).toBe( 'flavortown' );
|
||||||
|
expect( params[ 1 ] ).toBe( 'donkeysauce' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return an empty array when no namespace variables are matched', () => {
|
||||||
|
const namespace = 'test/{my_attribute}/namespace';
|
||||||
|
const params = getUrlParameters( namespace, {
|
||||||
|
id: 5,
|
||||||
|
different_attribute: 'flavortown',
|
||||||
|
} );
|
||||||
|
expect( params.length ).toBe( 0 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should remove namespace params from a given query', () => {
|
||||||
|
const namespace = 'test/{my_attribute}/namespace';
|
||||||
|
const query = {
|
||||||
|
other_attribute: 'a',
|
||||||
|
my_attribute: 'b',
|
||||||
|
};
|
||||||
|
const params = cleanQuery( query, namespace );
|
||||||
|
expect( params.other_attribute ).toBe( 'a' );
|
||||||
|
expect( params.my_attribute ).toBeUndefined();
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -14,6 +14,13 @@ import {
|
||||||
|
|
||||||
export type IdType = number | string;
|
export type IdType = number | string;
|
||||||
|
|
||||||
|
export type IdQuery =
|
||||||
|
| IdType
|
||||||
|
| {
|
||||||
|
id: IdType;
|
||||||
|
[ key: string ]: IdType;
|
||||||
|
};
|
||||||
|
|
||||||
export type Item = {
|
export type Item = {
|
||||||
id: IdType;
|
id: IdType;
|
||||||
[ key: string ]: unknown;
|
[ key: string ]: unknown;
|
||||||
|
@ -21,6 +28,11 @@ export type Item = {
|
||||||
|
|
||||||
export type ItemQuery = BaseQueryParams & {
|
export type ItemQuery = BaseQueryParams & {
|
||||||
[ key: string ]: unknown;
|
[ key: string ]: unknown;
|
||||||
|
parent_id?: IdType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Params = {
|
||||||
|
[ key: string ]: IdType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type WithRequiredProperty< Type, Key extends keyof Type > = Type & {
|
type WithRequiredProperty< Type, Key extends keyof Type > = Type & {
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { IdQuery, IdType, ItemQuery } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a REST path given a template path and URL params.
|
||||||
|
*
|
||||||
|
* @param templatePath Path with variable names.
|
||||||
|
* @param query Item query.
|
||||||
|
* @param parameters Array of items to replace in the templatePath.
|
||||||
|
* @return string REST path.
|
||||||
|
*/
|
||||||
|
export const getRestPath = (
|
||||||
|
templatePath: string,
|
||||||
|
query: Partial< ItemQuery >,
|
||||||
|
parameters: IdType[]
|
||||||
|
) => {
|
||||||
|
let path = templatePath;
|
||||||
|
|
||||||
|
path.match( /{(.*?)}/g )?.forEach( ( str, i ) => {
|
||||||
|
path = path.replace( str, parameters[ i ].toString() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
const regex = new RegExp( /{|}/ );
|
||||||
|
if ( regex.test( path.toString() ) ) {
|
||||||
|
throw new Error( 'Not all URL parameters were replaced' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return addQueryArgs( path, query );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a key from an item ID and optional parent.
|
||||||
|
*
|
||||||
|
* @param query Item Query.
|
||||||
|
* @param urlParameters Parameters used for URL.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
export const getKey = ( query: IdQuery, urlParameters: IdType[] = [] ) => {
|
||||||
|
const id =
|
||||||
|
typeof query === 'string' || typeof query === 'number'
|
||||||
|
? query
|
||||||
|
: query.id;
|
||||||
|
|
||||||
|
if ( ! urlParameters.length ) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = '';
|
||||||
|
urlParameters.forEach( ( param ) => {
|
||||||
|
prefix = param + '/';
|
||||||
|
} );
|
||||||
|
|
||||||
|
return `${ prefix }${ id }`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an ID query into a ID string.
|
||||||
|
*
|
||||||
|
* @param query Id Query
|
||||||
|
* @return string ID.
|
||||||
|
*/
|
||||||
|
export const parseId = ( query: IdQuery, urlParameters: IdType[] = [] ) => {
|
||||||
|
if ( typeof query === 'string' || typeof query === 'number' ) {
|
||||||
|
return {
|
||||||
|
id: query,
|
||||||
|
key: query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: query.id,
|
||||||
|
key: getKey( query, urlParameters ),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new function that adds in the namespace.
|
||||||
|
*
|
||||||
|
* @param fn Function to wrap.
|
||||||
|
* @param namespace Namespace to pass to last argument of function.
|
||||||
|
* @return Wrapped function
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const applyNamespace = < T extends ( ...args: any[] ) => unknown >(
|
||||||
|
fn: T,
|
||||||
|
namespace: string
|
||||||
|
) => {
|
||||||
|
return ( ...args: Parameters< T > ) => {
|
||||||
|
return fn( ...args, namespace );
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key names from a namespace string.
|
||||||
|
*
|
||||||
|
* @param namespace Namespace to get keys from.
|
||||||
|
* @return Array of keys.
|
||||||
|
*/
|
||||||
|
export const getNamespaceKeys = ( namespace: string ) => {
|
||||||
|
const keys: string[] = [];
|
||||||
|
|
||||||
|
namespace.match( /{(.*?)}/g )?.forEach( ( match ) => {
|
||||||
|
const key = match.substr( 1, match.length - 2 );
|
||||||
|
keys.push( key );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL parameters from namespace and provided query.
|
||||||
|
*
|
||||||
|
* @param namespace Namespace string to replace params in.
|
||||||
|
* @param query Query object with key values.
|
||||||
|
* @return Array of URL parameter values.
|
||||||
|
*/
|
||||||
|
export const getUrlParameters = (
|
||||||
|
namespace: string,
|
||||||
|
query: IdQuery | Partial< ItemQuery >
|
||||||
|
) => {
|
||||||
|
if ( typeof query !== 'object' ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: IdType[] = [];
|
||||||
|
const keys = getNamespaceKeys( namespace );
|
||||||
|
keys.forEach( ( key ) => {
|
||||||
|
if ( query.hasOwnProperty( key ) ) {
|
||||||
|
params.push( query[ key ] as IdType );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean a query of all namespaced params.
|
||||||
|
*
|
||||||
|
* @param query Query to clean.
|
||||||
|
* @param namespace
|
||||||
|
* @return Cleaned query object.
|
||||||
|
*/
|
||||||
|
export const cleanQuery = (
|
||||||
|
query: Partial< ItemQuery >,
|
||||||
|
namespace: string
|
||||||
|
) => {
|
||||||
|
const cleaned = { ...query };
|
||||||
|
|
||||||
|
const keys = getNamespaceKeys( namespace );
|
||||||
|
keys.forEach( ( key ) => {
|
||||||
|
delete cleaned[ key ];
|
||||||
|
} );
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
};
|
|
@ -22,6 +22,7 @@ export { EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME } from './product-attributes
|
||||||
export { EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME } from './product-shipping-classes';
|
export { EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME } from './product-shipping-classes';
|
||||||
export { EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME } from './shipping-zones';
|
export { EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME } from './shipping-zones';
|
||||||
export { EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME } from './product-tags';
|
export { EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME } from './product-tags';
|
||||||
|
export { EXPERIMENTAL_PRODUCT_ATTRIBUTE_TERMS_STORE_NAME } from './product-attribute-terms';
|
||||||
export { PaymentGateway } from './payment-gateways/types';
|
export { PaymentGateway } from './payment-gateways/types';
|
||||||
|
|
||||||
// Export hooks
|
// Export hooks
|
||||||
|
@ -97,6 +98,7 @@ import type { EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME } from './product-attri
|
||||||
import type { EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME } from './product-shipping-classes';
|
import type { EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME } from './product-shipping-classes';
|
||||||
import type { EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME } from './shipping-zones';
|
import type { EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME } from './shipping-zones';
|
||||||
import type { EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME } from './product-tags';
|
import type { EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME } from './product-tags';
|
||||||
|
import type { EXPERIMENTAL_PRODUCT_ATTRIBUTE_TERMS_STORE_NAME } from './product-attribute-terms';
|
||||||
|
|
||||||
export type WCDataStoreName =
|
export type WCDataStoreName =
|
||||||
| typeof REVIEWS_STORE_NAME
|
| typeof REVIEWS_STORE_NAME
|
||||||
|
@ -114,6 +116,7 @@ export type WCDataStoreName =
|
||||||
| typeof PRODUCTS_STORE_NAME
|
| typeof PRODUCTS_STORE_NAME
|
||||||
| typeof ORDERS_STORE_NAME
|
| typeof ORDERS_STORE_NAME
|
||||||
| typeof EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME
|
| typeof EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME
|
||||||
|
| typeof EXPERIMENTAL_PRODUCT_ATTRIBUTE_TERMS_STORE_NAME
|
||||||
| typeof EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME
|
| typeof EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME
|
||||||
| typeof EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME
|
| typeof EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME
|
||||||
| typeof EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME;
|
| typeof EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME;
|
||||||
|
@ -132,6 +135,7 @@ import { ProductAttributeSelectors } from './product-attributes/types';
|
||||||
import { ProductShippingClassSelectors } from './product-shipping-classes/types';
|
import { ProductShippingClassSelectors } from './product-shipping-classes/types';
|
||||||
import { ShippingZonesSelectors } from './shipping-zones/types';
|
import { ShippingZonesSelectors } from './shipping-zones/types';
|
||||||
import { ProductTagSelectors } from './product-tags/types';
|
import { ProductTagSelectors } from './product-tags/types';
|
||||||
|
import { ProductAttributeTermsSelectors } from './product-attribute-terms/types';
|
||||||
|
|
||||||
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one
|
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one
|
||||||
// of the already typed selectors for an example of how you can do this.
|
// of the already typed selectors for an example of how you can do this.
|
||||||
|
@ -167,6 +171,8 @@ export type WCSelectorType< T > = T extends typeof REVIEWS_STORE_NAME
|
||||||
? ProductShippingClassSelectors
|
? ProductShippingClassSelectors
|
||||||
: T extends typeof EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME
|
: T extends typeof EXPERIMENTAL_PRODUCT_TAGS_STORE_NAME
|
||||||
? ProductTagSelectors
|
? ProductTagSelectors
|
||||||
|
: T extends typeof EXPERIMENTAL_PRODUCT_ATTRIBUTE_TERMS_STORE_NAME
|
||||||
|
? ProductAttributeTermsSelectors
|
||||||
: T extends typeof ORDERS_STORE_NAME
|
: T extends typeof ORDERS_STORE_NAME
|
||||||
? OrdersSelectors
|
? OrdersSelectors
|
||||||
: T extends typeof EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME
|
: T extends typeof EXPERIMENTAL_SHIPPING_ZONES_STORE_NAME
|
||||||
|
@ -181,6 +187,7 @@ export interface WCDataSelector {
|
||||||
export { ActionDispatchers as PluginsStoreActions } from './plugins/actions';
|
export { ActionDispatchers as PluginsStoreActions } from './plugins/actions';
|
||||||
export { ActionDispatchers as ProductAttributesActions } from './product-attributes/types';
|
export { ActionDispatchers as ProductAttributesActions } from './product-attributes/types';
|
||||||
export { ActionDispatchers as ProductTagsActions } from './product-tags/types';
|
export { ActionDispatchers as ProductTagsActions } from './product-tags/types';
|
||||||
|
export { ActionDispatchers as ProductAttributeTermsActions } from './product-attribute-terms/types';
|
||||||
export { ActionDispatchers as ProductsStoreActions } from './products/actions';
|
export { ActionDispatchers as ProductsStoreActions } from './products/actions';
|
||||||
export { ActionDispatchers as ProductShippingClassesActions } from './product-shipping-classes/types';
|
export { ActionDispatchers as ProductShippingClassesActions } from './product-shipping-classes/types';
|
||||||
export { ActionDispatchers as ShippingZonesActions } from './shipping-zones/types';
|
export { ActionDispatchers as ShippingZonesActions } from './shipping-zones/types';
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const STORE_NAME = 'wc/admin/products/attributes/terms';
|
||||||
|
|
||||||
|
export const WC_PRODUCT_ATTRIBUTE_TERMS_NAMESPACE =
|
||||||
|
'/wc/v3/products/attributes/{attribute_id}/terms';
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { STORE_NAME, WC_PRODUCT_ATTRIBUTE_TERMS_NAMESPACE } from './constants';
|
||||||
|
import { createCrudDataStore } from '../crud';
|
||||||
|
|
||||||
|
createCrudDataStore( {
|
||||||
|
storeName: STORE_NAME,
|
||||||
|
resourceName: 'ProductAttributeTerm',
|
||||||
|
pluralResourceName: 'ProductAttributeTerms',
|
||||||
|
namespace: WC_PRODUCT_ATTRIBUTE_TERMS_NAMESPACE,
|
||||||
|
} );
|
||||||
|
|
||||||
|
export const EXPERIMENTAL_PRODUCT_ATTRIBUTE_TERMS_STORE_NAME = STORE_NAME;
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { DispatchFromMap } from '@automattic/data-stores';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { CrudActions, CrudSelectors } from '../crud/types';
|
||||||
|
|
||||||
|
type ProductAttributeTerm = {
|
||||||
|
id: number;
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
menu_order: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Query = {
|
||||||
|
context: string;
|
||||||
|
page: number;
|
||||||
|
per_page: number;
|
||||||
|
search: string;
|
||||||
|
exclude: number[];
|
||||||
|
include: number[];
|
||||||
|
offset: number;
|
||||||
|
order: string;
|
||||||
|
orderby: string;
|
||||||
|
hide_empty: boolean;
|
||||||
|
product: number;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadOnlyProperties = 'id' | 'count';
|
||||||
|
|
||||||
|
type MutableProperties = Partial<
|
||||||
|
Omit< ProductAttributeTerm, ReadOnlyProperties >
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ProductAttributeTermActions = CrudActions<
|
||||||
|
'ProductAttributeTerm',
|
||||||
|
ProductAttributeTerm,
|
||||||
|
MutableProperties,
|
||||||
|
'name'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type ProductAttributeTermsSelectors = CrudSelectors<
|
||||||
|
'ProductAttributeTerm',
|
||||||
|
'ProductAttributeTerms',
|
||||||
|
ProductAttributeTerm,
|
||||||
|
Query,
|
||||||
|
MutableProperties
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type ActionDispatchers = DispatchFromMap< ProductAttributeTermActions >;
|
Loading…
Reference in New Issue