From 6f0490d324b079d37cb38d898e3d6f5d0e34ba2f Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 20 Sep 2023 17:26:56 +1000 Subject: [PATCH] getEntityRecord Updating resolver --- packages/core-data/src/resolvers.js | 102 ++++++++++++++++++++-------- packages/core-data/src/selectors.ts | 80 ++++++++++++++-------- 2 files changed, 126 insertions(+), 56 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index dfd77fc6b369b1..fe6d20a6bdc86d 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -58,9 +58,14 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { select, dispatch } ) => { const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + // @TODO Create predictable parsing rules for names like post:[key]:revisions. + const splitName = name.split( ':' )[ 0 ]; const entityConfig = configs.find( - ( config ) => config.name === name && config.kind === kind + ( config ) => config.name === splitName && config.kind === kind ); + const isRevisionEntityRecord = + entityConfig?.supports?.revisions && + name.split( ':' )?.[ 2 ] === 'revisions'; if ( ! entityConfig || entityConfig?.__experimentalNoFetch ) { return; } @@ -73,10 +78,12 @@ export const getEntityRecord = try { // Entity supports configs, - // use the sync algorithm instead of the old fetch behavior. + // use the sync algorithm instead of the old fetch behavior, + // but not for revisions. @TODO check this. if ( window.__experimentalEnableSync && entityConfig.syncConfig && + ! isRevisionEntityRecord && ! query ) { const objectId = entityConfig.getSyncObjectId( key ); @@ -136,33 +143,71 @@ export const getEntityRecord = // modifications are relevant to how the data is tracked in state, and not // for how the request is made to the REST API. - // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const path = addQueryArgs( - entityConfig.baseURL + ( key ? '/' + key : '' ), - { - ...entityConfig.baseURLParams, - ...query, - } - ); + // @TODO this is a mess. + // @TODO Create predictable URL building rules for names like post:[key]:revisions. + // @TODO Possibly `entityConfig.getRevisionsUrl( { name } )? + let path; + if ( isRevisionEntityRecord ) { + const [ , parentKey ] = name.split( ':' ); + path = addQueryArgs( + `${ entityConfig.baseURL }/${ parentKey }/revisions${ + key ? '/' + key : '' + }`, + { + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', + ...query, + } + ); + } else { + path = addQueryArgs( + entityConfig.baseURL + ( key ? '/' + key : '' ), + { + ...entityConfig.baseURLParams, + ...query, + } + ); + } if ( query !== undefined ) { - query = { ...query, include: [ key ] }; + query = isRevisionEntityRecord + ? { context: 'view', ...query, include: [ key ] } + : { ...query, include: [ key ] }; // The resolution cache won't consider query as reusable based on the // fields, so it's tested here, prior to initiating the REST request, // and without causing `getEntityRecords` resolution to occur. + // @TODO how to handle revisions here? + // @TODO will it know if a new revision has been created? const hasRecords = select.hasEntityRecords( kind, name, query ); + if ( hasRecords ) { return; } } const record = await apiFetch( { path } ); - dispatch.receiveEntityRecords( kind, name, record, query ); + // @TODO just dispatching here to send the action type. + if ( isRevisionEntityRecord ) { + dispatch( { + type: 'RECEIVE_ITEM_REVISIONS', + kind, + name, + items: [ record ], + query: { + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', + ...query, + }, + invalidateCache: false, + } ); + } else { + dispatch.receiveEntityRecords( kind, name, record, query ); + } } } finally { dispatch.__unstableReleaseStoreLock( lock ); @@ -236,13 +281,11 @@ export const getEntityRecords = path = addQueryArgs( `${ entityConfig.baseURL }/${ parentKey }/revisions`, { - ...{ - // @TODO Default query params for revisions should be defined in the entity config? - order: 'desc', - orderby: 'date', - // @TODO check if this is the default for revisions (should be view?). Is there anything else? - context: 'view', - }, + // @TODO Default query params for revisions should be defined in the entity config? + order: 'desc', + orderby: 'date', + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', ...query, } ); @@ -278,13 +321,11 @@ export const getEntityRecords = name, items: records, query: { - ...{ - // @TODO Default query params for revisions should be defined in the entity config? - order: 'desc', - orderby: 'date', - // @TODO check if this is the default for revisions (should be view?). Is there anything else? - context: 'view', - }, + // @TODO Default query params for revisions should be defined in the entity config? + order: 'desc', + orderby: 'date', + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', ...query, }, invalidateCache: false, @@ -317,13 +358,16 @@ export const getEntityRecords = dispatch.__unstableReleaseStoreLock( lock ); } }; - +// @TODO work out how to invalidate revisions. At the moment, adding a new post revisions doesn't update the state. getEntityRecords.shouldInvalidate = ( action, kind, name ) => { + const splitName = name.split( ':' )[ 0 ]; return ( - ( action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS' ) && + ( action.type === 'RECEIVE_ITEMS' || + action.type === 'REMOVE_ITEMS' || + action.type === 'RECEIVE_ITEM_REVISIONS' ) && action.invalidateCache && kind === action.kind && - name === action.name + splitName === action.name ); }; diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 6f71362435009c..3e46534863472f 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -321,14 +321,32 @@ export const getEntityRecord = createSelector( key: EntityRecordKey, query?: GetRecordsHttpQuery ): EntityRecord | undefined => { - const queriedState = - state.entities.records?.[ kind ]?.[ name ]?.queriedData; + // @TODO this is a mess. + // @TODO Create predictable parsing rules for names like post:[key]:revisions. + // @TODO update the resolver to fetch the revision item. + const splitName = name?.split( ':' ); + const isRevision = splitName?.[ 2 ] === 'revisions'; + + const queriedState = isRevision + ? state.entities.records?.[ kind ]?.[ splitName[ 0 ] ]?.revisions[ + splitName[ 1 ] + ] + : state.entities.records?.[ kind ]?.[ name ]?.queriedData; if ( ! queriedState ) { return undefined; } - const context = query?.context ?? 'default'; - if ( query === undefined ) { + const queryParams = isRevision + ? { + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', + ...query, + } + : query; + + const context = queryParams?.context ?? 'default'; + + if ( queryParams === undefined ) { // If expecting a complete item, validate that completeness. if ( ! queriedState.itemIsComplete[ context ]?.[ key ] ) { return undefined; @@ -338,9 +356,10 @@ export const getEntityRecord = createSelector( } const item = queriedState.items[ context ]?.[ key ]; - if ( item && query._fields ) { + if ( item && queryParams._fields ) { const filteredItem = {}; - const fields = getNormalizedCommaSeparable( query._fields ) ?? []; + const fields = + getNormalizedCommaSeparable( queryParams._fields ) ?? []; for ( let f = 0; f < fields.length; f++ ) { const field = fields[ f ].split( '.' ); let value = item; @@ -355,13 +374,33 @@ export const getEntityRecord = createSelector( return item; } ) as GetEntityRecord, ( state: State, kind, name, recordId, query ) => { - const context = query?.context ?? 'default'; + // @TODO this is a mess. + // @TODO Create predictable parsing rules for names like post:[key]:revisions. + const splitName = name?.split( ':' ); + const isRevision = splitName?.[ 2 ] === 'revisions'; + const queryParams = isRevision + ? { + // @TODO check if this is the default for revisions (should be view?). Is there anything else? + context: 'view', + ...query, + } + : query; + + const context = queryParams?.context ?? 'default'; + return [ - state.entities.records?.[ kind ]?.[ name ]?.queriedData?.items[ - context - ]?.[ recordId ], - state.entities.records?.[ kind ]?.[ name ]?.queriedData - ?.itemIsComplete[ context ]?.[ recordId ], + isRevision + ? state.entities.records?.[ kind ]?.[ splitName[ 0 ] ] + ?.revisions[ splitName[ 1 ] ]?.items[ recordId ] + : state.entities.records?.[ kind ]?.[ name ]?.queriedData + ?.items[ context ]?.[ recordId ], + isRevision + ? state.entities.records?.[ kind ]?.[ splitName[ 0 ] ] + ?.revisions[ splitName[ 1 ] ]?.itemIsComplete[ + context + ]?.[ recordId ] + : state.entities.records?.[ kind ]?.[ name ]?.queriedData + ?.itemIsComplete[ context ]?.[ recordId ], ]; } ) as GetEntityRecord; @@ -518,12 +557,13 @@ export const getEntityRecords = ( < // assigned for the given parameters, then it is known to not exist. // @TODO this is a mess. // @TODO Create predictable parsing rules for names like post:[key]:revisions. - const splitName = name.split( ':' ); + const splitName = name?.split( ':' ); if ( splitName?.[ 2 ] === 'revisions' ) { const queriedStateRevisions = state.entities.records?.[ kind ]?.[ splitName[ 0 ] ]?.revisions[ splitName[ 1 ] ]; + if ( ! queriedStateRevisions ) { return null; } @@ -534,20 +574,6 @@ export const getEntityRecords = ( < // @TODO check if this is the default for revisions (should be view?). Is there anything else? context: 'view', }; - const test = getQueriedItems( - queriedStateRevisions, - { - ...{ - // @TODO Default query params for revisions should be defined in the entity config? - order: 'desc', - orderby: 'date', - // @TODO check if this is the default for revisions (should be view?). Is there anything else? - context: 'default', - }, - ...query, - }, - splitName[ 1 ] - ); return getQueriedItems( queriedStateRevisions, { ...query,