Skip to content

Commit

Permalink
JITMs: add redux structure and REST API client method to be used by t…
Browse files Browse the repository at this point in the history
…he JITM component of the Jetpack Dashboard (Spin-off of #10759). (#10818)

This PR is an extract of #10759 in order to simplify that PR and scope it to UI changes.

Author: @jeherve 

#### Changes proposed in this Pull Request:

* Introduces `_inc/client/state/jitm` and files for reducer, actions and tests.
* Introduces method `fetchJitm` in the REST API client.

#### Testing instructions:


* Checkout this branch
* Run `yarn test-client _inc/client/state/jitm/test/*`
* Confirm tests pass

#### Proposed changelog entry for your changes:

None needed
  • Loading branch information
oskosk authored and jeherve committed Dec 5, 2018
1 parent 3c05813 commit dabff02
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 3 deletions.
24 changes: 22 additions & 2 deletions _inc/client/rest-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require( 'es6-promise' ).polyfill();
import 'whatwg-fetch';
import assign from 'lodash/assign';
import head from 'lodash/head';

/**
* Helps create new custom error classes to better notify upper layers.
Expand Down Expand Up @@ -266,8 +267,27 @@ function JetpackRestApiClient( root, nonce ) {
verifySiteGoogle: ( keyringId ) => postRequest( `${ apiRoot }jetpack/v4/verify-site/google`, postParams, {
body: JSON.stringify( { keyring_id: keyringId } ),
} )
.then( checkStatus )
.then( parseJsonResponse )
.then( checkStatus )
.then( parseJsonResponse ),

fetchJitm: ( message_path, query_url ) => {
const requestUrl = `${ apiRoot }jetpack/v4/jitm?message_path=${ encodeURIComponent( message_path ) }&query=${ encodeURIComponent( query_url ) }`;

return getRequest( requestUrl, getParams )
.then( checkStatus )
.then( parseJsonResponse )
.then( messages => ( head( messages ) ) );
},

dismissJitm: ( id, feature_class ) => postRequest(
`${ apiRoot }jetpack/v4/jitm`,
postParams,
{
body: JSON.stringify( { id, feature_class } )
}
)
.then( checkStatus )
.then( parseJsonResponse )
};

function addCacheBuster( route ) {
Expand Down
7 changes: 7 additions & 0 deletions _inc/client/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,10 @@ export const JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH_SUCCESS = 'JETPACK_SITE_VER
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST';
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS';
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL';

export const JITM_FETCH = 'JITM_FETCH';
export const JITM_FETCH_RECEIVE = 'JITM_FETCH_RECEIVE';
export const JITM_FETCH_FAIL = 'JITM_FETCH_FAIL';
export const JITM_DISMISS = 'JITM_DISMISS';
export const JITM_DISMISS_SUCCESS = 'JITM_DISMISS_SUCCESS';
export const JITM_DISMISS_FAIL = 'JITM_DISMISS_FAIL';
56 changes: 56 additions & 0 deletions _inc/client/state/jitm/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Internal dependencies
*/
import {
JITM_FETCH,
JITM_FETCH_RECEIVE,
JITM_FETCH_FAIL,
JITM_DISMISS,
JITM_DISMISS_SUCCESS,
JITM_DISMISS_FAIL,
} from 'state/action-types';
import restApi from 'rest-api';

export const fetchJitm = ( message_path = '', query_url = 'page=jetpack' ) => {
return dispatch => {
dispatch( {
type: JITM_FETCH
} );
return restApi
.fetchJitm( message_path, query_url )
.then( message => {
dispatch( {
type: JITM_FETCH_RECEIVE,
message: message
} );
} )
.catch( error => {
dispatch( {
type: JITM_FETCH_FAIL,
error: error
} );
} );
};
};

export const getJitmDismissalResponse = ( id, feature_class ) => {
return dispatch => {
dispatch( {
type: JITM_DISMISS
} );
return restApi
.dismissJitm( id, feature_class )
.then( response => {
dispatch( {
type: JITM_DISMISS_SUCCESS,
response: response
} );
} )
.catch( error => {
dispatch( {
type: JITM_DISMISS_FAIL,
error: error
} );
} );
};
};
9 changes: 9 additions & 0 deletions _inc/client/state/jitm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Internal dependencies
*/
import * as reducer from './reducer';
import * as actions from './actions';

const all = { ...reducer, ...actions };

export default all;
102 changes: 102 additions & 0 deletions _inc/client/state/jitm/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* External dependencies
*/
import { combineReducers } from 'redux';
import get from 'lodash/get';
import assign from 'lodash/assign';

/**
* Internal dependencies
*/
import {
JITM_FETCH,
JITM_FETCH_RECEIVE,
JITM_FETCH_FAIL,
JITM_DISMISS,
JITM_DISMISS_SUCCESS,
JITM_DISMISS_FAIL
} from 'state/action-types';

export const data = ( state = {}, action ) => {
switch ( action.type ) {
case JITM_FETCH_RECEIVE:
return assign( {}, state, { message: action.message } );
case JITM_DISMISS_SUCCESS:
return assign( {}, state, { response: action.response } );
default:
return state;
}
};

export const initialRequestsState = {
isFetchingJitm: false,
isDismissingJitm: false,
};

export const requests = ( state = initialRequestsState, action ) => {
switch ( action.type ) {
case JITM_FETCH:
return assign( {}, state, {
isFetchingJitm: true
} );
case JITM_FETCH_RECEIVE:
case JITM_FETCH_FAIL:
return assign( {}, state, {
isFetchingJitm: false
} );
case JITM_DISMISS:
return assign( {}, state, {
isDismissingJitm: true
} );
case JITM_DISMISS_SUCCESS:
case JITM_DISMISS_FAIL:
return assign( {}, state, {
isDismissingJitm: false
} );
default:
return state;
}
};

export const reducer = combineReducers( {
data,
requests
} );

/**
* Returns true if currently requesting a JITM message. Otherwise false.
*
* @param {Object} state Global state tree
* @return {Boolean} Whether a JITM is being requested
*/
export function isFetchingJitm( state ) {
return !! state.jetpack.jitm.requests.isFetchingJitm;
}

/**
* Returns true if currently requesting the dismissal of a JITM message. Otherwise false.
*
* @param {Object} state Global state tree
* @return {Boolean} Whether a JITM is being dismissed.
*/
export function isDismissingJitm( state ) {
return !! state.jetpack.jitm.requests.isDismissingJitm;
}

/**
* Returns the current JITM message
* @param {Object} state Global state tree
* @return {Object} Features
*/
export function getJitm( state ) {
return get( state.jetpack.jitm, [ 'data', 'message' ], {} );
}

/**
* Dismiss the current JITM message
* @param {Object} state Global state tree
* @return {Object} Response
*/
export function getJitmDismissalResponse( state ) {
return get( state.jetpack.jitm, [ 'data', 'response' ], {} );
}
50 changes: 50 additions & 0 deletions _inc/client/state/jitm/test/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from 'chai';

import {
data as dataReducer,
requests as requestsReducer,
initialRequestsState
} from '../reducer';

describe( 'data reducer', () => {
it( 'data state should default to empty object', () => {
const state = dataReducer( undefined, {} );
expect( state ).to.eql( {} );
} );
} );

describe( 'requests reducer', () => {
it( 'requests state should default to empty object', () => {
const state = requestsReducer( undefined, {} );
expect( state ).to.eql( initialRequestsState );
} );

describe( '#fetchJitm', () => {
it( 'should set isFetchingJitm to true when fetching JITMs', () => {
const stateIn = {};
const action = {
type: 'JITM_FETCH'
};
let stateOut = requestsReducer( stateIn, action );
expect( stateOut.isFetchingJitm ).to.be.true;
} );

it( 'should set isFetchingJitm to false when JITM fetch succeeds', () => {
const stateIn = {};
const action = {
type: 'JITM_FETCH_RECEIVE'
};
let stateOut = requestsReducer( stateIn, action );
expect( stateOut.isFetchingJitm ).to.be.false;
} );

it( 'should set isFetchingJitm to false when fetching JITMs fails', () => {
const stateIn = {};
const action = {
type: 'JITM_FETCH_FAIL'
};
let stateOut = requestsReducer( stateIn, action );
expect( stateOut.isFetchingJitm ).to.be.false;
} );
} );
} );
38 changes: 38 additions & 0 deletions _inc/client/state/jitm/test/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect } from 'chai';

