Untitled
unknown
javascript
4 years ago
15 kB
6
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...