Skip to content

Commit

Permalink
Editor: Post revisions into redux (#12733)
Browse files Browse the repository at this point in the history
* Post: Add `/revision` route + early infrastructure to load revisions: `QueryPostRevisions`
* Post Revisions: Normalize revisions for state
* Post Revisions: Add `requesting` to track the status of revisions requests in Redux
* Post Revisions: Use `data-layer` instead of redux-thunk to fetch revisions
* Post Revisions: Tests for reducers
* Post Revisions: Tests for selectors
  • Loading branch information
bperson authored and youknowriad committed Apr 24, 2017
1 parent 971f415 commit 63bfc14
Show file tree
Hide file tree
Showing 14 changed files with 770 additions and 0 deletions.
46 changes: 46 additions & 0 deletions client/components/data/query-post-revisions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Query Post Revisions
================

`<QueryPostRevisions />` is a React component used to request post revisions data.

## Usage

Render the component, passing `siteId` and `postId`. It does not accept any children, nor does it render any element to the page. You can use it adjacent to other sibling components which make use of the fetched data made available through the global application state.

```jsx
import React from 'react';
import QueryPostRevisions from 'components/data/query-post-revisions';

export default function PostRevisions( { revisions } ) {
return (
<div>
<QueryPostRevisions
siteId={ 12345678 }
postId={ 10 }
/>
<div>{ revisions }</div>
</div>
);
}
```

## Props

### `siteId`

<table>
<tr><th>Type</th><td>Number</td></tr>
<tr><th>Required</th><td>Yes</td></tr>
</table>

The site ID for which we request post revisions.


### `postId`

<table>
<tr><th>Type</th><td>Number</td></tr>
<tr><th>Required</th><td>Yes</td></tr>
</table>

The post ID for which we request post revisions.
46 changes: 46 additions & 0 deletions client/components/data/query-post-revisions/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { Component, PropTypes } from 'react';
import { connect } from 'react-redux';

/**
* Internal dependencies
*/
import { requestPostRevisions } from 'state/posts/revisions/actions';

class QueryPostRevisions extends Component {
componentWillMount() {
this.request();
}

componentDidUpdate( prevProps ) {
if (
this.props.siteId === prevProps.siteId &&
this.props.postId === prevProps.postId
) {
return;
}

this.request();
}

request() {
this.props.requestPostRevisions( this.props.siteId, this.props.postId );
}

render() {
return null;
}
}

QueryPostRevisions.propTypes = {
postId: PropTypes.number,
siteId: PropTypes.number,
requestPostRevisions: PropTypes.func,
};

export default connect(
() => ( {} ),
{ requestPostRevisions }
)( QueryPostRevisions );
4 changes: 4 additions & 0 deletions client/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ export const POST_REQUEST_SUCCESS = 'POST_REQUEST_SUCCESS';
export const POST_RESTORE = 'POST_RESTORE';
export const POST_RESTORE_FAILURE = 'POST_RESTORE_FAILURE';
export const POST_RESTORE_SUCCESS = 'POST_RESTORE_SUCCESS';
export const POST_REVISIONS_RECEIVE = 'POST_REVISIONS_RECEIVE';
export const POST_REVISIONS_REQUEST = 'POST_REVISIONS_REQUEST';
export const POST_REVISIONS_REQUEST_FAILURE = 'POST_REVISIONS_REQUEST_FAILURE';
export const POST_REVISIONS_REQUEST_SUCCESS = 'POST_REVISIONS_REQUEST_SUCCESS';
export const POST_SAVE = 'POST_SAVE';
export const POST_SAVE_FAILURE = 'POST_SAVE_FAILURE';
export const POST_SAVE_SUCCESS = 'POST_SAVE_SUCCESS';
Expand Down
2 changes: 2 additions & 0 deletions client/state/data-layer/wpcom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { mergeHandlers } from 'state/data-layer/utils';
import accountRecovery from './account-recovery';
import me from './me';
import plans from './plans';
import posts from './posts';
import read from './read';
import sites from './sites';
import timezones from './timezones';
Expand All @@ -15,6 +16,7 @@ export const handlers = mergeHandlers(
accountRecovery,
me,
plans,
posts,
read,
sites,
timezones,
Expand Down
6 changes: 6 additions & 0 deletions client/state/data-layer/wpcom/posts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import revisions from './revisions';

export default revisions;
92 changes: 92 additions & 0 deletions client/state/data-layer/wpcom/posts/revisions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* External dependencies
*/
import { flow, map } from 'lodash';
import mapValues from 'lodash/fp/mapValues';
import pick from 'lodash/fp/pick';

/**
* Internal dependencies
*/
import {
POST_REVISIONS_REQUEST,
} from 'state/action-types';
import { dispatchRequest } from 'state/data-layer/wpcom-http/utils';
import { http } from 'state/data-layer/wpcom-http/actions';
import {
receivePostRevisions,
receivePostRevisionsSuccess,
receivePostRevisionsFailure,
} from 'state/posts/revisions/actions';

/**
* Normalize a WP REST API Post Revisions ressource for consumption in Calypso
*
* @param {Object} revision Raw revision from the API
* @returns {Object} the normalized revision
*/
export function normalizeRevision( revision ) {
if ( ! revision ) {
return revision;
}

return {
...revision,
...flow(
pick( [ 'title', 'content', 'excerpt' ] ),
mapValues( ( val = {} ) => val.rendered )
)( revision )
};
}

/**
* Dispatches returned error from post revisions request
*
* @param {Function} dispatch Redux dispatcher
* @param {Object} action Redux action
* @param {String} action.siteId of the revisions
* @param {String} action.postId of the revisions
* @param {Function} next dispatches to next middleware in chain
* @param {Object} rawError from HTTP request
* @returns {Object} the dispatched action
*/
export const receiveError = ( { dispatch }, { siteId, postId }, next, rawError ) =>
dispatch( receivePostRevisionsFailure( siteId, postId, rawError ) );

/**
* Dispatches returned post revisions
*
* @param {Function} dispatch Redux dispatcher
* @param {Object} action Redux action
* @param {String} action.siteId of the revisions
* @param {String} action.postId of the revisions
* @param {Function} next dispatches to next middleware in chain
* @param {Array} revisions raw data from post revisions API
*/
export const receiveSuccess = ( { dispatch }, { siteId, postId }, next, revisions ) => {
dispatch( receivePostRevisionsSuccess( siteId, postId ) );
dispatch( receivePostRevisions( siteId, postId, map( revisions, normalizeRevision ) ) );
};

/**
* Dispatches a request to fetch post revisions
*
* @param {Function} dispatch Redux dispatcher
* @param {Object} action Redux action
*/
export const fetchPostRevisions = ( { dispatch }, action ) => {
const { siteId, postId } = action;
dispatch( http( {
path: `/sites/${ siteId }/posts/${ postId }/revisions`,
method: 'GET',
query: {
apiNamespace: 'wp/v2',
},
}, action ) );
};

const dispatchPostRevisionsRequest = dispatchRequest( fetchPostRevisions, receiveSuccess, receiveError );

export default {
[ POST_REVISIONS_REQUEST ]: [ dispatchPostRevisionsRequest ]
};
114 changes: 114 additions & 0 deletions client/state/data-layer/wpcom/posts/revisions/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* External dependencies
*/
import { expect } from 'chai';
import { map } from 'lodash';
import sinon from 'sinon';

/**
* Internal dependencies
*/
import {
fetchPostRevisions,
normalizeRevision,
receiveSuccess,
receiveError,
} from '../';
import {
receivePostRevisions,
receivePostRevisionsSuccess,
receivePostRevisionsFailure,
requestPostRevisions,
} from 'state/posts/revisions/actions';
import { http } from 'state/data-layer/wpcom-http/actions';

const successfulPostRevisionsResponse = [
{
author: 1,
date: '2017-04-20T12:14:40',
date_gmt: '2017-04-20T12:14:40',
id: 11,
modified: '2017-04-21T12:14:40',
modified_gmt: '2017-04-21T12:14:40',
parent: 10,
title: {
rendered: 'Sed nobis ab earum',
},
content: {
rendered: '<p>Lorem ipsum</p>',
},
excerpt: {
rendered: '',
},
},
];

const normalizedPostRevisions = [
{
author: 1,
date: '2017-04-20T12:14:40',
date_gmt: '2017-04-20T12:14:40',
id: 11,
modified: '2017-04-21T12:14:40',
modified_gmt: '2017-04-21T12:14:40',
parent: 10,
title: 'Sed nobis ab earum',
content: '<p>Lorem ipsum</p>',
excerpt: '',
},
];

describe( '#normalizeRevision', () => {
it( 'should only keep the rendered version of `title`, `content` and `excerpt`', () => {
expect(
map( successfulPostRevisionsResponse, normalizeRevision )
).to.eql( normalizedPostRevisions );
} );
} );

describe( '#fetchPostRevisions', () => {
it( 'should dispatch HTTP request to tag endpoint', () => {
const action = requestPostRevisions( 12345678, 10 );
const dispatch = sinon.spy();
const next = sinon.spy();

fetchPostRevisions( { dispatch }, action, next );

expect( dispatch ).to.have.been.calledOnce;
expect( dispatch ).to.have.been.calledWith( http( {
method: 'GET',
path: '/sites/12345678/posts/10/revisions',
query: {
apiNamespace: 'wp/v2',
},
}, action ) );
} );
} );

describe( '#receiveSuccess', () => {
it( 'should normalize the revisions and dispatch `receivePostRevisions` and `receivePostRevisionsSuccess`', () => {
const action = requestPostRevisions( 12345678, 10 );
const dispatch = sinon.spy();
const next = sinon.spy();

receiveSuccess( { dispatch }, action, next, successfulPostRevisionsResponse );

expect( dispatch ).to.have.been.calledTwice;
expect( dispatch ).to.have.been.calledWith( receivePostRevisionsSuccess( 12345678, 10 ) );
expect( dispatch ).to.have.been.calledWith( receivePostRevisions( 12345678, 10, normalizedPostRevisions ) );
} );
} );

describe( '#receiveError', () => {
it( 'should dispatch `receivePostRevisionsFailure`', () => {
const action = requestPostRevisions( 12345678, 10 );
const dispatch = sinon.spy();
const next = sinon.spy();
const rawError = new Error( 'Foo Bar' );

receiveError( { dispatch }, action, next, rawError );

expect( dispatch ).to.have.been.calledOnce;
expect( dispatch ).to.have.been.calledWith( receivePostRevisionsFailure( 12345678, 10, rawError ) );
} );
} );
2 changes: 2 additions & 0 deletions client/state/posts/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from 'state/action-types';
import counts from './counts/reducer';
import likes from './likes/reducer';
import revisions from './revisions/reducer';
import {
getSerializedPostsQuery,
isTermsEqual,
Expand Down Expand Up @@ -315,4 +316,5 @@ export default combineReducers( {
queries,
edits,
likes,
revisions,
} );
Loading

0 comments on commit 63bfc14

Please sign in to comment.