Skip to content

Commit

Permalink
Core Data: Add support for autosaving entities.
Browse files Browse the repository at this point in the history
  • Loading branch information
epiqueras committed Aug 5, 2019
1 parent a22e340 commit 9291845
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 23 deletions.
19 changes: 18 additions & 1 deletion docs/designers-developers/developers/data/data-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,21 @@ _Returns_

- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made.

<a name="isAutosavingEntityRecord" href="#isAutosavingEntityRecord">#</a> **isAutosavingEntityRecord**

Returns true if the specified entity record is autosaving, and false otherwise.

_Parameters_

- _state_ `Object`: State tree.
- _kind_ `string`: Entity kind.
- _name_ `string`: Entity name.
- _recordId_ `number`: Record ID.

_Returns_

- `boolean`: Whether the entity record is autosaving or not.

<a name="isPreviewEmbedFallback" href="#isPreviewEmbedFallback">#</a> **isPreviewEmbedFallback**

Determines if the returned preview is an oEmbed link fallback.
Expand Down Expand Up @@ -387,7 +402,7 @@ _Parameters_

_Returns_

- `?Object`: Whether the entity record is saving or not.
- `boolean`: Whether the entity record is saving or not.


<!-- END TOKEN(Autogenerated selectors) -->
Expand Down Expand Up @@ -545,6 +560,7 @@ _Parameters_
- _kind_ `string`: Kind of the entity.
- _name_ `string`: Name of the entity.
- _recordId_ `Object`: ID of the record.
- _options_ `Object`: Saving options.

<a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord**

Expand All @@ -555,6 +571,7 @@ _Parameters_
- _kind_ `string`: Kind of the received entity.
- _name_ `string`: Name of the received entity.
- _record_ `Object`: Record to be saved.
- _options_ `Object`: Saving options.

<a name="undo" href="#undo">#</a> **undo**

Expand Down
19 changes: 18 additions & 1 deletion packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ _Parameters_
- _kind_ `string`: Kind of the entity.
- _name_ `string`: Name of the entity.
- _recordId_ `Object`: ID of the record.
- _options_ `Object`: Saving options.

<a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord**

Expand All @@ -201,6 +202,7 @@ _Parameters_
- _kind_ `string`: Kind of the received entity.
- _name_ `string`: Name of the received entity.
- _record_ `Object`: Record to be saved.
- _options_ `Object`: Saving options.

<a name="undo" href="#undo">#</a> **undo**

Expand Down Expand Up @@ -552,6 +554,21 @@ _Returns_

- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made.

<a name="isAutosavingEntityRecord" href="#isAutosavingEntityRecord">#</a> **isAutosavingEntityRecord**

Returns true if the specified entity record is autosaving, and false otherwise.

_Parameters_

- _state_ `Object`: State tree.
- _kind_ `string`: Entity kind.
- _name_ `string`: Entity name.
- _recordId_ `number`: Record ID.

_Returns_

- `boolean`: Whether the entity record is autosaving or not.

<a name="isPreviewEmbedFallback" href="#isPreviewEmbedFallback">#</a> **isPreviewEmbedFallback**

Determines if the returned preview is an oEmbed link fallback.
Expand Down Expand Up @@ -596,7 +613,7 @@ _Parameters_

_Returns_

- `?Object`: Whether the entity record is saving or not.
- `boolean`: Whether the entity record is saving or not.


<!-- END TOKEN(Autogenerated selectors) -->
Expand Down
84 changes: 67 additions & 17 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { castArray, merge, isEqual, find } from 'lodash';
import { castArray, get, merge, isEqual, find } from 'lodash';

