From fb7a697004761dfaf4e3234054a13679e88cb15a Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Sat, 26 Oct 2019 00:07:32 -0500 Subject: [PATCH 1/4] Refactor error reporting system --- packages/app/App.tsx | 47 +++++++------------ .../xcshareddata/xcschemes/WHS.xcscheme | 2 +- packages/app/src/actions/async.ts | 3 +- packages/app/src/constants/fetch.ts | 11 +++-- packages/app/src/screens/AddSchedule.tsx | 13 ++--- packages/app/src/screens/Login.tsx | 9 ++-- packages/app/src/screens/Settings.tsx | 9 +--- packages/app/src/utils/process-info.ts | 18 +++++-- packages/app/src/utils/utils.ts | 45 ++++++++++++------ 9 files changed, 85 insertions(+), 72 deletions(-) diff --git a/packages/app/App.tsx b/packages/app/App.tsx index 53d97a8..eafd0b3 100755 --- a/packages/app/App.tsx +++ b/packages/app/App.tsx @@ -25,10 +25,8 @@ import { getScheduleTypeOnDate, isScheduleEmpty } from './src/utils/query-schedu import Settings from './src/screens/Settings'; import AddSchedule from './src/screens/AddSchedule'; import registerNotificationScheduler, { scheduleNotifications } from './src/utils/notifications'; -import { reportScheduleCaution, notify } from './src/utils/utils'; +import { reportScheduleCaution, reportError } from './src/utils/utils'; import client from './src/utils/bugsnag'; -import { LoginError } from './src/utils/error'; -import { LOGIN_CREDENTIALS_CHANGED_MSG } from './src/constants/fetch'; interface AppComponentState { rehydrated: boolean; @@ -77,34 +75,26 @@ export default class App extends Component<{}, AppComponentState> { } = store.getState(); const now = new Date(); - try { - if (semesterOneStart === null || semesterTwoStart === null || semesterTwoEnd === null) { - return; - } - if (isAfter(now, semesterTwoEnd)) { - client.leaveBreadcrumb('Refreshing dates after end of year'); + if (semesterOneStart === null || semesterTwoStart === null || semesterTwoEnd === null) { + return; + } + if (isAfter(now, semesterTwoEnd)) { + client.leaveBreadcrumb('Refreshing dates after end of year'); - await store.dispatch(fetchDates(now.getFullYear())); - store.dispatch(setRefreshed([false, false])); - return PushNotification.cancelAllLocalNotifications(); - } + await store.dispatch(fetchDates(now.getFullYear())); + store.dispatch(setRefreshed([false, false])); + return PushNotification.cancelAllLocalNotifications(); + } - const shouldRefresh = (isAfter(now, semesterTwoStart) && !refreshedSemesterTwo) - || (isAfter(now, semesterOneStart) && !refreshedSemesterOne); - if (isScheduleEmpty(schedule) || shouldRefresh) { - client.leaveBreadcrumb('Refreshing semesters one/two'); + const shouldRefresh = (isAfter(now, semesterTwoStart) && !refreshedSemesterTwo) + || (isAfter(now, semesterOneStart) && !refreshedSemesterOne); + if (isScheduleEmpty(schedule) || shouldRefresh) { + client.leaveBreadcrumb('Refreshing semesters one/two'); - await store.dispatch(fetchUserInfo(username, password)); - await scheduleNotifications(); - } - await registerNotificationScheduler(); - } catch (error) { - if (error instanceof LoginError) { - return notify('Error', LOGIN_CREDENTIALS_CHANGED_MSG); - } - // rethrow to handle in handleRehydrate - throw error; + await store.dispatch(fetchUserInfo(username, password)); + await scheduleNotifications(); } + await registerNotificationScheduler(); } private silentlyUpdateData = async () => { @@ -188,8 +178,7 @@ export default class App extends Component<{}, AppComponentState> { } this.setState({ rehydrated: true }); } catch (error) { - notify('Error', 'Something went wrong, please try restarting the app.'); - client.notify(error); + reportError(error); } } diff --git a/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme b/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme index 0223b76..28b5cda 100644 --- a/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme +++ b/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme @@ -84,7 +84,7 @@ { controller.abort(); try { - const response = await fetch(getTeacherSearchURL(teacherQuery, TEACHER_FETCH_LIMIT, username, password), { - method: 'POST', - timeout: FETCH_TIMEOUT, - signal, - }); - const { teachers: json } = await response.json(); + const json = await fetchTeachersFromQuery(teacherQuery, username, password, signal); const teachersAvailable = json.filter(({ firstName, lastName, email }: RawTeacherData) => { const alreadyAdded = teacherSchedules.some((teacherObj) => teacherObj.name === `${firstName} ${lastName}`); return email !== null && !alreadyAdded; diff --git a/packages/app/src/screens/Login.tsx b/packages/app/src/screens/Login.tsx index 0805fa5..b8bce59 100755 --- a/packages/app/src/screens/Login.tsx +++ b/packages/app/src/screens/Login.tsx @@ -65,11 +65,12 @@ export default memo(function Login(props: NavigationScreenProps) { dispatch(setDaySchedule(getScheduleTypeOnDate(now, updatedDates))); props.navigation.navigate('Dashboard'); } catch (error) { - setError(true); - setLoading(false); - if (!(error instanceof LoginError)) { - reportError(error); + if (error instanceof LoginError) { + setError(true); + setLoading(false); + return; } + reportError(error); } }; diff --git a/packages/app/src/screens/Settings.tsx b/packages/app/src/screens/Settings.tsx index b704c47..c03358e 100644 --- a/packages/app/src/screens/Settings.tsx +++ b/packages/app/src/screens/Settings.tsx @@ -18,8 +18,7 @@ import ButtonGroup from '../components/drawer/ButtonGroup'; import client from '../utils/bugsnag'; import { SUBTEXT_SIZE } from '../constants/style'; import { scheduleNotifications } from '../utils/notifications'; -import { LoginError } from '../utils/error'; -import { LOGIN_CREDENTIALS_CHANGED_MSG } from '../constants/fetch'; +import { SUCCESS, REFRESHED_MSG } from '../constants/fetch'; const SettingIcon = styled(Icon)` font-size: ${SUBTEXT_SIZE}; @@ -49,12 +48,8 @@ export default authorizedRoute('Settings', function Settings() { try { await dispatch(fetchUserInfo(username, password)); await scheduleNotifications(); - notify('Success', 'Your information has been refreshed.'); + notify(SUCCESS, REFRESHED_MSG); } catch (error) { - if (error instanceof LoginError) { - return notify('Error', LOGIN_CREDENTIALS_CHANGED_MSG); - } - notify('Error', error); reportError(error); } setRefreshing(false); diff --git a/packages/app/src/utils/process-info.ts b/packages/app/src/utils/process-info.ts index f820ef2..1a96bba 100755 --- a/packages/app/src/utils/process-info.ts +++ b/packages/app/src/utils/process-info.ts @@ -1,12 +1,13 @@ -import fetch from 'react-native-fetch-polyfill'; import { load } from 'react-native-cheerio'; import { DateType, DateSchema } from '@whs/server'; +import { fetch } from './utils'; import { processSchedule } from './process-schedule'; import { HEADER_SELECTOR, STUDENT_OVERVIEW_SELECTOR, STUDENT_ID_SELECTOR, SCHOOL_PICTURE_SELECTOR, SCHOOL_PICTURE_REGEX, SCHOOL_PICTURE_BLANK_FLAG, SCHOOL_PICTURE_BLANK_SYMBOL, - SCHEDULE_SELECTOR, SCHEDULE_REGEX, LOGIN_URL, FETCH_TIMEOUT, LOGIN_ERROR_SELECTOR, DATES_URL, SEARCH_URL, TEACHER_URL, + SCHEDULE_SELECTOR, SCHEDULE_REGEX, LOGIN_URL, FETCH_TIMEOUT, LOGIN_ERROR_SELECTOR, DATES_URL, + SEARCH_URL, TEACHER_URL, TEACHER_FETCH_LIMIT, } from '../constants/fetch'; import { UserInfo, UserOverviewMap, UserOverviewKeys } from '../types/store'; import { Schedule, TeacherSchedule, RawSchedule } from '../types/schedule'; @@ -23,7 +24,7 @@ export async function parseHTMLFromURL(url: string, options?: RequestInit) { ...options, }); if (!response.ok) { - throw new NetworkError('Fetch from URL was not successful!'); + throw new NetworkError(); } const html = await response.text(); return load(html); @@ -112,6 +113,15 @@ export function getLoginError($: CheerioSelector) { return $(LOGIN_ERROR_SELECTOR).text().trim(); } +export async function fetchTeachersFromQuery(query: string, username: string, password: string, signal: AbortSignal) { + const response = await fetch(getTeacherSearchURL(query, TEACHER_FETCH_LIMIT, username, password), { + method: 'POST', + timeout: FETCH_TIMEOUT, + signal, + }); + return response.json(); +} + /** * Refreshes a given collection of teacher schedules * @param teacherSchedules old teacher schedules to refresh @@ -138,7 +148,7 @@ export async function getDates(type: DateType, year: number): Promise(arr: T[]) { return arr.slice(-1)[0]; } +/** + * Same signature as WHATWG fetch but rethrows TypeError as NetworkError for catching + */ +export async function fetch(input?: string | Request, init?: RequestInit & Timeout): Promise { + try { + const response = await fetchPolyfill(input, init); + return response; + } catch (error) { + if (error instanceof TypeError) { + throw new NetworkError(); + } + throw error; + } +} + export function notify(title: string, body: string) { - // ensure a string is passed; patch to fix UnexpectedNativeTypeException - Alert.alert(title, body || UNKNOWN_ERROR_MSG, [{ text: 'OK' }]); + Alert.alert(title, body, [{ text: 'OK' }]); } -export function reportError(error: Error) { - const didRequestFail = error.message === NETWORK_REQUEST_FAILED; - notify('Error', didRequestFail ? NETWORK_REQUEST_FAILED_MSG : error.message); - if (!didRequestFail) { - client.notify(error); +export function reportError(error: Error, customMessage?: string) { + if (error instanceof LoginError) { + return notify(ERROR, LOGIN_CREDENTIALS_CHANGED_MSG); + } else if (error instanceof NetworkError) { + return notify(ERROR, NETWORK_REQUEST_FAILED_MSG); } + notify(ERROR, customMessage || UNKNOWN_ERROR_MSG); + client.notify(error); } export function reportScheduleCaution(semesterOneStart: Date) { @@ -105,16 +126,12 @@ export function reportScheduleCaution(semesterOneStart: Date) { const firstDate = format(semesterOneStart, 'MMMM do'); notify( - 'Caution', + CAUTION, `Many schedules are changing and will not be considered final until ${schedulesFinalDate}. ` + `Please be sure to refresh your schedule so that you attend the correct classes starting ${firstDate}.`, ); } export function reportNotEnoughSpace() { - notify( - 'Error', - 'There is not enough space on your phone to save your login, schedule, and other critical information.' - + 'Please clear up some space and retry logging in.', - ); + notify(ERROR, NO_SPACE_MSG); } From a011da65009e690cdd1e61ddb773c1fce6b1387d Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Sat, 26 Oct 2019 00:43:39 -0500 Subject: [PATCH 2/4] Add tests for error reporting --- packages/app/__tests__/info/process-test.ts | 16 +++++++++--- packages/app/__tests__/misc/utils-test.ts | 28 ++++++++++++++++++--- packages/app/src/screens/AddSchedule.tsx | 5 ++-- packages/app/src/utils/process-info.ts | 2 +- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/app/__tests__/info/process-test.ts b/packages/app/__tests__/info/process-test.ts index 812cd15..6d3afed 100644 --- a/packages/app/__tests__/info/process-test.ts +++ b/packages/app/__tests__/info/process-test.ts @@ -1,10 +1,11 @@ -import fetch from 'react-native-fetch-polyfill'; import { getLoginURL, getLoginError, getSchoolPictureFromHTML, processName, - getUserScheduleFromHTML, getUserInfoFromHTML, getTeacherSchedules, getTeacherSearchURL, getTeacherURL, + getUserScheduleFromHTML, getUserInfoFromHTML, + getTeacherSchedules, getTeacherSearchURL, getTeacherURL, fetchTeachersFromQuery, } from '../../src/utils/process-info'; -import { LOGIN_URL, SCHOOL_PICTURE_BLANK_SYMBOL } from '../../src/constants/fetch'; +import { fetch } from '../../src/utils/utils'; +import { LOGIN_URL, SCHOOL_PICTURE_BLANK_SYMBOL, TEACHER_FETCH_LIMIT } from '../../src/constants/fetch'; import { processSchedule } from '../../src/utils/process-schedule'; import { getStudent$, getError$, getNew$, getTeacher$, fetchMock, open, TEST_HTML_DIR } from '../test-utils/fetch'; import { TeacherSchedule } from '../../src/types/schedule'; @@ -12,7 +13,8 @@ import { TeacherSchedule } from '../../src/types/schedule'; fetchMock.config.fetch = fetch; fetchMock .post(getTeacherURL(1, 'John', '12345'), open(`${TEST_HTML_DIR}/teacher.html`)) - .post(getTeacherURL(2, 'John', '12345'), open(`${TEST_HTML_DIR}/teacher.html`)); + .post(getTeacherURL(2, 'John', '12345'), open(`${TEST_HTML_DIR}/teacher.html`)) + .post(getTeacherSearchURL('test', TEACHER_FETCH_LIMIT, 'John', '12345'), []); describe('processing user info', () => { describe('getLoginURL', () => { @@ -129,6 +131,12 @@ describe('processing user info', () => { }); }); + describe('fetchTeachersFromQuery', () => { + it('should launch request to fetch teachers list', async () => { + expect(await fetchTeachersFromQuery('test', 'John', '12345')).toEqual([]); + }); + }); + describe('getTeacherSchedules', () => { it('should get specified teacher schedules', async () => { const teacherOneURL = getTeacherURL(1, 'John', '12345'); diff --git a/packages/app/__tests__/misc/utils-test.ts b/packages/app/__tests__/misc/utils-test.ts index 54c1870..e43a49d 100644 --- a/packages/app/__tests__/misc/utils-test.ts +++ b/packages/app/__tests__/misc/utils-test.ts @@ -1,5 +1,11 @@ -import { insert, sortByProps, getWithFallback, splice, sum, last, reportError } from '../../src/utils/utils'; +import { fetch, insert, sortByProps, getWithFallback, splice, sum, last, reportError } from '../../src/utils/utils'; import client from '../../src/utils/bugsnag'; +import { LoginError, NetworkError } from '../../src/utils/error'; +import { fetchMock } from '../test-utils/fetch'; + +fetchMock.config.fetch = fetch; +fetchMock + .get('/', new Promise((res) => setTimeout(res, 1000))); describe('array utils', () => { describe('splice', () => { @@ -148,11 +154,27 @@ describe('array utils', () => { }); }); + describe('custom fetch', () => { + it('should throw NetworkError instead of TypeError', async () => { + await expect(fetch('/', { timeout: 500 })).rejects.toThrowError(NetworkError); + }); + }); + describe('reportError', () => { - it('should alert and notify bugsnag', () => { + const bugsnagNotify = client.notify as jest.Mock; + + it('should not report login and network errors to bugsnag', () => { + reportError(new LoginError()); + expect(bugsnagNotify).not.toBeCalled(); + + reportError(new NetworkError()); + expect(bugsnagNotify).not.toBeCalled(); + }); + + it('should notify bugsnag otherwise', () => { const error = new Error('Test Error'); reportError(error); - expect((client.notify as jest.Mock).mock.calls[0]).toEqual([error]); + expect(bugsnagNotify.mock.calls[0]).toEqual([error]); }); }); }); diff --git a/packages/app/src/screens/AddSchedule.tsx b/packages/app/src/screens/AddSchedule.tsx index b9cdc64..db02d8f 100644 --- a/packages/app/src/screens/AddSchedule.tsx +++ b/packages/app/src/screens/AddSchedule.tsx @@ -21,6 +21,7 @@ import { getUserScheduleFromHTML, parseHTMLFromURL, getTeacherURL, fetchTeachersFromQuery, } from '../utils/process-info'; import client from '../utils/bugsnag'; +import { SUCCESS } from '../constants/fetch'; const ListContainer = styled.View` border-radius: ${FORM_BORDER_RADIUS}; @@ -82,7 +83,7 @@ export default authorizedRoute('Add Schedule', function AddSchedule() { const $ = await parseHTMLFromURL(url, { signal, method: 'POST' }); const schedule = await getUserScheduleFromHTML($); dispatch(addTeacherSchedule({ name, url, schedule })); - notify('Success', `${name}'s schedule added!`); + notify(SUCCESS, `${name}'s schedule added!`); setTeachers([]); setQuery(''); } catch (error) { @@ -97,7 +98,7 @@ export default authorizedRoute('Add Schedule', function AddSchedule() { const removed = teacherSchedules.filter((teacher) => teacher.name !== name); dispatch(setTeacherSchedules(removed)); - notify('Success', `${name}'s schedule removed.`); + notify(SUCCESS, `${name}'s schedule removed.`); setRemovingTeacher(false); }; diff --git a/packages/app/src/utils/process-info.ts b/packages/app/src/utils/process-info.ts index 1a96bba..13a9be0 100755 --- a/packages/app/src/utils/process-info.ts +++ b/packages/app/src/utils/process-info.ts @@ -113,7 +113,7 @@ export function getLoginError($: CheerioSelector) { return $(LOGIN_ERROR_SELECTOR).text().trim(); } -export async function fetchTeachersFromQuery(query: string, username: string, password: string, signal: AbortSignal) { +export async function fetchTeachersFromQuery(query: string, username: string, password: string, signal?: AbortSignal) { const response = await fetch(getTeacherSearchURL(query, TEACHER_FETCH_LIMIT, username, password), { method: 'POST', timeout: FETCH_TIMEOUT, From 21e98ae5d107824851a8cf2ae71a830903d4ff8c Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Sat, 26 Oct 2019 01:43:52 -0500 Subject: [PATCH 3/4] Fix logic errors, add debouncing and bump version --- .../xcshareddata/xcschemes/WHS.xcscheme | 2 +- packages/app/package.json | 2 ++ packages/app/src/screens/AddSchedule.tsx | 2 +- packages/app/src/screens/Login.tsx | 6 +++--- packages/app/src/utils/bugsnag.ts | 2 +- packages/app/src/utils/process-info.ts | 3 +++ packages/app/src/utils/utils.ts | 7 ++++--- packages/app/tslint.json | 2 +- yarn.lock | 17 +++++++++++++++++ 9 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme b/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme index 28b5cda..0223b76 100644 --- a/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme +++ b/packages/app/ios/WHS.xcodeproj/xcshareddata/xcschemes/WHS.xcscheme @@ -84,7 +84,7 @@ { controller.abort(); try { - const json = await fetchTeachersFromQuery(teacherQuery, username, password, signal); + const { teachers: json } = await fetchTeachersFromQuery(teacherQuery, username, password, signal); const teachersAvailable = json.filter(({ firstName, lastName, email }: RawTeacherData) => { const alreadyAdded = teacherSchedules.some((teacherObj) => teacherObj.name === `${firstName} ${lastName}`); return email !== null && !alreadyAdded; diff --git a/packages/app/src/screens/Login.tsx b/packages/app/src/screens/Login.tsx index b8bce59..ad4d74e 100755 --- a/packages/app/src/screens/Login.tsx +++ b/packages/app/src/screens/Login.tsx @@ -67,10 +67,10 @@ export default memo(function Login(props: NavigationScreenProps) { } catch (error) { if (error instanceof LoginError) { setError(true); - setLoading(false); - return; + } else { + reportError(error); } - reportError(error); + setLoading(false); } }; diff --git a/packages/app/src/utils/bugsnag.ts b/packages/app/src/utils/bugsnag.ts index 9fd5b77..1136d9f 100644 --- a/packages/app/src/utils/bugsnag.ts +++ b/packages/app/src/utils/bugsnag.ts @@ -29,7 +29,7 @@ function serializeState(state: AppState): IMetadata { } const config = new Configuration(); -config.codeBundleId = '3.0.1-b2'; +config.codeBundleId = '3.0.1-b3'; config.notifyReleaseStages = ['production']; config.registerBeforeSendCallback((report) => { const state = store.getState(); diff --git a/packages/app/src/utils/process-info.ts b/packages/app/src/utils/process-info.ts index 13a9be0..b02f456 100755 --- a/packages/app/src/utils/process-info.ts +++ b/packages/app/src/utils/process-info.ts @@ -119,6 +119,9 @@ export async function fetchTeachersFromQuery(query: string, username: string, pa timeout: FETCH_TIMEOUT, signal, }); + if (!response.ok) { + throw new NetworkError(); + } return response.json(); } diff --git a/packages/app/src/utils/utils.ts b/packages/app/src/utils/utils.ts index 6cfcac0..69dbb8b 100644 --- a/packages/app/src/utils/utils.ts +++ b/packages/app/src/utils/utils.ts @@ -1,6 +1,7 @@ import { Alert } from 'react-native'; import { subDays, format } from 'date-fns'; -import fetchPolyfill, { Timeout } from 'react-native-fetch-polyfill'; +import fetchPolyfill from 'react-native-fetch-polyfill'; +import debounce from 'lodash.debounce'; import client from './bugsnag'; import { NetworkError, LoginError } from './error'; @@ -107,7 +108,7 @@ export function notify(title: string, body: string) { Alert.alert(title, body, [{ text: 'OK' }]); } -export function reportError(error: Error, customMessage?: string) { +export const reportError = debounce((error: Error, customMessage?: string) => { if (error instanceof LoginError) { return notify(ERROR, LOGIN_CREDENTIALS_CHANGED_MSG); } else if (error instanceof NetworkError) { @@ -115,7 +116,7 @@ export function reportError(error: Error, customMessage?: string) { } notify(ERROR, customMessage || UNKNOWN_ERROR_MSG); client.notify(error); -} +}, 5000, { leading: true }); export function reportScheduleCaution(semesterOneStart: Date) { // On the server-side, semesterOneStart represents the day everyone goes to school diff --git a/packages/app/tslint.json b/packages/app/tslint.json index 8ab61fb..f94d287 100644 --- a/packages/app/tslint.json +++ b/packages/app/tslint.json @@ -12,7 +12,7 @@ "groups": [ { "name": "packages", - "match": "^[^\\.]+$", + "match": "^(?:(?!\\.\\/).)+$", "order": 10 }, { diff --git a/yarn.lock b/yarn.lock index d5a402f..ab51eab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2237,6 +2237,18 @@ dependencies: "@types/node" "*" +"@types/lodash.debounce@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60" + integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.144" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.144.tgz#12e57fc99064bce45e5ab3c8bc4783feb75eab8e" + integrity sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg== + "@types/mime@*": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -9494,6 +9506,11 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.defaultsdeep@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" From 6b5ff3fcf4707537d3fa0754e9631f5e71fdf5c3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Sat, 26 Oct 2019 01:46:43 -0500 Subject: [PATCH 4/4] Debounce notify, not reportError --- packages/app/src/utils/utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/utils/utils.ts b/packages/app/src/utils/utils.ts index 69dbb8b..739dcb0 100644 --- a/packages/app/src/utils/utils.ts +++ b/packages/app/src/utils/utils.ts @@ -104,11 +104,11 @@ export async function fetch(input?: string | Request, init?: RequestInit & Timeo } } -export function notify(title: string, body: string) { +export const notify = debounce((title: string, body: string) => { Alert.alert(title, body, [{ text: 'OK' }]); -} +}, 5000, { leading: true }); -export const reportError = debounce((error: Error, customMessage?: string) => { +export function reportError(error: Error, customMessage?: string) { if (error instanceof LoginError) { return notify(ERROR, LOGIN_CREDENTIALS_CHANGED_MSG); } else if (error instanceof NetworkError) { @@ -116,7 +116,7 @@ export const reportError = debounce((error: Error, customMessage?: string) => { } notify(ERROR, customMessage || UNKNOWN_ERROR_MSG); client.notify(error); -}, 5000, { leading: true }); +} export function reportScheduleCaution(semesterOneStart: Date) { // On the server-side, semesterOneStart represents the day everyone goes to school