Untitled
unknown
javascript
5 years ago
15 kB
11
Indexable
import ReactGA from 'react-ga'
import { C, actions, selectors } from 'state'
import store from 'store'
import * as Sentry from '@sentry/browser'
import defaultClient from 'client'
const message = 'If the issue persists please let us know.'
const queryErrors = {
startLiveShare: { title: 'starting Live Share', message },
addProject: { title: 'adding a project', message },
continueProject: { title: 'staring a project', message },
acceptInvite: { title: 'accepting invite', message },
addMember: { title: 'adding member', message },
removeMember: { title: 'removing member', message },
githubAuth: { title: 'login', message },
gitlabAuth: { title: 'login', message },
bitbucketAuth: { title: 'login', message },
createTeam: { title: 'adding your team', message },
addTeamLeader: { title: 'adding a team leader', message },
renameTeam: { title: 'renaming a team', message },
deleteProject: { title: 'deleting a project', message },
user: {
title: 'fetching your data',
message:
'Refresh the browser. If this happens again, use logout button or let us know.',
},
}
const queriesThatShouldNotifyOnError = Object.keys(queryErrors)
const getLoading = selectors.api.getLoading
const getToken = selectors.getToken
/**
* Gracefully handle mutations. Handles all the async aspects of querying, automatically dispatches redux
* actions for loading, error and success. Adds ability to handle success and error outside of redux,
* for example to set localStorage.
* @param {Object} objectParam mutation takes one param will all the details, like variables living inside
* @param {string} objectParam.name - Name of the mutation, for example githubAuth
* @param {string} objectParam.storeKey - Key where mutation result will be stored unless a custom onSuccessDispatch or onErrorDispatch are provided
* @param {string} objectParam.variables - Mutation variables
* @param {string} objectParam.context - Mutation context
* @param {string} objectParam.errorPolicy - Mutation errorPolicy, defaults to all
* @param {string} objectParam.mutation - Actual mutation
* @param {function} objectParam.onSuccess - Function called with the result after mutation succeeds. Example: user => localStorage.setItem('token', user.token)
* @param {function} objectParam.onError - Function called with the error after mutation fails. Example: () => localStorage.removeItem('token')
* @param {function} objectParam.onSuccessDispatch - Function called with the result - returns action dispatch. Example: user => ({ type: "USER_LOGIN_SUCCESS", payload: user })
* @param {function} objectParam.onSuccessError - Function called with the error - returns action dispatch. Example: error => ({ type: "USER_LOGIN_ERROR", payload: error })
* @param {function} objectParam.dataSelector - Function used to get result data, useful for example for paginated results like data.myProjects.edges
* @param {string} objectParam.client - GraphQL client, for example Github
*/
export const mutation = ({
name,
storeKey = name,
variables,
context,
errorPolicy = 'all',
fetchPolicy = 'no-cache',
mutation,
onLoading,
onSuccess,
onError,
onLoadingDispatch,
onSuccessDispatch,
onErrorDispatch,
dataSelector = data => data[name],
client = defaultClient,
allowRepeated = true,
storeInState = true,
}) => {
return async dispatch => {
const state = store.getState()
const token = getToken(state)
if (getLoading(name)(state) && !allowRepeated) {
dispatch({
type: C.api.SKIPPING_REPEATED_QUERY,
payload: { name, storeKey },
})
return null
}
onLoading && onLoading(storeKey)
if (onLoadingDispatch) {
if (Array.isArray(onLoadingDispatch)) {
onLoadingDispatch.forEach(action => dispatch(action(storeKey)))
} else {
dispatch(onLoadingDispatch(storeKey))
}
} else {
dispatch({
type: C.api.FETCH_START,
payload: { storeKey },
})
}
const requestStartTime = performance.now()
try {
const { data, errors } = await client.mutate({
mutation,
context:
context || context === null
? context
: {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'node',
},
},
variables,
fetchPolicy,
errorPolicy,
})
if (errors) {
throw errors
}
const result = dataSelector(data)
if (result) {
const requestEndTime = performance.now()
const requestTime = requestEndTime - requestStartTime
if (name === 'resetCron') {
dispatch(actions.latency.fullLatencyMeasurement(requestTime))
}
ReactGA.timing({
category: 'Request Performance',
variable: name,
value: requestTime,
})
console.log('Request Performance', requestTime)
}
if (onSuccess) {
if (Array.isArray(onSuccess)) {
onSuccess.forEach(func => func(result))
} else {
onSuccess(result)
}
}
if (storeInState) {
dispatch({
type: C.api.FETCH_SUCCESS,
payload: { storeKey, data: result },
})
} else {
dispatch({
type: C.api.FETCH_SUCCESS,
payload: { storeKey, data: true },
})
}
if (onSuccessDispatch) {
if (Array.isArray(onSuccessDispatch)) {
onSuccessDispatch.forEach(action => dispatch(action(result)))
} else {
dispatch(onSuccessDispatch(result))
}
}
const requestHandlingEndTime = performance.now()
ReactGA.timing({
category: 'Request Handling Performance',
variable: name,
value: requestHandlingEndTime - requestStartTime,
})
console.log(
'Request Handling Performance',
requestHandlingEndTime - requestStartTime,
name
)
return result
} catch (errorObj) {
if (Array.isArray(errorObj)) {
const error = errorObj[0].extensions.code
const errorMessage = errorObj[0].message
onError && onError(error)
if (onErrorDispatch) {
if (Array.isArray(onErrorDispatch)) {
onErrorDispatch.forEach(action => dispatch(action(error)))
} else {
dispatch(onErrorDispatch(error))
}
} else {
dispatch({
type: C.api.FETCH_ERROR,
storeKey,
payload: { error, errorMessage, storeKey },
})
}
const requestEndTime = performance.now()
ReactGA.timing({
category: 'Request Error Performance',
variable: name,
value: requestEndTime - requestStartTime,
})
if (queriesThatShouldNotifyOnError.includes(name.toLowerCase())) {
dispatch(
actions.messages.setMessage({
key: name,
title: `Error happened during ${queryErrors[name].title}`,
message: `${queryErrors[name].message}`,
})
)
Sentry.withScope(scope => {
scope.setExtras({ errorObj, state })
})
}
console.log(
'Request Error Performance',
requestEndTime - requestStartTime,
name
)
return null
} else {
const error = errorObj.toString()
console.log('Error', error)
onError && onError(error)
if (onErrorDispatch) {
if (Array.isArray(onErrorDispatch)) {
onErrorDispatch.forEach(action => dispatch(action(error)))
} else {
dispatch(onErrorDispatch(error))
}
} else {
dispatch({
type: C.api.FETCH_ERROR,
storeKey,
payload: { error, message: error, storeKey },
})
}
const requestEndTime = performance.now()
ReactGA.timing({
category: 'Request Error Performance',
variable: name,
value: requestEndTime - requestStartTime,
})
if (queriesThatShouldNotifyOnError.includes(name.toLowerCase())) {
dispatch(
actions.messages.setMessage({
key: name,
title: `Error happened during ${queryErrors[name].title}`,
message: `${queryErrors[name].message}`,
})
)
Sentry.withScope(scope => {
scope.setExtras({ errorObj, state })
})
}
console.log(
'Request Error Performance',
requestEndTime - requestStartTime,
name
)
return null
}
}
}
}
/**
* Gracefully handle queries. Handles all the async aspects of querying, automatically dispatches redux
* actions for loading, error and success. Adds ability to handle success and error outside of redux,
* for example to set localStorage.
* @param {Object} objectParam mutation takes one param will all the details, like variables living inside
* @param {string} objectParam.name - Name of the mutation, for example githubAuth
* @param {string} objectParam.storeKey - Key where mutation result will be stored unless a custom onSuccessDispatch or onErrorDispatch are provided
* @param {string} objectParam.variables - Mutation variables
* @param {string} objectParam.context - Mutation context
* @param {string} objectParam.errorPolicy - Mutation fetchPolicy, defaults to cache-first
* @param {string} objectParam.errorPolicy - Mutation errorPolicy, defaults to all
* @param {string} objectParam.query - Actual query
* @param {function} objectParam.onSuccess - Function called with the result after query succeeds. Example: user => localStorage.setItem('token', user.token)
* @param {function} objectParam.onError - Function called with the error after query fails. Example: () => localStorage.removeItem('token')
* @param {function} objectParam.onSuccessDispatch - Function called with the result - returns action dispatch. Example: user => ({ type: "USER_LOGIN_SUCCESS", payload: user })
* @param {function} objectParam.onErrorDispatch - Function called with the error - returns action dispatch. Example: error => ({ type: "USER_LOGIN_ERROR", payload: error })
* @param {function} objectParam.dataSelector - Function used to get result data, useful for example for paginated results like data.myProjects.edges
* @param {string} objectParam.client - GraphQL client, for example Github
*/
export const query = ({
name,
storeKey = name,
variables,
context,
fetchPolicy = 'network-only',
errorPolicy = 'all',
query,
onLoading,
onSuccess,
onError,
onLoadingDispatch,
onSuccessDispatch,
onErrorDispatch,
dataSelector = data => data[name],
client = defaultClient,
allowRepeated = true,
isPagination = false,
id,
}) => {
return async dispatch => {
const state = store.getState()
const token = getToken(state)
if (getLoading(name)(state) && !allowRepeated) {
dispatch({
type: C.api.SKIPPING_REPEATED_QUERY,
payload: { name, storeKey },
})
return null
}
onLoading && onLoading(storeKey)
if (onLoadingDispatch) {
if (Array.isArray(onLoadingDispatch)) {
onLoadingDispatch.forEach(action => dispatch(action(storeKey)))
} else {
dispatch(onLoadingDispatch(storeKey))
}
} else {
dispatch({
type: C.api.FETCH_START,
payload: { storeKey },
})
}
const requestStartTime = performance.now()
try {
const { data } = await client.query({
query,
context:
context || context === null
? context
: {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'node',
},
},
variables,
fetchPolicy,
errorPolicy,
})
const result = dataSelector(data)
if (result) {
const requestEndTime = performance.now()
const requestTime = requestEndTime - requestStartTime
if (name === 'resetCron') {
dispatch(actions.latency.fullLatencyMeasurement(requestTime))
}
ReactGA.timing({
category: 'Request Performance',
variable: name,
value: requestTime,
})
console.log('Request Performance', requestTime)
}
if (onSuccess) {
if (Array.isArray(onSuccess)) {
onSuccess.forEach(func => func(result))
} else {
onSuccess(result)
}
}
if (isPagination) {
dispatch({
type: C.api.APPEND_PAGINATION_DATA,
payload: { storeKey, data: result, id },
})
if (storeKey === 'getInvites') {
const flatResult = result?.edges?.flatMap(invite =>
invite?.steps?.flatMap(step => step?.task)
)
dispatch({
type: C.api.APPEND_PAGINATION_DATA,
payload: {
storeKey: 'getProjects',
data: { edges: flatResult },
id: 'candidatesTasks',
},
})
}
} else {
dispatch({
type: C.api.FETCH_SUCCESS,
payload: { storeKey, data: result },
})
}
if (onSuccessDispatch) {
if (Array.isArray(onSuccessDispatch)) {
onSuccessDispatch.forEach(action => dispatch(action(result)))
} else {
dispatch(onSuccessDispatch(result))
}
}
const requestHandlingEndTime = performance.now()
ReactGA.timing({
category: 'Request Handling Performance',
variable: name,
value: requestHandlingEndTime - requestStartTime,
})
console.log(
'Request Handling Performance',
requestHandlingEndTime - requestStartTime,
name
)
return result
} catch (errorObj) {
const error = errorObj.toString()
console.log('fetch error: ', error, 'errorObj', errorObj)
if (queriesThatShouldNotifyOnError.includes(name.toLowerCase())) {
dispatch(
actions.messages.setMessage({
key: name,
title: `Error happened during ${queryErrors[name].title}`,
message: `${queryErrors[name].message}`,
})
)
Sentry.withScope(scope => {
scope.setExtras({
errorObj,
state,
})
})
}
onError && onError(error)
if (onErrorDispatch) {
if (Array.isArray(onErrorDispatch)) {
onErrorDispatch.forEach(action => dispatch(action(error)))
} else {
dispatch(onErrorDispatch(error))
}
} else {
dispatch({
type: C.api.FETCH_ERROR,
storeKey,
payload: { error, errorMessage: error, storeKey },
})
}
const requestEndTime = performance.now()
ReactGA.timing({
category: 'Request Error Performance',
variable: name,
value: requestEndTime - requestStartTime,
})
console.log(
'Request Error Performance',
requestEndTime - requestStartTime,
name
)
return null
}
}
}
Editor is loading...