/**
* Internal dependencies
Expand Down Expand Up @@ -127,7 +127,11 @@ export function receiveEmbedPreview( url, preview ) {
* @return {Object} Action object.
*/
export function* editEntityRecord( kind, name, recordId, edits ) {
const { transientEdits = {}, mergedEdits = {} } = yield select( 'getEntity', kind, name );
const { transientEdits = {}, mergedEdits = {} } = yield select(
'getEntity',
kind,
name
);
const record = yield select( 'getEntityRecord', kind, name, recordId );
const editedRecord = yield select(
'getEditedEntityRecord',
Expand All @@ -143,10 +147,11 @@ export function* editEntityRecord( kind, name, recordId, edits ) {
// Clear edits when they are equal to their persisted counterparts
// so that the property is not considered dirty.
edits: Object.keys( edits ).reduce( ( acc, key ) => {
const recordValue = get( record[ key ], 'raw', record[ key ] );
const value = mergedEdits[ key ] ?
merge( record[ key ], edits[ key ] ) :
merge( recordValue, edits[ key ] ) :
edits[ key ];
acc[ key ] = isEqual( record[ key ], value ) ? undefined : value;
acc[ key ] = isEqual( recordValue, value ) ? undefined : value;
return acc;
}, {} ),
transientEdits,
Expand Down Expand Up @@ -209,29 +214,73 @@ export function* redo() {
* @param {string} kind Kind of the received entity.
* @param {string} name Name of the received entity.
* @param {Object} record Record to be saved.
* @param {Object} options Saving options.
*/
export function* saveEntityRecord( kind, name, record ) {
export function* saveEntityRecord(
kind,
name,
record,
{ isAutosave = false } = { isAutosave: false }
) {
const entities = yield getKindEntities( kind );
const entity = find( entities, { kind, name } );
if ( ! entity ) {
return;
}
const key = entity.key || DEFAULT_ENTITY_KEY;
const recordId = record[ key ];
const entityIdKey = entity.key || DEFAULT_ENTITY_KEY;
const recordId = record[ entityIdKey ];

yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId };
yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId, isAutosave };
let error;
try {
const updatedRecord = yield apiFetch( {
path: `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`,
method: recordId ? 'PUT' : 'POST',
data: record,
} );
yield receiveEntityRecords( kind, name, updatedRecord, undefined, true );
const path = `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`;
if ( isAutosave ) {
const persistedRecord = yield select(
'getEntityRecord',
kind,
name,
recordId
);
const currentUser = yield select( 'getCurrentUser' );
const currentUserId = currentUser ? currentUser.id : undefined;
const autosavePost = yield select(
'getAutosave',
persistedRecord.type,
persistedRecord.id,
currentUserId
);
let data = { ...persistedRecord, ...autosavePost, ...record };
data = Object.keys( data ).reduce( ( acc, key ) => {
if ( key in [ 'title', 'excerpt', 'content' ] ) {
acc[ key ] = get( data[ key ], 'raw', data[ key ] );
}
return acc;
}, {} );
const autosave = yield apiFetch( {
path: `${ path }/autosaves`,
method: 'POST',
data,
} );
yield receiveAutosaves( persistedRecord.id, autosave );
} else {
const updatedRecord = yield apiFetch( {
path,
method: recordId ? 'PUT' : 'POST',
data: record,
} );
yield receiveEntityRecords( kind, name, updatedRecord, undefined, true );
}
} catch ( _error ) {
error = _error;
}
yield { type: 'SAVE_ENTITY_RECORD_FINISH', kind, name, recordId, error };
yield {
type: 'SAVE_ENTITY_RECORD_FINISH',
kind,
name,
recordId,
error,
isAutosave,
};
}

/**
Expand All @@ -240,8 +289,9 @@ export function* saveEntityRecord( kind, name, record ) {
* @param {string} kind Kind of the entity.
* @param {string} name Name of the entity.
* @param {Object} recordId ID of the record.
* @param {Object} options Saving options.
*/
export function* saveEditedEntityRecord( kind, name, recordId ) {
export function* saveEditedEntityRecord( kind, name, recordId, options ) {
if ( ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) ) {
return;
}
Expand All @@ -252,7 +302,7 @@ export function* saveEditedEntityRecord( kind, name, recordId ) {
recordId
);
const record = { id: recordId, ...edits };
yield* saveEntityRecord( kind, name, record );
yield* saveEntityRecord( kind, name, record, options );
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ function entity( entityConfig ) {
[ action.recordId ]: {
pending: action.type === 'SAVE_ENTITY_RECORD_START',
error: action.error,
isAutosave: action.isAutosave,
},
};
}
Expand Down
28 changes: 24 additions & 4 deletions packages/core-data/src/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,25 @@ export const getEditedEntityRecord = createSelector(
( state ) => [ state.entities.data ]
);

/**
* Returns true if the specified entity record is autosaving, and false otherwise.
*
* @param {Object} state State tree.
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number} recordId Record ID.
*
* @return {boolean} Whether the entity record is autosaving or not.
*/
export function isAutosavingEntityRecord( state, kind, name, recordId ) {
const { pending, isAutosave } = get(
state.entities.data,
[ kind, name, 'saving', recordId ],
{}
);
return Boolean( pending && isAutosave );
}

/**
* Returns true if the specified entity record is saving, and false otherwise.
*
Expand All @@ -215,14 +234,15 @@ export const getEditedEntityRecord = createSelector(
* @param {string} name Entity name.
* @param {number} recordId Record ID.
*
* @return {Object?} Whether the entity record is saving or not.
* @return {boolean} Whether the entity record is saving or not.
*/
export function isSavingEntityRecord( state, kind, name, recordId ) {
return get(
const { pending, isAutosave } = get(
state.entities.data,
[ kind, name, 'saving', recordId, 'pending' ],
false
[ kind, name, 'saving', recordId ],
{}
);
return Boolean( pending && ! isAutosave );
}

/**
Expand Down

0 comments on commit 9291845

Please sign in to comment.