-
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
Post revisions into redux #12733
Post revisions into redux #12733
Conversation
… `QueryPostRevisions`
client/post-editor/post-revision.jsx
Outdated
<QueryPostRevisions | ||
siteId={ this.props.siteId } | ||
postId={ this.props.postId } /> | ||
<h1>{ this.props.translate( 'Post History' ) }</h1> |
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.
Hi! I've found a possible matching string that has already been translated 63 times:
translate( 'History' )
ES Score: 9
Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).
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 string will need to include the post type as a translation placeholder.
client/post-editor/controller.js
Outdated
const siteId = getSelectedSiteId( context.store.getState() ); | ||
const postId = getPostID( context ); | ||
|
||
renderRevision( context, siteId, postId ); |
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.
Shouldn't these be revisions
(current method) and renderRevisions
(current line)?
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 should :) That said, I removed that code (for now) to keep the PR focused on redux changes. Will keep it in mind for the next PR, thx :)
client/post-editor/index.js
Outdated
@@ -14,6 +14,7 @@ module.exports = function() { | |||
page( '/post', controller.pressThis, sitesController.siteSelection, sitesController.sites ); | |||
page( '/post/new', () => page.redirect( '/post' ) ); // redirect from beep-beep-boop | |||
page( '/post/:site?/:post?', sitesController.siteSelection, sitesController.fetchJetpackSettings, controller.post ); | |||
page( '/post/revision/:site?/:post?', sitesController.siteSelection, sitesController.fetchJetpackSettings, controller.revision ); |
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'd prefer to see /post/:site/:post/revisions
(note: revisions
comes last, and there are no ?
or optional parameters). I don't think we can ever reach this point before selecting a site and a post.
Also, it doesn't have to be done in this PR, but it's worth thinking about whether the approach we're using will handle revisions for post types other than post
(page
and custom post types).
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.
/post/:site/:post/revisions
will do :)
Also, it doesn't have to be done in this PR, but it's worth thinking about whether the approach we're using will handle revisions for post types other than post (page and custom post types).
It seems for pages, there will be some changes needed when querying as the URI changes to use /pages
. But - as you said - considering this one PR is already XL, I will probably tackle that in a subsequent PR.
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.
will do :)
or maybe not ^^. It seems this breaks the expected "position" of the site ID in getSiteFragment
: https://github.com/Automattic/wp-calypso/blob/0492a2f/client/lib/route/path.js#L23
So we should either stick to /post/revisions/:site/:post/
or maybe /revision/:site/:post
. Last solution could be to attack the root cause and get getSiteFragment
to use page.js's params first and then default to exploding the path. It seems most routes are using :site
, I've found only 3 occurences of :siteId
, there could be other weird usages out there so this feels like a risky move tbh.
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.
From getSiteFragment
code that you linked:
There are 2 URL positions where we should look for the site fragment: last (most sections) and second-to-last (post ID is last in editor)
Aren't we just adding a 3rd position here? So, the only changes to getSiteFragment
needed to support /post/:site/:post/revisions
should be as follows:
- change
i = 2
toi = 3
- add
pieces.length
bounds checking, since otherwise we'll try to evaluatepieces[ -1 ]
- update the comments and the tests accordingly
Last solution could be to attack the root cause and get
getSiteFragment
to use page.js's params first and then default to exploding the path ... there could be other weird usages out there so this feels like a risky move tbh.
yes, I also think this could easily break something, whether now or in the future.
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.
Aren't we just adding a 3rd position here? So, the only changes to getSiteFragment needed to support /post/:site/:post/revisions should be as follows:
The more positions we accept, the harder it will be to maintain and the more chances we have of breaking something. After having read #9561, #9445 and #4614 it definitely feels like this is already quite fragile, hence why I'm kinda reluctant at touching it :)
I proposed the idea of using page.js
params as that would free us from any positional guessing. The probability of having a slug-like (or number) at a position where we interpret it as a site identifier is considerably higher than naming a param :site
or :siteId
that wouldn't actually contain a site identifier. The (hopefully) net gain in maintainability makes the "risk" of touching it potentially worth it (but it's still a (very) risky move imho)
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.
Things are less clear in the rest of Calypso because of (some) support for manipulating multiple sites at once, but in the post editor the URLs follow the convention of /verb/site/more-specific-things-here
. Viewing revisions
is a more specific action than viewing a particular post ID, so it should come after the post ID in the URL.
I think the only way adding an extra URL position in getSiteFragment
would break anything is if we have a URL like /verb.with.dots/site.wordpress.com/numeric/numeric
(something that would be newly and incorrectly recognized as a site ID in the length - 3
position). This seems extremely unlikely to me.
In the past when changing a function like getSiteFragment
, I've successfully used a testing approach like this:
- Implement both versions of the function side-by-side
- Add debugging to print out return values from both versions of the code, especially if they differ
- Navigate around the app, hitting as many sections as possible
Unfortunately for the issues you pointed to, this was missed in some of our longer and harder-to-reach routes (#9542 and #9550 for specific examples).
I agree that using page.js
params would be a better design, but it's a much larger (and to me, much scarier) change - currently getSiteFragment
is called using an actual parameterized URL rather than a page.js
route, so this would require restructuring all of the code where it is used.
Finally I agree with @youknowriad's suggestion that we keep this PR to the Redux changes only. There are other things to discuss around URLs, like what the possible flows look like for getting to this feature (different post types, etc) and what that implies about the possible URL structures.
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 think the only way adding an extra URL position in getSiteFragment would break anything is if we have a URL like /verb.with.dots/site.wordpress.com/numeric/numeric (something that would be newly and incorrectly recognized as a site ID in the length - 3 position). This seems extremely unlikely to me.
Yes it's probably a fair assessment of the risk if we add the third-to-last position only in the slug detection (which introduces asymmetry between the slug and numeric ID detections). Adding it to the numeric ID detection might be a bit riskier. (but looking at the current page
calls I couldn't see anything ;) )
Anyway, @youknowriad is right: this isn't needed for this PR ;)
I agree this needs tests.
It's really unfortunate that this endpoint doesn't support pagination. I wasn't aware of that, and it's something we need to get fixed in WP core soon. I would suggest building the feature without this capability yet, but leaving the door open to add it later. Then, once we have a patch for the API endpoint, we can run it on WP.com for a while to make sure it works well, and get it committed to WP core and released as part of the 4.7.x series. One thing you could do in the meantime is create a ticket at https://core.trac.wordpress.org/ and provide some details, including links to the current/previous issues with running out of memory when displaying revisions. |
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.
Great job here. I left some comments. I think the placeholder route is not necessary here, we could keep the PR only for adding redux stuff (minor).
Also, testing the selectors, reducers and action creators would be awesome.
this.request( this.props ); | ||
} | ||
|
||
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.
Should we use componentDidUpdate
instead to trigger side effects? Here's a gist on what should we do where in React Lifecycle methods https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9
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.
TIL, great gist! PR updated :)
( state, siteId, postId ) => { | ||
return map( | ||
get( state.posts.revisions.revisions, [ siteId, postId ], [] ), | ||
normalizePostForDisplay ); |
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.
Minor: I guess it's personal preference, but I like to keep closing/opening parens on their own line
when we have long params like here.
return map(
get( state.posts.revisions.revisions, [ siteId, postId ], [] ),
normalizePostForDisplay
);
Same above with the if
if (
this.props.siteId === nextProps.siteId &&
this.props.postId === nextProps.postId
) {
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.
np, I wonder if it's worth maybe updating the if
example in the Javascript Guidelines?
Currently it's showing:
if ( firstCondition() && secondCondition() &&
thirdCondition() ) {
doStuff();
}
Fixed comments + added tests for the reducers / selectors. I didn't see much value in creating tests for the action creators as they are currently "dumb" arrow functions with no logic in them. As suggested by @nylen I also just created a ticket on trac to track the REST endpoint pagination enhancement request in WP Core: https://core.trac.wordpress.org/ticket/40510 . That said, while testing more edge cases / wordpress setups for revisions, I found out that Wordpress.com sites have a default limit of 25 revisions which means pagination wouldn't bring lots of value, but it could be useful for Jetpack sites? |
...state[ action.siteId ], | ||
[ action.postId ]: action.type === POST_REVISIONS_REQUEST, | ||
}, | ||
}; |
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 use lodash merge
here to avoid the multiple ...
revisions, | ||
} ); | ||
|
||
function normalizeRevisionForState( revision ) { |
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 wonder if we should normalize the objects in the state or in the data middleware layer. I probably have a preference over the data layer (because we could imagine using different APIs with different shapes and reuse the same reducer)
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.
Makes sense, a couple of other places in the data-layer are already normalizing stuff "fromAPI": https://github.com/Automattic/wp-calypso/blob/master/client/state/data-layer/wpcom/read/tags/utils.js
Updated in the latest version + a couple of "dumb" tests of the revisions data-layer.
@@ -42,13 +38,11 @@ export function requesting( state = {}, action ) { | |||
export function revisions( state = {}, action ) { | |||
if ( action.type === POST_REVISIONS_RECEIVE ) { | |||
const { siteId, postId } = action; | |||
return { | |||
...state, | |||
return merge( {}, 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.
Thanks for these updates, this is looking really good. Just one last thing, I think merge
is not suited in this exact place because revision are objects that would be merged recursively here and since the reducer is not really aware of the shape of these objects. So, it's possible to end up with a meged revision object (old and new revision of the same Id) and the intent here is to replace the object completely.
Sorry, If I confused you with my previous comment, I like to use merge
in reducer of scalar values (like above) but not when the reducers objects with unknown shapes.
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.
nice catch!
Sorry, If I confused you with my previous comment, I like to use merge in reducer of scalar values (like above) but not when the reducers objects with unknown shapes.
hehe np, that's why code reviews are great :) that makes a lot of sense, will keep it in mind :)
I've added a "test" for it as well as reverting back to using spreads for that reducer.
…tations of discarding previous content
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 you can just add a README for the query-post-revisions
component.
This is looking really good. Great work 👍
The site ID for which we request post revisions. | ||
|
||
|
||
### `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.
postId ? 😄
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.
oops :D
…h the original action once the request is completed.
What form does this limit take? I.e., when a post would go over 25 revisions, how is this handled? |
it's dropping old revisions to make room for new ones (FIFO style) Not sure how it's done by the backend though. |
First PR for my trial project described on the Team IO P2 blog: a UI for browsing a post's revisions.
While I continue to discuss the UI / UX for a post's history in the editor, this is a first step towards getting the revisions data for a post loaded in Redux. A placeholder page/route has been created and is available at:
/post/revision/<siteId>/<postId>
QueryPostRevisions
componentPOST_REVISIONS_RECEIVE
action and reducer handlingPOST_REVISIONS_REQUEST_(SUCCESS|FAILURE)
action and reducer handlingREADME.md
for new components + JSDocFurther down the line / might not be worth including in this specific PR:
POST_REVISIONS_RECEIVE
action, postponed / not needed for now