import {
fetchJitm,
getJitm,
isFetchingJitm,
} from '../reducer';

let state = {
jetpack: {
jitm: {
data: {
message: 'Hi'
},
requests: {
isFetchingJitm: true
}
}
}
};

describe( 'status selectors', () => {
describe( '#getJitm', () => {
it( 'should return state.jetpack.jitm.data.message', () => {
const stateIn = state;
const output = getJitm( stateIn );
expect( output ).to.equal( state.jetpack.jitm.data.message );
} );
} );

describe( '#isFetchingJitm', () => {
it( 'should return state.jetpack.jitm.requests.isFetchingJim', () => {
const stateIn = state;
const output = isFetchingJitm( stateIn );
expect( output ).to.equal( state.jetpack.jitm.requests.isFetchingJitm );
} );
} );
} )
4 changes: 3 additions & 1 deletion _inc/client/state/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { reducer as search } from 'state/search/reducer';
import { reducer as devCard } from 'state/dev-version/reducer';
import { reducer as publicize } from 'state/publicize/reducer';
import { reducer as siteVerify } from 'state/site-verify/reducer';
import { reducer as jitm } from 'state/jitm/reducer';

const jetpackReducer = combineReducers( {
initialState,
Expand All @@ -39,7 +40,8 @@ const jetpackReducer = combineReducers( {
search,
devCard,
publicize,
siteVerify
siteVerify,
jitm,
} );

export default combineReducers( {
Expand Down

0 comments on commit dabff02

Please sign in to comment.