-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Move embed API call out of block and into data #6678
Move embed API call out of block and into data #6678
Conversation
ba05e09
to
9e1e74d
Compare
38f9cdd
to
d94f53a
Compare
This surfaces a question: Is embed API data really block data? Or is it a property entity of the API that we should house within |
Been thinking about this. This is an API call. It's not specific to that block instance (it's totally possible to embed the same URL in multiple blocks)... is it specific to that block type? Are there other blocks that would use embed data? Are there other places in the editor?
I'd think that we would want individual block stores if that block's state was stored in redux, rather than With it being an API call, it does seem like I'm happy to change the store this uses. Perhaps we need to agree on a naming convention for API data fetched by blocks? How do you think we should move forward? |
I think we should move this to |
Ok, moving this into core data. By "naming convention" I meant have a predictable name for where API data is going to be in the store, but if that's not really a concern at this point I'll just move it into core data as it is. |
de3e458
to
9939995
Compare
core-data/selectors.js
Outdated
* | ||
* @return {Mixed} Undefined if the preview has not been fetched, false if the URL cannot be embedded, array of embed preview data if the preview has been fetched. | ||
*/ | ||
export function getPreview( state, url ) { |
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.
To disambiguate, could we name the selector getEmbedPreview
? I might imagine this could be mistaken for e.g. a post preview.
core-data/selectors.js
Outdated
* @param {Object} state Data state. | ||
* @param {string} url Embedded URL. | ||
* | ||
* @return {Mixed} Undefined if the preview has not been fetched, false if the URL cannot be embedded, array of embed preview data if the preview has been fetched. |
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.
The correct form of the mixed type in JSDoc is {*}
.
See: http://usejsdoc.org/tags-param.html#multiple-types-and-repeatable-parameters
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.
However, it might be better to use the "nullable" type here.
http://usejsdoc.org/tags-type.html
This may not work so well given that it returns so many varying types, though this may identify that the selector is doing too much work. For example, should we consider a separate selector for isURLEmbeddable
?
core-data/selectors.js
Outdated
|
||
const oEmbedLinkCheck = '<a href="' + url + '">' + url + '</a>'; | ||
|
||
if ( oEmbedLinkCheck === preview.html ) { |
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 wish there was a more reliable way to determine this from the server? Like a response property or header. Constructing a string URL to compare seems rather fragile.
core-blocks/embed/index.js
Outdated
return; | ||
} | ||
setAttributes( { url } ); | ||
this.setState( { fetching: true, error: false } ); |
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.
May be fine to address separately, but see isRequestingTerms
in packages/core-data/src/selectors.js
for an example of how we can rely on the resolution flow to determine whether a fetch is happening rather than using state.
@aduth thanks for those points, I'll address them when I fix the conflicts :) |
067f54d
to
5f26a75
Compare
Rebased on master and squashed commits. Changed @aduth :
and...
Yes, these two are related, we're relying on
I also wish that. It is extremely fragile, and I want to address this in another PR. We can have the |
core-blocks/embed/index.js
Outdated
}; | ||
} | ||
|
||
componentDidMount() { | ||
this.doServerSideRender(); | ||
componentWillMount() { |
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.
componentWillMount
is a deprecated lifecycle function, and can usually be substituted with constructor
. Probably sensible anyways to set initial state via this.state
.
https://reactjs.org/docs/react-component.html#the-component-lifecycle
core-blocks/embed/index.js
Outdated
} | ||
|
||
componentWillUnmount() { | ||
// can't abort the fetch promise, so let it know we will unmount | ||
this.unmounting = true; | ||
} | ||
|
||
componentWillReceiveProps( nextProps ) { |
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.
componentWillReceiveProps
is a deprecated lifecycle function. For your purpose here, you might consider componentDidUpdate
, though it will incur a secondary render unfortunately. getDerivedStateFromProps
is another option, though will require more substantial refactor.
https://reactjs.org/docs/react-component.html#the-component-lifecycle
core-blocks/embed/index.js
Outdated
edit: compose( | ||
withSelect( ( select, ownProps ) => { | ||
const { url } = ownProps.attributes; | ||
// Preview is undefined if we don't know the status of it, false if it failed, |
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.
To me, false
seems like a loose interpretation of a failed request, thus necessitating a comment like this explaining it. I might prefer just to have a separate selector here to determine success of the result.
@aduth I think I'd addressed your concerns here. The deprecated lifecycle methods have been migrated to new, supported ones. There's a new selector to say if the preview we have is an oEmbed fallback or not, so no looking at undefined/false as having special meaning. |
bf28e3e
to
574a6f7
Compare
574a6f7
to
14b7e75
Compare
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.
Globally looking good, I left some small comments, I'm mainly thinking about ways to simplify the Embed Block Edit component, It doesn't seem usefull to keep the state, unless I'm missing something?
packages/core-data/src/resolvers.js
Outdated
/** | ||
* External dependencies | ||
*/ | ||
import { stringify } from 'querystring'; |
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.
you can use addQueryArgs
instead of this. see #8300
if ( ! preview ) { | ||
return false; | ||
} | ||
return preview.html === oEmbedLinkCheck; |
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.
It seems fragile to test the html
. WP can decide to change the fallback right? Can't we add an explicit attribute to the embed endpoint when it's a fallback: isFallback: true
?
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.
The embed fallbacks are currently done by a filter in embed_maybe_make_link
, and that only deals with HTML, you can't change any of the data structure. We'd have to change the core embed code itself to do that.
/** | ||
* External dependencies | ||
*/ | ||
import { stringify } from 'querystring'; |
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.
same as above :)
core-blocks/embed/index.js
Outdated
this.processPreview = this.processPreview.bind( this ); | ||
|
||
this.state = { | ||
html: this.props.preview ? this.props.preview.html : '', |
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't we rely on the props directly and avoid all these state values now that the fetching is done in a HoC?
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 hadn't noticed, but yes, we can get rid of a lot of the state!
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 easily get rid of everything except fetching
and url
! Which is nice :)
…w-fetched-with-data-api
core-blocks/embed/index.js
Outdated
|
||
this.state = { | ||
editingURL: false, | ||
fetching: !! this.props.attributes.url && ! this.props.preview, |
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't we get rid of fetching
using the isResolving
selector in core/data
?
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 think so, because (if I'm reading this correctly) isResolving
works on an attribute in a store, so every embed block on the page would go into fetching mode when one of them fetches.
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.
@notnownikki No, it's not, you have to pass the same set of arguments passed to your getEmbedPreview
selectors to the isResolving
selector, so you'll pass the url
of the current block which means you get the "fetching" status for the current selector call.
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.
Oh then I had totally read it wrong! I'll get on that :)
core-blocks/embed/index.js
Outdated
import { Button, Placeholder, Spinner, SandBox, IconButton, Toolbar } from '@wordpress/components'; | ||
import { createBlock } from '@wordpress/blocks'; | ||
import { RichText, BlockControls } from '@wordpress/editor'; | ||
import apiFetch from '@wordpress/api-fetch'; | ||
import { addQueryArgs } from '@wordpress/url'; | ||
import { withSelect, isRequesting } from '@wordpress/data'; |
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.
isRequesting
here is useless right?
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.
Yes, there are still some things to clean up, lint should have picked this up in the tests :)
core-blocks/embed/index.js
Outdated
setAttributes( { url } ); | ||
} | ||
|
||
processPreview() { |
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'm not sure I understand what this function does as it seems to do a lot of different things. I wonder if it's better to remove it in place where needed? At least we should add a JS doc explaining the logic here.
core-blocks/embed/index.js
Outdated
// Form has been submitted without changing the url, and we already have a preview, | ||
// so process it again. Happens when the user clicks edit and then immediately | ||
// clicks embed. | ||
this.processPreview(); |
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.
Do we really need to to do anything here? since the url stayed the same, why are we calling processPreview
here?
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 one of the areas I need to clean up, because so much of the state isn't there any more. I'll mark this PR as WIP until I have everything cleaned up.
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.
The changes are looking good to me 👍 One thing I was wondering though is that we don't have embed e2e test (switching blocks, preview ...) I think it's must because these involve some advanced workflows that are not tested at all. I'm approving because I think we can work on these separately.
Thank you! I've been looking at how we can have e2e tests for these, the thing I'm running up against is that we'd be relying on YouTube etc. to return responses, at the moment we don't have an easy way of mocking these that I can see. |
I don't really care about mocking those, just take a public video/tweet anything. If it breaks once in a while because of network issues, it's not important, the most important thing is that it's tested :) |
Great, I will start on that next! |
Description
The removes the API call from inside the embed block and uses withSelect instead.
Moving the data requests into the framework and makes it possible to extend the embed behaviour through plugin code
How has this been tested?
The usual embed tests from the Sandbox README, plus doing them twice in a row to make sure the embed previews come from the store, and try to embed a url that cannot be embedded to make sure the correct error is reported.
Checklist: