Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CRUD functionality to eventespresso/core wp.data store. #780

Closed
wants to merge 94 commits into from

Conversation

nerrad
Copy link
Contributor

@nerrad nerrad commented Nov 13, 2018

Problem this Pull Request solves

Now that we have the basic wp.data store(s) and model entity creation/management in place for javascript work in wp, its time to expand that to add some additional CRUD functionality (needed for REM work as well).

In this pull:

eventespresso/core store state shape

The store state for eventespresso/core is given a shape described here. Essentially:

{
	entities: {},
    relations: { index: {}, entityMap: {} },
	dirty: { relations: { index: {}, add: {}, delete: {} }, trash: {}, delete: {} }
}

The eventespresso/core state becomes the authority for all entities worked with in the request as well as the interface for crud actions.

It should be noted that while the top level for the state are plain objects, the various trees in the state are immutable maps. The interface to the state (via actions and selectors) gives access to the data in the maps.

Selectors, Actions and Action Generators

All the selectors and actions for eventespresso/core are found described here. It should be noted that for any selectors having to calculate values or convert from immutable map, the selector is created using rememo. This adds a cache layer that:

  • optimizes against potentially expensive queries.
  • ensures the same converted object/array retrieved from immutable map is returned by the selector when the immutable state has not changed. This is important with regards to ensuring any components using this state in react only re-renders when necessary.

One thing I've taken care to do when it comes to action naming is that anything that results in data actually persisting data to the server is prefixed with persist :)

Other stores

eventespresso/lists and eventespresso/schema also have changes using immutable and rememo. This required updates to our existing gutenberg block (and various components using those stores) so that they are correctly accounting for the changes.

One important change related to that is that entities are now returned as a simple array of entities rather than a native javascript Map. This is slightly more performant as there really is no need to index by id when that's readily available from the data if/when needed. If we find we really NEED to have entities returned ordered and with keys being the ids, we can add a new selector to the store for that purpose.

