Skip to content

Commit

Permalink
feat(ZNTA-975): add middleware to include meta.timestamp in all API c…
Browse files Browse the repository at this point in the history
…alls

The middleware also adds some repeated defaults such as credentials and
JSON headers, which avoids the need to wrap calls in a helper function.
I chose a distinct type rather than reusing CALL_API so that there is a
way to skip the new behaviour if that is ever needed.
  • Loading branch information
davidmason committed Aug 4, 2017
1 parent 6315977 commit 1f76246
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { debounce, isEmpty } from 'lodash'
import { CALL_API } from 'redux-api-middleware'
import { CALL_API_ENHANCED } from '../middlewares/call-api'

import {
GLOSSARY_SEARCH_TEXT_CHANGE,
Expand All @@ -20,7 +20,6 @@ import {

import { baseRestUrl } from '../api'
import { waitForPhraseDetail } from '../utils/phrase-util'
import { getJsonWithCredentials } from '../utils/api-util'

/* Call as search text changes to trigger a glossary search when the text stops
* changing. This prevents excessive requests while the user is typing.
Expand Down Expand Up @@ -100,20 +99,17 @@ function findGlossaryTerms (searchText) {
`${baseRestUrl}/glossary/search?srcLocale=${srcLocale}&transLocale=${transLocale}&project=${projectSlug}&searchText=${encodeURIComponent(searchText)}&maxResults=${MAX_GLOSSARY_TERMS}` // eslint-disable-line max-len

dispatch({
[CALL_API]: getJsonWithCredentials({
[CALL_API_ENHANCED]: {
endpoint: glossaryUrl,
types: [
GLOSSARY_TERMS_REQUEST,
{
type: GLOSSARY_TERMS_SUCCESS,
meta: { timestamp, searchText }
meta: { searchText }
},
{
type: GLOSSARY_TERMS_FAILURE,
meta: { timestamp }
}
GLOSSARY_TERMS_FAILURE
]
})
}
})
}
}
Expand Down Expand Up @@ -162,7 +158,7 @@ function getGlossaryDetails (term) {
`${baseRestUrl}/glossary/details/${transLocale}?${termIdsQuery}`

dispatch({
[CALL_API]: getJsonWithCredentials({
[CALL_API_ENHANCED]: {
endpoint: glossaryDetailsUrl,
types: [
GLOSSARY_DETAILS_REQUEST,
Expand All @@ -174,7 +170,7 @@ function getGlossaryDetails (term) {
type: GLOSSARY_DETAILS_FAILURE
}
]
})
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* Enahanced CALL_API that sets some defaults and gives a better API */

import { CALL_API } from 'redux-api-middleware'

// use this key instead of CALL_API to trigger this middleware
export const CALL_API_ENHANCED = Symbol('CALL_API_ENHANCED')

/*
* Applies the following enhancements before passing to redux-api-middleware:
*
* - defaults method to 'GET' if not specified
* - always include credentials
* - sets JSON content type and accept headers
* - adds meta.timestamp to request, success and failure actions
*/
export default store => next => action => {
if (action[CALL_API_ENHANCED]) {
const { headers, types, ...remainingKeys } = action[CALL_API_ENHANCED]
return next({
[CALL_API]: {
// default, overridden if remainingKeys.method is present
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...headers
},
types: typesWithTimestamp(types),
// remaining keys are last to allow overriding anything above
...remainingKeys
}
})
} else {
return next(action)
}
}

/* Add meta.timestamp to all the given methods */
function typesWithTimestamp ([request, success, failure]) {
const timestamp = Date.now()
return [
withTimestamp(request, timestamp),
withTimestamp(success, timestamp),
withTimestamp(failure, timestamp)
]
}

/* Add meta.timestamp to a redux-api-middleware type descriptor */
// Note: may not work properly when meta is or returns a promise. If we start
// using promises for meta, this should be updated.
function withTimestamp (type, timestamp) {
const normalType = typeof type === 'string' || typeof type === 'symbol'
? { type }
: type
const oldMeta = normalType.meta || {}
const meta = typeof oldMeta === 'function'
? (...args) => ({ ...(oldMeta(...args)), timestamp })
: { ...oldMeta, timestamp }
return { ...normalType, meta }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createStore, applyMiddleware } from 'redux'
import { apiMiddleware } from 'redux-api-middleware'
import enhancedCallApi from './call-api'
import newContextFetchMiddleware from './new-context-fetch'
import getStateInActions from './getstate-in-actions'
import titleUpdateMiddleware from './title-update'
Expand All @@ -26,6 +27,7 @@ const createStoreWithMiddleware =
newContextFetchMiddleware,
// reduxRouterMiddleware,
thunk,
enhancedCallApi,
apiMiddleware,
// must run after thunk because it fails with thunks
getStateInActions,
Expand Down
33 changes: 0 additions & 33 deletions server/zanata-frontend/src/frontend/app/editor/utils/api-util.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import watch from './watch'
import { getCurrentPagePhrasesAndLocale } from '../selectors'
import { baseRestUrl } from '../api'
import { fill, isEmpty, mapValues } from 'lodash'
import { getJsonWithCredentials } from '../utils/api-util'
import { CALL_API, getJSON } from 'redux-api-middleware'
import { getJSON } from 'redux-api-middleware'
import { CALL_API_ENHANCED } from '../middlewares/call-api'
import {
STATUS_UNTRANSLATED,
transUnitStatusToPhraseStatus
Expand Down Expand Up @@ -44,22 +44,18 @@ function fetchPhraseDetail (locale, phraseIds) {
const phraseDetailUrl =
`${baseRestUrl}/source+trans/${locale.id}?ids=${phraseIds.join(',')}`
return {
[CALL_API]: getJsonWithCredentials({
[CALL_API_ENHANCED]: {
endpoint: phraseDetailUrl,
types: [
{
type: PHRASE_DETAIL_REQUEST
},
PHRASE_DETAIL_REQUEST,
{
type: PHRASE_DETAIL_SUCCESS,
payload: (action, state, res) => getJSON(res)
.then(details => transUnitDetailToPhraseDetail(details, locale))
},
{
type: PHRASE_DETAIL_FAILURE
}
PHRASE_DETAIL_FAILURE
]
})
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { createSelector } from 'reselect'
import watch from './watch'
import { debounce, every, isEmpty } from 'lodash'
import { getLang } from '../selectors'
import { CALL_API, getJSON } from 'redux-api-middleware'
import { jsonWithCredentials } from '../utils/api-util'
import { getJSON } from 'redux-api-middleware'
import { CALL_API_ENHANCED } from '../middlewares/call-api'
import { encode } from '../utils/doc-id-util'
import { baseRestUrl } from '../api'
import { transUnitStatusToPhraseStatus } from '../utils/status-util'
Expand Down Expand Up @@ -97,7 +97,7 @@ function fetchPhraseList (project, version, localeId, docId, filter) {
`${baseRestUrl}/project/${project}/version/${version}/doc/${encodedId}/status/${localeId}` // eslint-disable-line max-len

return {
[CALL_API]: jsonWithCredentials({
[CALL_API_ENHANCED]: {
endpoint: url,
method: 'POST',
body: JSON.stringify(filter || {}),
Expand All @@ -123,6 +123,6 @@ function fetchPhraseList (project, version, localeId, docId, filter) {
meta: { filter: filtered }
}
]
})
}
}
}

0 comments on commit 1f76246

Please sign in to comment.