-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Gutenberg Integration: load editor with a new post #27074
Conversation
export default connect( | ||
null, | ||
{ createGutenbergPostDraft } | ||
)( QueryGutenbergCreatePost ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a pretty unexpected use of a query component - is the CreatePostDraft
a kind of resource we might imagine polling for or is it intended to be a kind of action like "create this post draft"?
if it's the first would it make more sense to call it something like <QueryPostDraft />
and if it's the second would it make sense to dispatch it as an action instead and use some declarative query component to retrieve it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally query components are reserved for GETs and intended to be idempotent (can be called multiple times without side-effects). Unless I'm misreading this, let's not use a query component and instead dispatch this in the parent component.
These new resources look an awful lot like "shove data"
In this case I think that import { requestGutenbergDraftPost as requestDraft } from 'state/data-getters';
const mapStateToProps = ( state, { siteId } ) => ( {
draftPost: requestDraft( siteId ).data
} ) export const requestGutenbergDraftPost = siteId => requestHttpData(
`gutenberg-draft-post-${ siteId }`,
http( {
path: `/sites/${ siteId }/posts/create-draft`,
method: 'GET',
apiNamespace: 'wp/v2',
} ),
{
fromApi: () => post => [ [ `gutenberg-draft-post-${ siteId }`, post ] ]
}
); |
client/state/action-types.js
Outdated
@@ -262,6 +262,9 @@ export const GUIDED_TRANSFER_STATUS_REQUEST = 'GUIDED_TRANSFER_STATUS_REQUEST'; | |||
export const GUIDED_TRANSFER_STATUS_REQUEST_FAILURE = 'GUIDED_TRANSFER_STATUS_REQUEST_FAILURE'; | |||
export const GUIDED_TRANSFER_STATUS_REQUEST_SUCCESS = 'GUIDED_TRANSFER_STATUS_REQUEST_SUCCESS'; | |||
export const GUTENBERG_OPT_IN_DIALOG_IS_SHOWING = 'GUTENBERG_OPT_IN_DIALOG_IS_SHOWING'; | |||
export const GUTENBERG_CREATE_POST_DRAFT = 'GUTENBERG_CREATE_POST_DRAFT'; | |||
export const GUTENBERG_SITE_POST_REQUEST = 'GUTENBERG_SITE_POST_REQUEST'; | |||
export const GUTENBERG_SITE_POST_RECEIVE = 'GUTENBERG_SITE_POST_RECEIVE'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of naming should we be prefixing with Gutenberg? These are core v2 schema posts right?
I'll also recommend a more descriptive name that would allow some of these actions to make sense out of the context of any server action:
*POST_ADD
vs *_SITE_POST_RECEIVE
*POST_DRAFT_ADD
vs * _CREATE_POST_DRAFT
(it sounds weird but actions should be postfixed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of naming
the core project has been starting to eschew the name Gutenberg
in code review. I hope this makes your day more interesting. they are moving to internalize the name "editor" and the like.
I ran into this while working on the parser stuff - renames from things like Gutenberg_Block
to WP_Block
just wanted to throw that out there 😬
/** | ||
* External dependencies | ||
*/ | ||
import { expect } from 'chai'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this :D Let's use jest. No need to import chai.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wasn't sure exactly what we were using, so I defaulted to chai. Fixed on e2a414c.
client/state/index.js
Outdated
@@ -136,6 +137,7 @@ const reducers = { | |||
form, | |||
googleAppsUsers, | |||
googleMyBusiness, | |||
gutenberg, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be gutenberg? wpv2
maybe?
client/gutenberg/editor/main.jsx
Outdated
|
||
return ( | ||
<Editor settings={ editorSettings } hasFixedToolbar={ true } post={ post } onError={ noop } /> | ||
<Fragment> | ||
{ isEmpty( post ) && <QueryGutenbergCreatePost siteId={ siteId } /> } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this not be done on componentDidUpdate
/componentDidMount
? I think it's fine to copy pasta those lifecycle methods from QueryComponent and get rid of it. An HOC might be another alternative if we have many usages.
Promising start @rodrigoi @dmsnell if you'd like us to use a new pattern here, don't forget to link to additional context. I reviewed #22549 myself and I had to refresh myself. I do think we'll end up using this wp/v2 post by multiple components (for example, imagine a draft selector), but we can maybe start with this if @rodrigoi is up for this. @dmsnell I believe using a key of |
@gwwar thanks for the link! the geolocation data refactor is a simple example of the conversion too.
If I understand what you are saying here then it shouldn't be a problem. With
this suggestion may come from my general confusion mentioned in comments above about what we're actually trying to fetch. if it is clear what we're fetching we can create a clear key. I'm not sure how to change this though in my ignorance of the goal. are you saying that this endpoint isn't cacheable? if so we can look at ways to take that into account. the suggestion about |
ok, I've went ahead with @dmsnell recommendation and re-wrote all the HTTP calls using functions that dispatch a As of today, the only section using the Anyway, this is ready for another pass. |
90ba14b
to
d7d826b
Compare
I gave this a run, but had odd behaviour.
I was properly sandboxed with D18064 (I could see the |
client/state/data-getters/index.js
Outdated
|
||
export const requestGutenbergDraftPost = siteId => | ||
requestHttpData( | ||
`gutenberg-draft-post-${ siteId }`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this key is sufficient. For example, what happens when we load the new post page multiple times? We probably need to assign a temporary draft id, until the response comes back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we look at media for example, I believe the transient object we create is uniqueId( 'media-' ),
We only need to make this unique to the client, so we can do something similar here with:
uniqueId( `gutenberg-draft-post-${ siteId }-` )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the GET
verb on d77f521 and updated D18064-code also. Please apply the patch again before testing.
About the requestId
, I don't think it'll be a problem to use that key. I haven't found any problems (blocked requests) when reloading the page or loading multiple pages. But using an uniqueId
is always a good idea.
I've implemented two versions, one using the route's controller (d2d95b6fcdf978ecefc68db4fe792e299a10daa3):
export const post = ( context, next ) => {
const postId = getPostID( context );
const uniqueDraftId = uniqueId( 'gutenberg-draft-' );
//reserved space for adding the post to context see post-editor/controller.js
context.primary = <GutenbergEditor { ...{ postId, uniqueDraftId } } />;
next();
};
and another using a HOC (fd77025afdb0c1d372155fb900c0fc036da7e10b, e8899f785f64af9402488b287312536dc800e608 and 714beb69a514e8ec2d9a71a038a4198d9925155c):
export const withUniqueDraftId = WrappedComponent => {
return class extends Component {
constructor( props ) {
super( props );
this.state = {
uniqueDraftId: uniqueId( 'gutenberg-draft-' ),
};
}
render() {
const { uniqueDraftId } = this.state;
return <WrappedComponent { ...{ ...this.props, uniqueDraftId } } />;
}
};
};
export default withUniqueDraftId( connect( mapStateToProps )( GutenbergEditor ) );
You can verify that the requestId
is passed down to the HTTP_DATA_REQUEST
action on the redux inspector:
which version do we prefer? I'm leaning towards using the controller just because it's far less code, but composing things is nice too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates here @rodrigoi Let's make this a bit easier on ourselves and ONLY target one case. Eg, can I open an existing post? Eg /gutenberg/post/myid
Let's also remove the autosaveMonitor
like we do in #27060 for now and no-op on the new post path. I can open up a new issue specific to the new post case, and another one to get autosave going.
Added #27140 to track this.
@kwight thanks for taking the time to test this! The autosave problem is expected and will have it's own diff. The same with the The sign that this works is authors and categories loading correctly on the sidebar and the cagetories block. Publish is broken, and will most likely need changes on the |
client/state/data-getters/index.js
Outdated
{} | ||
), | ||
{ formApi: () => ( { ID } ) => [ [ `gutenberg-draft-post-${ siteId }`, ID ] ] } | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably remove this for now and revisit how to handle autosave drafts in a follow up PR. requestHttpData
here feels incorrect for this POST case and is even more unexpected in mapStateToProps
. Overall I think we'd want to trigger specific requests to create the draft only after we have some user input.
If we want to keep this pretty lightweight later I think we'd end up making a create action, but we could potentially still keep a poll going of the post object using requestHttpData.
Thoughts here @dmsnell ?
…/onFailure" warnings.
…nique draft id, in case it's needed
c231c66
to
816e624
Compare
Had a good pairing session with @rodrigoi ✨ to help untangle why we needed a new endpoint. To our understanding underneath the hood all Gutenberg instances need an autodraft. Normally, this is provided by the PHP shell in wp-admin as a hidden form field, but needs to be requested in a standalone instance like Calypso or p2. We're reusing an internal endpoint here (limited to p2 sites + a8c users) for now while we discuss where new custom v2 endpoints and how this should be shaped. Expect posts and additional issues to follow. Work here should at least uncover new classes of errors to start digging into that we can get folks started on. :D |
@gwwar It sounds like a better way to fix this would be in Gutenberg core - adding a new endpoint there and using it during editor initialization instead of hidden form fields? Otherwise it looks like this could add maintenance burden (errors) during updates. |
Let's follow up with an issue to see if we can help make changes in core to avoid this. :) Do try and take your afk though @vindl ! |
This PR adds the bare minimum wiring to Calypso to support the API v2 Post format, so Gutenberg can somewhat start to function.
Depends on D18064-code
Gutenberg relies heavily on the provided
post
prop, with a schema as returned by thewp/v2
API when theedit
context is provided. Without a valid post, Gutenberg fails to load most of the sidebar tools.This PR adds as little code as possible to wire the
wp/v2
endpoints needed to create a draft and load the post so Gutenberg can use the returnedpost
.This PR is based on the
createNewPost
function onp2-gutenberg
:### StateThepost
stored on Calypso's redux state is insufficient for Gutenberg, that relies on fields returned by the API when querying for a post and theedit
context is provided. In particular, the sidebar uses the_links
metadata returned by the API to decide whether or not to populate hierarchical and flat lists like categories and taxonomies, despite the fact that those values are queried and stored oncore
andcore/data
redux store.Gutenberg stores the current post information on the
currentPost
key on thecore/editor
store. Calypso has to do something similar, so, on thegutenberg.currentPost
branch on Calypso's redux store, we keep the information returned from the API call.### Query ComponentsThis PR also incorporates two query components,QueryGutenbergCreatePost
to create a new auto draft, andQueryGutenbergSitePost
to query any post using thewp/v2
endpoint.The App Component holding the Gutenberg instance implements onlyQueryGutenbergCreatePost
. When the page load, a new draft is created, fetched, loaded into state and sent over theprops
to Gutenberg. Refresh the page, and a new one is created.### Data LayerThis PR also breaks new ground because it implements the first two endpoints for thewp/v2
API namespace for calypso. I've made the controversial decision of creating a new handler (wpHandlers
) and a new top level folder structure to keep things apart.TherequestGutenbergPostDraft
request handler dispatches the fetch of the newly created draft on success, and there are no state management associated with that action. I initially implemented tracking on different active drafts, but removed all that complexity in favor of fixing the rest of the integration problems. We can worry about multiple instances of Gutenberg in the future.How To Test and What to Expect
Do not expect much 😎
There are some basic unit tests for the state management and the data layer that can be run with:To test the Gutenberg integration, you'll have to apply D18064-code and sandbox
public-api.wordpress.com
to use the new create draft endpoint. Then, navigate to/gutenberg/post/{site-slug}
.Gutenberg should load with all the sidebar elements active. The Categories panel should load all the available categories, and the authors should load on the Status and Visibility panel. Taxonomies load on the store, but for reason to explore on another PR they are not loading on the token input.
The Categories block should load all the categories, but autosave and the rest of the functionality is sadly broken. This PR should enable work on those features.