Other changes

  • moved our es lint configuration to use @wordpress/eslint-plugin. This will help us with keeping up to date with the WP javascript standards with little effort on our end. We are still able to customize as needed.
  • using constants for action names. The number of places the action type names were being referenced increased (along with the number of actions!) so using constants reduces the chance for human error and accidental collisions.
  • added a convertToMapFromObject method as a part of the @eventespresso/helpers module.
  • Added a getIdsFromBaseEntityArray method as a part of the @eventespresso/helpers module. This is a handy utility to extract an array of ids for all the BaseEntity instances in a given array.
  • added a normalizeEntityId function to the @eventespresso/helpers module. This function will leave a cuid alone and coerce anything else to an integer (using lodash.toInteger. This is used extensively in our stores.
  • added a removeEmptyFromState function to the @eventespresso/helpers module that recursively clears empty things from a given object/Immutable.map. This is used extensively in eventespresso/core reducers when removing things from the state.
  • Added assertImmutableObjectHasPath function to the @eventespresso/model module. This does what it says, if the given path does not exist in the given immutable map/set/other then it throws an Exception.
  • The server now exposes a base_rest_route value via the eejsdata object. This in turn is accessed via the eejs.data module. The base_rest_route is essentially the root route path for all WP REST Requests. It is useful when the javascript client needs to strip it out from urls.
  • Along with the above, a new function was added to the @eventespresso/model that strips the base_rest_route from a given url string: stripBaseRouteFromUrl
  • various fixes were made to the BaseEntity factory.
  • Added two functions to @eventespresso/model: pluralModelName and singularModelName that basically allow for normalizing to plural or singular for a given string. For instance if you do pluralModelName( 'person' ) you'll get back people. This is used extensively throughout the stores code for normalizing model name references in the state. In the state, entity models names are always singular, whereas relation name references are always plural. While our api is somewhat inconsistent, we can get the correct slugs from the schema, in the state however I favoured consistency. This is especially useful because the the consuming code doesn't have to worry about what the correct slug is for a model, it can always use the singular model reference (or constants we can expose) and the store will automatically normalize to its correct reference. These functions are memoized, so you can call them repeatedly and it only does the conversion to singular/plural once for a given string. So keeps things optimized.
  • getPrimaryKeyQueryString is added to @eventespresso/model. It basically returns the query string to use in a REST api request for the given model and primary key value(s). This is also memoized.
  • Added a InvalidModelEntity exception to our @eventespresso/eejs module
  • @eventespresso/eejs now exposes a middlewares property (eejs.middleWares). This will contain any middlewares that are registered on demand on live page load. The first middlware in this is capsMiddleware which is invoked inline as middleware on the wp.apiFetch module. This middleware ensures that the proper caps context is added to all outgoing EE REST_API requests.
  • webpack config now accounts for the @wordpress/url module. Take a gander at that module sometime, there's some useful utilities in there for working with urls. We can now use any of the functions exposed on that module by importing @wordpress/url in our code.

How has this been tested

  • First layer of testing is done via a crap load of jest tests added.
  • Second layer of testing involves the existing EventAttendees block. This is one thing that should receive additional testing before merging this branch in to master just to ensure I haven't broken anything that is user facing.
  • Third layer of testing involved "monkey" testing all the various selectors and actions in the browser console. For example doing things like the following directly in the browser console while in the context of the GB editor:
Event = await wp.data.select('eventespresso/core').getEventById( 1212 );
newDate = await wp.data.dispatch('eventespresso/core').createDatetime( { DTT_name: 'test_date' } );

For this third layer of testing I created checklists that I went through (see beginning here). For everything tested, I used redux dev tools extension to ensure the state had the expected changes. Anytime testing failed, I would write a jest test reproducing the fail and then fix it. That way I expanded on the jest tests to cover things I missed in the initial run through (and ensure any regressions get caught).

Remaining items for discussion/future work.

The following is a list of unresolved things thought of while working on this pull. I've decided that they can be worked on in future pulls and some may require additional discussion/planning:

  • Apps will need to take care of updating the ui/ux after persisting. So its expected they'll take the returned entities after persisting and replace any of those passed through as props. With the current plan, I'm not sure if apps will need a map of cuid => new_id for the returned entities. I don't think so, but we'll see on implementation.
  • How do we handle hard deletes that are "blocked"? The server does surface when a delete doesn't complete but not why. So this means that for now consuming applications will need to be aware of blocking deletes conceptually rather than programmatically. I do have awareness built into the persistDelete actions in the store for recognizing when a delete didn't complete on the server but its limited (the id is not returned when a delete is unsuccessful)
  • Right now it's up to the client to decide whether it trashes or hard deletes entities. So its technically possible for consuming code to request a trash for entities that have no trash route and end up permanently deleting instead. There might be usefulness in recording on the model entity instance whether it is "trashable" or not to expose that information to consuming code.
  • For entities that have foreign key fields, I'm uncertain on how things are handled by the REST request when it receives a foreign key that is different than what already exists for that entity. This may mean that when a relation is added/removed in the state, we also need to update the foreign key in any stored entity instances for that relation id. This is not currently being done.
  • wp.data has a resolver cache reset api for resetting the resolved state for a selector with corresponding resolver. I could forsee this being something we may need in our stores as well (i.e. 'refreshing' the view on a page without a full page reload). I feel this is something that we can iterate on in future pulls as the need becomes more apparent.
  • I want to evaluate what happens for action creators/resolvers when there is a problem with the request or one of the yield steps. There may be cases where a Promise could be returned with an error object for client code to handle. If that's the case, I think I want to improve the error handling so its a bit more consistent. Related are a couple pulls I've created for gutenberg ( specifically @wordpress/redux-routine) before we can do something reliable here: Improve castError handling of non strings WordPress/gutenberg#13315 and Fix/unhandled promise rejection warning when returning null from action generator WordPress/gutenberg#13314 Once those pulls are merged I can look into improving our error handling in the various resolvers/action generators that do requests.
  • In documentation, we need to make sure there's clarity on what is the expected public api client code would use for interacting with the store. For instance, the removeRelatedEntities dispatch is simply for removing stored related entities in the relations tree of the state but NOT any queued relation data (dirty state tree) or the actual relation entities themselves ( entities state tree). So it's an api that shouldn't be generally used unless client code knows what it is doing.
  • When Maintenance Mode is active on the server, I'm assuming that affects rest requests. How do we surface that to clients?

Checklist

@nerrad
Copy link
Contributor Author

nerrad commented Nov 16, 2018

Things to consider:

Example state for the eventespresso/core store (before being converted to be an Immutable.Map):

const state = {
	entities: {
		event: {
			10: { ...eventEntity },
		},
		datetime: {
			'123124cuid': { ...datetimeEntity },
		},
		ticket: {
			20: { ...ticketEntity }
		}
	},
	relations: {
		index: {
			datetimes: {
				'123124cuid': {
					event: [ 10 ]
				}
			},
			tickets: {
				20: {
					datetime: [ '123124cuid' ],
				},
			},
		},
		entityMap: { 
			event: {
				10: {
					datetimes: [ '123124cuid' ]
				},
			},
			datetime: {
				'123124cuid': {
					tickets: [ 20 ],
				}
			},
		}
	},
	dirty: {
		relations: {
			index: {
				datetimes: {
					40: {
						event: { delete: [ 10 ] },
						ticket: { delete: [ 20 ] },
					},
					'123124cuid': {
						event: { add: [ 10 ] },
					}
				},
				tickets: {
					20: {
						datetime: { add: [ '12314cuid' ] },
					}
				}
			},
			delete: {
				event: {
					10: {
						datetimes: [ 40 ]
					}
				},
				ticket: {
					20: {
						datetimes: [ 40 ]
					}
				}
			},
			add: {
				event: {
					10: {
						datetimes: [ '123124cuid' ]
					}
				},
				datetime: {
					'123124cuid': {
						tickets: [ 20 ],
					}
				}
			}
		},
		trash: {
			event: [ 20 ],
		},
		delete: {
			datetime: [ 40 ],
		}
	}
};

Insert/Update

This will be handled by all dispatch actions.

  • dirty state (new, dirty) etc is maintained within the entities themselves so there is no need to store that in the actual state since its a property of the entities.
  • persistEntityRecord will be the action creator (generator) for handling inserts/updates of provided model entity instances (Note, this action creator expects the provided model entity instances are already in the state). This will return the new record.
  • When entity records are persisted, for any NEW entity records, we need to also handle replicating the new id in any queued relation adds/deletes that might exist for that entity (i.e. replace cuid in state with the id from the persisted entity).
  • createEntity - this action creator (generator) is used to create an entity instance from a given "new" entity and added to the state for that entity. I'm on the fence about this action as apps can get the factory from the eventespresso/schema store and use that to create the model entity instance, then just pass it in to the state using receiveEntityRecords. So this is more of a convenience helper.
  • REMOVE_ENTITY_BY_ID this will be needed for after entities are persisted that are NEW since their ID will have changed. After persisting, new entity object instances are refreshed from the db and replace what exists in state.
  • persistForEntityIds This receives an array of entityIds for a given model and handles persisting those to the server. It returns an object of all the new entity instances after persisted to the db and replacing existing in state.

Things needing worked out:

  • Apps will need to take care of updating the ui/ux after persisting. So its expected they'll take the returned entities and replace any of those passed through as props. With the current plan, I'm not sure if apps will need a map of cuid => new_id for the returned entities. I don't think so, but we'll see on implementation.

Deletes

  • RECEIVE_TRASH_ENTITY_ID - this will handle queuing soft deletes of entities to the state.
  • RECEIVE_DELETE_ENTITY_ID - this will handle queuing hard deletes of entities to the state.
  • deleteEntityById - action creator for queueing the hard delete of an entity.
  • trashEntityById - action creator for queuing the soft delete of an entity.
  • Figure out what is needed for any queued relation additions/removals when an entity is queued for delete/trash (if that's something handled server side we may not need to do anything). For instance, if there are any relation entities currently present in the store that are only connected to the removed entity, then do we need to remove them from the store as well?
  • persistDeletes - this will handle persisting all queued trash and deletes to the server.
  • REMOVE_DELETE_ENTITY_ID - handles removing the queued entity id for deletes from state.
  • REMOVE_TRASH_ENTITY_ID - handles removing the queued entity id for trash from state.

Things needing worked out:

  • Should hard deletes automatically handle removing any necessary relations or is that something the server handles (obviously any "scheduled" relation changes client side would still need removed from state).
  • How do we handle hard deletes that are "blocked"? I think the rest api on the server will take care of blocking if necessary but: We need a way to surface the reasons to the app - and provide a mechanism to resolve the conditions of the block, we need a way to override blocks if necessary
  • How do we programmatically know when an entity has a trash route vs an entity that ONLY has a permanent delete route?

Relations

  • selector for getting relations to an entity and its model. The retrieved relations are added to the state on a relations index (getRelationsForEntity?)
  • Start tracking the relation endpoints for models in eventespresso/schema as for cases when there is only the entity id for a given model we want to get the relation for. Although relation endpoints ARE available in actual entities, those will not always be available when retrieving relations. I'm kinda on the fence with this, I think I may start with retrieving an entity needed to get/add a relation for, we'll see.
  • REMOVE_RELATION action that removes that relation from the relations index and adds it to the dirty state for relations.
  • DELETE_RELATION action that persists the removal for relations to the server.
  • ARCHIVE_RELATION action
  • ADD_RELATION action that adds a new relation to the relations state and within the dirty index for relations.
  • PERSIST_ADD_RELATION action that persists the adding of the relations to the db.
  • PERSIST_ALL_RELATIONS action which takes care of executing PERSIST_ADD_RELATION and PERSIST_DELETE_RELATION.
  • UPDATE_RELATION_DIRTY_STATE takes care of updating the dirty state for relations after one of the persisting actions.
  • currently there's no simple process for adding/removing relations for entities (i.e. PUT /ee/v4.8.36/events/12/datetimes/12 or DELETE /ee/v4.8.36/events/12/datetimes/12) but rather the client needs to derive what type of relation exists and then either update the foreign key in the originating entity for belongs to relations, or do a request against the join table end point for has many type relations. (see Fet/rest relation endpoints (fixes #790) #798)
  • When relations are persisted, we need to handle relation ids that are cuid and use that to trigger persisting those specific new entities as well to get their new id and THEN persist the relation with that new id.
  • For "belongs to" relations where the foreign key for the relation is in the actual model, I need to make sure we're correctly accounting for changes in that foreign key in the cached entity in the state (especially for existing entities where the entity itself if not dirty and the relation changes).
  • normalize incoming relation state so that we always save in the state for one direction (i.e. if a getRelations is called for event -> datetime, and then the same thing done for datetime-> event at a later time, the data is normalized for event -> datetime in the state.
  • Make sure any selectors for relations account for the normalized directional flow of getting the relation per the above point. This also means that resolvers need to force resolve for the opposite relation order if necessary (so we're not doing unnecessary resolution for a relation that already exists in the state but for the opposite order).
  • new action type REMOVE_RELATED_ENTITIES_FOR_ENTITY needs to be called to remove any relations (dirty relations etc) in the state when an entity is queued for delete.

Things needing worked out:

  • How do we handle blockers for related data. For example, removing a registration related to a transaction that will change totals etc on related entities (transactions, registration_payment, line_item, ticket sold, datetime sold etc). What portion of these are handled by the server and what portion needs handled client side?
  • Investigate the resolver cache reset functionality recently added to wp.data and how that will need implemented with the work here.

@nerrad
Copy link
Contributor Author

nerrad commented Nov 19, 2018

Other tasks:

Other tasks that might not be not directly related to the ticket but things discovered in the process of doing the work that I'm doing as a part of this pull
too:

  • I'm running into increasing complexity to maintain "immutability" of state within the reducers etc. So I'm going to implement using immutable.js (and reselect (or rememo which I was reminded of WP core using) where it makes sense for the selectors) to make things more concise and less complex. This means the shape of the state is going to change a bit. Instead of a native JS Map (which would be mutable on retrieval from the Immutable object) I'm going to use Immutable's OrderedMap where entry order matters and Immutable's Map where order doesn't matter for key value pairs.
  • eventespresso/schema has an ./entities.js module (for concrete entity selector/resolvers) but there is no implementation of it!
  • I want to evaluate what happens for action creators/resolvers when there is a problem with the request or one of the yield steps. There may be cases where a Promise could be returned with an error object for client code to handle. If that's the case, I think I want to improve the error handling so its a bit more consistent. Related are a couple pulls I've created for gutenberg ( specifically @wordpress/redux-routine) before we can do something reliable here: Improve castError handling of non strings WordPress/gutenberg#13315 and Fix/unhandled promise rejection warning when returning null from action generator WordPress/gutenberg#13314
  • dynamic entity specific selectors/actions etc need created for eventespresso/core data (see entities.js).
  • Persisting the adding and removing relations will result in responses that have the new/old entity (see https://github.com/eventespresso/event-espresso-core/pull/798/files) I need to work out how I will update models that are "belong to" models with a foreign key. Preferably, I think we'll want to just update the foreign key field in the cached model entity.
  • create wp.apiFetch middleware for adding caps context (eg. read, edit, delete) to outgoing requests for EE rest GET requests (only applied to those endpoints!). This is needed because otherwise password protected events etc will not be retrieved as expected in the editor context. This middleware can be injected anytime the app is using the data in a context where it's needed.
  • In documentation, we need to make sure there's clarity on what is the expected public api client code would use for interacting with the store. For instance, the removeRelatedEntities dispatch is simply for removing stored related entities in the relations tree of the state but NOT any queued relation data (dirty state tree) or the actual relation entities themselves ( entities state tree). So it's an api that shouldn't be generally used unless client code knows what it is doing.
  • How do we handle blockers for related data. For example, removing a registration related to a transaction that will change totals etc on related entities (transactions, registration_payment, line_item, ticket sold, datetime sold etc). What portion of these are handled by the server and what portion needs handled client side? Answer: deletes have two arguments force (which controls whether to permanently delete if the entity is trash-able), allow_blocking (which controls whether to halt deletes if there's something blocking it (whatever the model defines as blocking). Both are exposed on schema and thus our model-entities and related crud actions should account for that.

@nerrad nerrad force-pushed the FET/add-crud-to-stores branch 3 times, most recently from d582004 to 9be725c Compare November 25, 2018 13:58
@nerrad nerrad force-pushed the FET/add-crud-to-stores branch 3 times, most recently from 05a459d to 0da5c2d Compare December 4, 2018 19:02
@nerrad nerrad force-pushed the FET/add-crud-to-stores branch 2 times, most recently from 28b1884 to 1a250e1 Compare December 12, 2018 21:59
@nerrad nerrad force-pushed the FET/add-crud-to-stores branch 3 times, most recently from f4e259c to 4df4d8d Compare January 1, 2019 16:33
@nerrad
Copy link
Contributor Author

nerrad commented Jan 8, 2019

Checklist for eventespresso/core selectors/dispatches

Selectors

A * next to the selector means that this has a corresponding resolver.

  • getEntityRecordsForModel( modelName ) - returns entities indexed by primary key
  • getEntitiesForModel( modelName ) - returns array of entities
  • getEntityById( modelName, entityId ) * - returns entity
  • getEntitiesByIds( modelName, entityIds ) - returns array of entities
  • getEntityIdsQueuedForTrash( modelName ) - returns array of ids
  • getEntityIdsQueuedForDelete( modelName ) - returns array of ids
  • getModelsQueuedForTrash( modelName ) - returns array of model names
  • getModelsQueuedForDelete( modelName ) - returns array of model names
  • getRelationIdsForEntityRelation( entity, relationName ) - returns array of ids
  • getRelatedEntities( entity, relationName ) * - returns array of entities
  • getRelationAdditionsQueuedForModel( modelName ) - returns object keyed by entity ids indexing an object keyed by relation name sand values being array of ids for relation
  • getRelationDeletionsQueuedForModel( modelName ) - same as previous
  • countRelationModelsIndexedForEntity( modelName, entityId ) - count of relation models that exist in the state related to the given model and entity id

Dispatch Actions

  • createEntity( modelName, plainObject ) - new entity instance (returns BaseEntity child)
  • createRelation( modelName, entityId, relationName, relationEntity ) - ensure the state reflects the expected values.
  • createRelations( modelName, entityId, relationName, relationEntities ) - same as above but for multiple relation entities for the given model, entity id, and relation
  • deleteEntityById( modelName, entityId ) - ensure the state reflects the expected values
  • trashEntityById( modelName, entityId ) - same as previous
  • removeDirtyRelationForAddition( modelName, entityId, relationName, relationEntityId ) - ensure state is updated as expected
  • removeRelationForEntity( modelName, entityId, relationName, relationEntityId ) - ensure state is updated as expected
  • receiveEntityRecords( modelName, entities ) - adds the entity records to the state. Note this should not replace any record already in the state.
  • receiveAndReplaceEntityRecords( modelName, entities ) - same as the previous except this will replace any already existing record for entities in the state.
  • receiveEntity( entity ) - Adds the entity to the state (but does not replace the entity already in the state if it exists)
  • receiveRelatedEntities( modelName, entityId, relationName, relatedEntityIds ) - Adds the relation to the state.
  • removeEntityById( modelName, entityId ) - removes the entity from the state tree
  • removeDeleteEntityId( modelName, entityId ) - removes the entity from being queued for delete
  • removeTrashEntityId( modelName, entityId ) - removes the entity from the trash queue in the state.
  • removeAllRelatedEntitiesForModelEntity( modelName, entityid ) - removes all indexed relations for a specific entity from the state so there is no longer record of that in the state. If the relation entities themselves have no other elations in the state tree, then they are also removed from the state
  • removeRelatedEntities( modelName, entityId, relationName, relationEntityIds ) - is more specific than the previous dispatch action.

Note: the following actions don't really need tested directly because they are indirectly covered by other tests regarding removing relations.

  • receiveDirtyRelationAddition( relationName, relationEntityId, modelName, entityId ) - this queues up the relation for addition.
  • receiveDirtyRelationDeletion( relationName, relationEntityId, modelName, entityId ) - this queues up the relation for deletion.
  • receiveTrashEntityId( modelName, entityId ) - This queues up the given entity for trashing in the state.
  • receiveDeleteEntityId( modelName, entityId ) - Same as the previous except queues for deleting.
  • receiveUpdatedEntityIdForRelations( modelName, oldEntityId, newEntityId ) - triggers the replacement of any instance of the old entity id in the state for the given model with the new entity id (across the entire state tree).
  • removeDirtyRelationAddition( relationName, relationEntityId, modelName, entityId ) - specific method wrapping removeDirtyRelationForType
  • removeDirtyRelationDeletion( relationName, relationEntityId, modelName, entityId ) - specific method wrapping removeDirtyRelationForType

Dispatch Actions that persist to the server

  • persistEntityRecord( modelName, entity ) - inserts or updates dirty entity records to the server database and replaces existing records in state with the successfully persisted result (via response from the server). So you should end up with NEW entities in the state. Especially relevant for new entities that get any defaults returned from the server. If the entity is not persisted (for whatever reason) null will be returned.
  • persistForEntityId( modelName, entityId ) - same as above except only the specific entity will be persisted (if its dirty). Returns the persisted entity if successful or null if not.
  • persistForEntityIds( modelName, entityIds ) - same as above except only the specific entities will be persisted (note, only the dirty entities in the given group will be persisted). returns the persistedEntities (Object) if successful or an empty Object if not.
  • persistDeletesForModel( modelName ) - persists any queued up deletes for the given model. Note the force=true argument is set so if this does permanent deletes. Returns an array of all the deletes persisted. Returns an array of ids successfully persisted.
  • persistTrashesForModel( modelname ) - persists any queued up trashes for the given model. Note that this just means force=true is not included. But if the model does not have a soft-delete facility, then permanent deletes will happen. Returns an array of all the trashes persisted. Returns an array of ids successfully persisted.
  • persistAllDeletes() - this persists all the queued deletes AND trashes for all models. Returns an object indexed by delete/trash, then for each object indexed by modelNames and the values are arrays of entity ids for each model that were persisted.
  • persistAddRelationsForModel( modelName ) - persists any queued relation additions (i.e. adding a datetime relation to an event) to the db. Returns an object indexed by the originating entity id for the requested model and with values that are an object indexed by relation names with values of relation ids successfully persisted.
  • persistDeleteRelationsForModel( modelName ) - same as above except handling relation removals.

Note: there are a number of other persistRelation type dispatch action but they are all indirectly tested via persistAddRelationsForModel and persistDeleteRelationsForModel

@nerrad
Copy link
Contributor Author

nerrad commented Jan 8, 2019

Checklist for eventespresso/lists selectors/dispatches

Selectors

A * next to the selector means that this has a corresponding resolver

  • getItems( identifier, queryString ) * - Returns all items for the given identifier and queryString. Items are returned in an array and may be an arbitrary set of data queried from the db using the querystring (raw response objects)
  • getEntities( modelName, queryString ) * - This is similar to getItems except it returns an array of BaseEntity children for the given model. Note: eventespresso/core is the authority for entity instances so any retrieved entities will get pulled from eventespresso/core if they already exist there and added to eventespresso/core state if not.
  • getEntitiesByIds( modelName, ids ) * - effectively the same as the previous query except it allows the client to just specify the ids they want. NOTE: this will set the resolution for getEntityById selector in eventespresso/core to fulfilled for each entityId in this array (prevents unnecessary queries to the server)
  • isRequestingItems( identifier, queryString ) - whether the items for the given identifier and query string are being resolved.
  • isRequestingEntities( modelName, queryString ) - effectively the same as the previous

Dispatch Actions

  • receiveResponse( identifier, queryString, items (array) ) - Used to add arbitrary items to the state.
  • receiveEntityResponse( modelName, queryString, entities (array) ) - Used to add an array of entity instances (BaseEntity) to the state.

@nerrad
Copy link
Contributor Author

nerrad commented Jan 8, 2019

Checklist for eventespresso/schema selectors/dispatchers

Selectors

A * next to the selector means that this has a corresponding resolver

  • getSchemaForModel( modelName ) * - provides the schema returned from the options endpoint for the given model
  • isRequestingSchemaForModel( modelName ) - whether the schema is being requested or not.
  • hasResolvedSchemaForModel( modelName ) - returns whether the schema has been resolved or not for the given modelName.
  • getFactoryForModel( modelName ) * - returns the entity factory for the given modelName. Note this will also resolve the schema for the model if that doesn't already exist in the state.
  • isRequestingFactoryForModel( modelName ) - whether the factory is being requested for the given model name.
  • hasResolvedFactoryForModel( modelName ) - whether the factory has been resolved for the given model name.
  • getRelationEndpointForEntityId( modelName, entityId, relationModelName ) * - this returns the relation endpoint for the the given model, id and relation. Will first attempt to get the info from the existing entity in the eventespresso/core state if it exists there otherwise will retrieve the entity (just the raw response) from the server to get it.
  • isRequestingRelationEndpointForEntityId( modelName, entityId, relationModelName ) - returns whether the relation endpoint is being requested or not.

Dispatch actions

  • receiveSchemaForModel( modelName, schema ) - adds the schema for the given model to the state
  • receiveFactoryForModel( modelName, factory ) - adds the factory for the given model to the state
  • receiveRelationEndpointForModelEntity - adds the relation endpoint for the given model to the state.

@nerrad nerrad force-pushed the FET/add-crud-to-stores branch 4 times, most recently from 2dbbad1 to af34ab1 Compare January 15, 2019 18:37
@nerrad nerrad changed the title WIP: Add CRUD functionality to eventespresso/core wp.data store. Add CRUD functionality to eventespresso/core wp.data store. Jan 16, 2019
@nerrad nerrad requested a review from tn3rb January 16, 2019 01:57
@nerrad
Copy link
Contributor Author

nerrad commented Jan 20, 2019

Ready for review again @tn3rb

const listItems = attendees.map(
( attendee ) => {
return <AttendeeListItem
key={ attendee.id || cuid() }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't get why you are doing this and just not using the index generated by the call to map(), ie:

		const listItems = attendees.map(
			( attendee, index ) => {
				return <AttendeeListItem
					key={ index  }

The key can not be referenced in any way whatsoever so there is no reason for needing to be able to know what the key is.
Its only purpose is to guarantee uniqueness among the mapped items and the index does this sufficiently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have missed my reply to your original comment which goes into a bit more explanation for why I took the path I did. Does that help with the reasons?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nvm I see what you mean now.

const relationName = pluralModelName( relationData.relationName );
const relationIds = [ ...relatedEntityIds ];
while ( relationIds.length > 0 ) {
const relationId = relationIds.shift();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed this one

assets/src/data/eventespresso/core/reducers/relations.js Outdated Show resolved Hide resolved
@tn3rb tn3rb removed their assignment Jan 21, 2019
@nerrad nerrad requested a review from tn3rb January 21, 2019 18:18
tn3rb
tn3rb previously approved these changes Jan 21, 2019
@nerrad
Copy link
Contributor Author

nerrad commented Jan 21, 2019

@joshfeck (or whoever ends up testing) the main thing needing tested here is that there are no regressions with the Event Attendees block in the new WP editor.

So:

  • verify that any ea blocks created before switching to this branch still render correctly in the editor after switching to this branch.
  • verify that adding new ea blocks to post content after switching to this branch still render correctly in the editor.

The front-end rendering of blocks should remain unchanged as well.

joshfeck and others added 2 commits January 23, 2019 13:08
# Conflicts:
#	assets/dist/build-manifest.json
#	assets/dist/ee-components.622f86c8649619cbcbcb.dist.js
#	assets/dist/ee-components.7d6c99591998d2eae4c0.dist.js
#	assets/dist/ee-components.b72fa352434166ef52be.dist.js
#	assets/dist/ee-data-stores.67b185e1920ba24c7431.dist.js
#	assets/dist/ee-model.7f810d358d08f82e088b.dist.js
#	assets/dist/ee-model.93606ab921bb65bfe8e0.dist.js
#	assets/dist/ee-model.b08f7f3ee436d1dbb468.dist.js
@joshfeck
Copy link
Contributor

Tested. Ready for merging.

@nerrad
Copy link
Contributor Author

nerrad commented Jan 24, 2019

This was squash/merged manually as opposed to using the ui so closing.

@nerrad nerrad closed this Jan 24, 2019
@nerrad nerrad added this to the 4.9.78 milestone Jan 24, 2019
@joshfeck joshfeck deleted the FET/add-crud-to-stores branch February 8, 2019 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants