Skip to content

Commit 9e23649

Browse files
Allow making context specific requests using the data module (#32961)
Co-authored-by: Nik Tsekouras <ntsekouras@outlook.com>
1 parent 74a8873 commit 9e23649

File tree

11 files changed

+360
-188
lines changed

11 files changed

+360
-188
lines changed

packages/core-data/src/queried-data/get-query-parts.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function getQueryParts( query ) {
4141
perPage: 10,
4242
fields: null,
4343
include: null,
44+
context: 'default',
4445
};
4546

4647
// Ensure stable key by sorting keys. Also more efficient for iterating.
@@ -65,6 +66,10 @@ export function getQueryParts( query ) {
6566
);
6667
break;
6768

69+
case 'context':
70+
parts.context = value;
71+
break;
72+
6873
default:
6974
// While in theory, we could exclude "_fields" from the stableKey
7075
// because two request with different fields have the same results

packages/core-data/src/queried-data/reducer.js

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* External dependencies
33
*/
4-
import { map, flowRight, omit, forEach, filter } from 'lodash';
4+
import { map, flowRight, omit, filter, mapValues } from 'lodash';
55

66
/**
77
* WordPress dependencies
@@ -20,6 +20,16 @@ import {
2020
import { DEFAULT_ENTITY_KEY } from '../entities';
2121
import getQueryParts from './get-query-parts';
2222

23+
function getContextFromAction( action ) {
24+
const { query } = action;
25+
if ( ! query ) {
26+
return 'default';
27+
}
28+
29+
const queryParts = getQueryParts( query );
30+
return queryParts.context;
31+
}
32+
2333
/**
2434
* Returns a merged array of item IDs, given details of the received paginated
2535
* items. The array is sparse-like with `undefined` entries where holes exist.
@@ -71,24 +81,30 @@ export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) {
7181
*
7282
* @return {Object} Next state.
7383
*/
74-
function items( state = {}, action ) {
84+
export function items( state = {}, action ) {
7585
switch ( action.type ) {
76-
case 'RECEIVE_ITEMS':
86+
case 'RECEIVE_ITEMS': {
87+
const context = getContextFromAction( action );
7788
const key = action.key || DEFAULT_ENTITY_KEY;
7889
return {
7990
...state,
80-
...action.items.reduce( ( accumulator, value ) => {
81-
const itemId = value[ key ];
82-
accumulator[ itemId ] = conservativeMapItem(
83-
state[ itemId ],
84-
value
85-
);
86-
return accumulator;
87-
}, {} ),
91+
[ context ]: {
92+
...state[ context ],
93+
...action.items.reduce( ( accumulator, value ) => {
94+
const itemId = value[ key ];
95+
accumulator[ itemId ] = conservativeMapItem(
96+
state?.[ context ]?.[ itemId ],
97+
value
98+
);
99+
return accumulator;
100+
}, {} ),
101+
},
88102
};
103+
}
89104
case 'REMOVE_ITEMS':
90-
const newState = omit( state, action.itemIds );
91-
return newState;
105+
return mapValues( state, ( contextState ) =>
106+
omit( contextState, action.itemIds )
107+
);
92108
}
93109
return state;
94110
}
@@ -106,32 +122,45 @@ function items( state = {}, action ) {
106122
* @return {Object<string,boolean>} Next state.
107123
*/
108124
export function itemIsComplete( state = {}, action ) {
109-
const { type, query, key = DEFAULT_ENTITY_KEY } = action;
110-
if ( type !== 'RECEIVE_ITEMS' ) {
111-
return state;
125+
switch ( action.type ) {
126+
case 'RECEIVE_ITEMS': {
127+
const context = getContextFromAction( action );
128+
const { query, key = DEFAULT_ENTITY_KEY } = action;
129+
130+
// An item is considered complete if it is received without an associated
131+
// fields query. Ideally, this would be implemented in such a way where the
132+
// complete aggregate of all fields would satisfy completeness. Since the
133+
// fields are not consistent across all entity types, this would require
134+
// introspection on the REST schema for each entity to know which fields
135+
// compose a complete item for that entity.
136+
const queryParts = query ? getQueryParts( query ) : {};
137+
const isCompleteQuery =
138+
! query || ! Array.isArray( queryParts.fields );
139+
140+
return {
141+
...state,
142+
[ context ]: {
143+
...state[ context ],
144+
...action.items.reduce( ( result, item ) => {
145+
const itemId = item[ key ];
146+
147+
// Defer to completeness if already assigned. Technically the
148+
// data may be outdated if receiving items for a field subset.
149+
result[ itemId ] =
150+
state?.[ context ]?.[ itemId ] || isCompleteQuery;
151+
152+
return result;
153+
}, {} ),
154+
},
155+
};
156+
}
157+
case 'REMOVE_ITEMS':
158+
return mapValues( state, ( contextState ) =>
159+
omit( contextState, action.itemIds )
160+
);
112161
}
113162

114-
// An item is considered complete if it is received without an associated
115-
// fields query. Ideally, this would be implemented in such a way where the
116-
// complete aggregate of all fields would satisfy completeness. Since the
117-
// fields are not consistent across all entity types, this would require
118-
// introspection on the REST schema for each entity to know which fields
119-
// compose a complete item for that entity.
120-
const isCompleteQuery =
121-
! query || ! Array.isArray( getQueryParts( query ).fields );
122-
123-
return {
124-
...state,
125-
...action.items.reduce( ( result, item ) => {
126-
const itemId = item[ key ];
127-
128-
// Defer to completeness if already assigned. Technically the
129-
// data may be outdated if receiving items for a field subset.
130-
result[ itemId ] = state[ itemId ] || isCompleteQuery;
131-
132-
return result;
133-
}, {} ),
134-
};
163+
return state;
135164
}
136165

137166
/**
@@ -163,6 +192,8 @@ const receiveQueries = flowRight( [
163192
return action;
164193
} ),
165194

195+
onSubKey( 'context' ),
196+
166197
// Queries shape is shared, but keyed by query `stableKey` part. Original
167198
// reducer tracks only a single query object.
168199
onSubKey( 'stableKey' ),
@@ -194,17 +225,18 @@ const queries = ( state = {}, action ) => {
194225
case 'RECEIVE_ITEMS':
195226
return receiveQueries( state, action );
196227
case 'REMOVE_ITEMS':
197-
const newState = { ...state };
198228
const removedItems = action.itemIds.reduce( ( result, itemId ) => {
199229
result[ itemId ] = true;
200230
return result;
201231
}, {} );
202-
forEach( newState, ( queryItems, key ) => {
203-
newState[ key ] = filter( queryItems, ( queryId ) => {
204-
return ! removedItems[ queryId ];
232+
233+
return mapValues( state, ( contextQueries ) => {
234+
return mapValues( contextQueries, ( queryItems ) => {
235+
return filter( queryItems, ( queryId ) => {
236+
return ! removedItems[ queryId ];
237+
} );
205238
} );
206239
} );
207-
return newState;
208240
default:
209241
return state;
210242
}

packages/core-data/src/queried-data/selectors.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ const queriedItemsCacheByState = new WeakMap();
2828
* @return {?Array} Query items.
2929
*/
3030
function getQueriedItemsUncached( state, query ) {
31-
const { stableKey, page, perPage, include, fields } = getQueryParts(
32-
query
33-
);
31+
const {
32+
stableKey,
33+
page,
34+
perPage,
35+
include,
36+
fields,
37+
context,
38+
} = getQueryParts( query );
3439
let itemIds;
40+
3541
if ( Array.isArray( include ) && ! stableKey ) {
3642
// If the parsed query yields a set of IDs, but otherwise no filtering,
3743
// it's safe to consider targeted item IDs as the include set. This
@@ -40,8 +46,8 @@ function getQueriedItemsUncached( state, query ) {
4046
itemIds = include;
4147
// TODO: Avoid storing the empty stable string in reducer, since it
4248
// can be computed dynamically here always.
43-
} else if ( state.queries[ stableKey ] ) {
44-
itemIds = state.queries[ stableKey ];
49+
} else if ( state.queries?.[ context ]?.[ stableKey ] ) {
50+
itemIds = state.queries[ context ][ stableKey ];
4551
}
4652

4753
if ( ! itemIds ) {
@@ -61,11 +67,11 @@ function getQueriedItemsUncached( state, query ) {
6167
continue;
6268
}
6369

64-
if ( ! state.items.hasOwnProperty( itemId ) ) {
70+
if ( ! state.items[ context ]?.hasOwnProperty( itemId ) ) {
6571
return null;
6672
}
6773

68-
const item = state.items[ itemId ];
74+
const item = state.items[ context ][ itemId ];
6975

7076
let filteredItem;
7177
if ( Array.isArray( fields ) ) {
@@ -79,7 +85,7 @@ function getQueriedItemsUncached( state, query ) {
7985
} else {
8086
// If expecting a complete item, validate that completeness, or
8187
// otherwise abort.
82-
if ( ! state.itemIsComplete[ itemId ] ) {
88+
if ( ! state.itemIsComplete[ context ]?.[ itemId ] ) {
8389
return null;
8490
}
8591

packages/core-data/src/queried-data/test/get-query-parts.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ describe( 'getQueryParts', () => {
88
const parts = getQueryParts( { page: 2, per_page: 2 } );
99

1010
expect( parts ).toEqual( {
11+
context: 'default',
1112
page: 2,
1213
perPage: 2,
1314
stableKey: '',
@@ -20,6 +21,7 @@ describe( 'getQueryParts', () => {
2021
const parts = getQueryParts( { include: [ 1 ] } );
2122

2223
expect( parts ).toEqual( {
24+
context: 'default',
2325
page: 1,
2426
perPage: 10,
2527
stableKey: '',
@@ -34,6 +36,7 @@ describe( 'getQueryParts', () => {
3436

3537
expect( first ).toEqual( second );
3638
expect( first ).toEqual( {
39+
context: 'default',
3740
page: 1,
3841
perPage: 10,
3942
stableKey: '%3F=%26&b=2',
@@ -46,6 +49,7 @@ describe( 'getQueryParts', () => {
4649
const parts = getQueryParts( { a: [ 1, 2 ] } );
4750

4851
expect( parts ).toEqual( {
52+
context: 'default',
4953
page: 1,
5054
perPage: 10,
5155
stableKey: 'a%5B0%5D=1&a%5B1%5D=2',
@@ -60,6 +64,7 @@ describe( 'getQueryParts', () => {
6064

6165
expect( first ).toEqual( second );
6266
expect( first ).toEqual( {
67+
context: 'default',
6368
page: 1,
6469
perPage: 10,
6570
stableKey: 'b=2',
@@ -72,6 +77,7 @@ describe( 'getQueryParts', () => {
7277
const parts = getQueryParts( { b: 2, page: 1, per_page: -1 } );
7378

7479
expect( parts ).toEqual( {
80+
context: 'default',
7581
page: 1,
7682
perPage: -1,
7783
stableKey: 'b=2',
@@ -84,11 +90,25 @@ describe( 'getQueryParts', () => {
8490
const parts = getQueryParts( { _fields: [ 'id', 'title' ] } );
8591

8692
expect( parts ).toEqual( {
93+
context: 'default',
8794
page: 1,
8895
perPage: 10,
8996
stableKey: '_fields=id%2Ctitle',
9097
fields: [ 'id', 'title' ],
9198
include: null,
9299
} );
93100
} );
101+
102+
it( 'returns the context as a dedicated query part', () => {
103+
const parts = getQueryParts( { context: 'view' } );
104+
105+
expect( parts ).toEqual( {
106+
page: 1,
107+
perPage: 10,
108+
stableKey: '',
109+
include: null,
110+
fields: null,
111+
context: 'view',
112+
} );
113+
} );
94114
} );

0 commit comments

Comments
 (0)