Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

Commit 5edc8cc

Browse files
authored
Merge pull request #3683 from mrfelton/perf/activity-loading
Improvements for activity list
2 parents 56cf680 + cb528db commit 5edc8cc

File tree

12 files changed

+107
-76
lines changed

12 files changed

+107
-76
lines changed

renderer/components/Activity/ActivityActions/ActivityActions.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const ActivityActions = ({
1616
changeFilter,
1717
reloadActivityHistory,
1818
updateSearchText,
19+
isPageLoading,
1920
isCustomFilter,
2021
intl,
2122
...rest
@@ -39,7 +40,7 @@ const ActivityActions = ({
3940
mx={3}
4041
/>
4142

42-
<ActivityRefresh mx={3} onClick={reloadActivityHistory} />
43+
<ActivityRefresh isPageLoading={isPageLoading} mx={3} onClick={reloadActivityHistory} />
4344
</Flex>
4445
</Flex>
4546
</Card>
@@ -51,6 +52,7 @@ ActivityActions.propTypes = {
5152
filters: PropTypes.array.isRequired,
5253
intl: intlShape.isRequired,
5354
isCustomFilter: PropTypes.bool,
55+
isPageLoading: PropTypes.bool,
5456
reloadActivityHistory: PropTypes.func.isRequired,
5557
searchText: PropTypes.string,
5658
updateSearchText: PropTypes.func.isRequired,

renderer/components/Activity/ActivityActions/ActivityRefresh.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
3-
import { injectIntl } from 'react-intl'
3+
import { useIntl } from 'react-intl'
44
import Refresh from 'components/Icon/Refresh'
55
import { ActionButton } from 'components/UI'
66
import messages from './messages'
77

8-
const ActivityRefresh = injectIntl(({ intl, onClick, ...rest }) => (
9-
<ActionButton
10-
hint={intl.formatMessage({ ...messages.refresh_button_hint })}
11-
onClick={onClick}
12-
{...rest}
13-
>
14-
<Refresh height="16px" width="16px" />
15-
</ActionButton>
16-
))
8+
const ActivityRefresh = ({ isPageLoading, onClick, ...rest }) => {
9+
const { formatMessage } = useIntl()
10+
return (
11+
<ActionButton
12+
hint={formatMessage({ ...messages.refresh_button_hint })}
13+
isLoading={isPageLoading}
14+
onClick={onClick}
15+
{...rest}
16+
>
17+
<Refresh height="16px" width="16px" />
18+
</ActionButton>
19+
)
20+
}
1721

1822
ActivityRefresh.propTypes = {
23+
isPageLoading: PropTypes.bool,
1924
onClick: PropTypes.func.isRequired,
2025
}
2126

renderer/components/App/App.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,15 @@ const App = ({
5454
willShowLnurlWithdrawPrompt,
5555
}) => {
5656
/**
57-
* App scheduler / polling service setup. Add new app-wide polls here
57+
* General app initialization.
5858
*/
5959
useEffect(() => {
60-
/**
61-
* Fetch node data on an exponentially incrementing backoff schedule so that when the app is first mounted, we fetch
62-
* node data quite frequently but as time goes on the frequency is reduced to a max of PEERS_MAX_REFETCH_INTERVAL
63-
*/
64-
appScheduler.addTask({
65-
task: () => !isSyncedToGraph && fetchDescribeNetwork(),
66-
taskId: 'fetchNetworkData',
67-
baseDelay: PEERS_INITIAL_REFETCH_INTERVAL,
68-
maxDelay: PEERS_MAX_REFETCH_INTERVAL,
69-
backoff: PEERS_REFETCH_BACKOFF_SCHEDULE,
70-
})
71-
appScheduler.addTask({
72-
task: updateAutopilotNodeScores,
73-
taskId: 'updateAutopilotNodeScores',
74-
baseDelay: AUTOPILOT_SCORES_REFRESH_INTERVAL,
75-
})
7660
if (activeWalletSettings.type === 'local') {
61+
appScheduler.addTask({
62+
task: updateAutopilotNodeScores,
63+
taskId: 'updateAutopilotNodeScores',
64+
baseDelay: AUTOPILOT_SCORES_REFRESH_INTERVAL,
65+
})
7766
appScheduler.addTask({
7867
task: () => fetchTransactions(true),
7968
taskId: 'fetchTransactions',
@@ -101,7 +90,6 @@ const App = ({
10190
}, [
10291
activeWalletSettings,
10392
initActivityHistory,
104-
isSyncedToGraph,
10593
fetchDescribeNetwork,
10694
fetchPeers,
10795
fetchSuggestedNodes,
@@ -112,6 +100,27 @@ const App = ({
112100
updateAutopilotNodeScores,
113101
])
114102

103+
/**
104+
* Fetch node data on an exponentially incrementing backoff schedule so that when the app is first mounted, we fetch
105+
* node data quite frequently but as time goes on the frequency is reduced to a max of PEERS_MAX_REFETCH_INTERVAL
106+
*/
107+
useEffect(() => {
108+
if (isSyncedToGraph) {
109+
appScheduler.removeTask('fetchNetworkData')
110+
} else {
111+
appScheduler.addTask({
112+
task: () => fetchDescribeNetwork(),
113+
taskId: 'fetchNetworkData',
114+
baseDelay: PEERS_INITIAL_REFETCH_INTERVAL,
115+
maxDelay: PEERS_MAX_REFETCH_INTERVAL,
116+
backoff: PEERS_REFETCH_BACKOFF_SCHEDULE,
117+
})
118+
}
119+
}, [isSyncedToGraph, fetchDescribeNetwork])
120+
121+
/**
122+
* Lnurl handlers.
123+
*/
115124
useEffect(() => {
116125
if (lnurlAuthParams && !willShowLnurlAuthPrompt) {
117126
finishLnurlAuth()

renderer/components/Home/Home.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Home extends React.Component {
3535
isWalletUnlockerGrpcActive: PropTypes.bool.isRequired,
3636
lndConnect: PropTypes.string,
3737
putWallet: PropTypes.func.isRequired,
38+
resetApp: PropTypes.func.isRequired,
3839
setActiveWallet: PropTypes.func.isRequired,
3940
setIsWalletOpen: PropTypes.func.isRequired,
4041
setUnlockWalletError: PropTypes.func.isRequired,
@@ -77,6 +78,7 @@ class Home extends React.Component {
7778
wallets,
7879
setActiveWallet,
7980
clearStartLndError,
81+
resetApp,
8082
showError,
8183
stopLnd,
8284
isNeutrinoRunning,
@@ -135,6 +137,7 @@ class Home extends React.Component {
135137
isStartingLnd={isStartingLnd}
136138
isWalletUnlockerGrpcActive={isWalletUnlockerGrpcActive}
137139
putWallet={putWallet}
140+
resetApp={resetApp}
138141
showError={showError}
139142
showNotification={showNotification}
140143
startLnd={startLnd}

renderer/components/Home/WalletLauncher.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class WalletLauncher extends React.Component {
201201
isStartingLnd: PropTypes.bool.isRequired,
202202
isWalletUnlockerGrpcActive: PropTypes.bool.isRequired,
203203
putWallet: PropTypes.func.isRequired,
204+
resetApp: PropTypes.func.isRequired,
204205
showError: PropTypes.func.isRequired,
205206
showNotification: PropTypes.func.isRequired,
206207
startLnd: PropTypes.func.isRequired,
@@ -259,8 +260,9 @@ class WalletLauncher extends React.Component {
259260

260261
launchWallet = async () => {
261262
try {
262-
const { startLnd, wallet } = this.props
263+
const { resetApp, startLnd, wallet } = this.props
263264
// discard settings edits and just launch current saved config
265+
resetApp()
264266
return await startLnd(wallet)
265267
} catch (e) {
266268
// this error is handled via startLndErrors mechanism

renderer/components/UI/ActionButton.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ StyledButton.propTypes = {
1616
active: PropTypes.bool,
1717
}
1818

19-
const ActionButton = ({ children, hint, onClick, timeout, ...rest }) => {
19+
const ActionButton = ({ children, hint, isLoading, onClick, timeout = 1000, ...rest }) => {
2020
const [status, setStatus] = useState(null)
2121
const buttonRef = useRef()
2222

@@ -49,20 +49,17 @@ const ActionButton = ({ children, hint, onClick, timeout, ...rest }) => {
4949
variant="secondary"
5050
{...rest}
5151
>
52-
{status === 'fetching' ? <Spinner height="16px" width="16px" /> : children}
52+
{status === 'fetching' || isLoading ? <Spinner height="16px" width="16px" /> : children}
5353
</StyledButton>
5454
)
5555
}
5656

5757
ActionButton.propTypes = {
5858
children: PropTypes.node,
5959
hint: PropTypes.node,
60+
isLoading: PropTypes.bool,
6061
onClick: PropTypes.func.isRequired,
6162
timeout: PropTypes.number,
6263
}
6364

64-
ActionButton.defaultProps = {
65-
timeout: 1000,
66-
}
67-
6865
export default ActionButton

renderer/containers/Activity/ActivityActions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const mapStateToProps = state => ({
1818
filters: activitySelectors.filters(state),
1919
searchText: activitySelectors.searchText(state),
2020
isCustomFilter: activitySelectors.isCustomFilter(state),
21+
isPageLoading: activitySelectors.isPageLoading(state),
2122
})
2223

2324
export default connect(mapStateToProps, mapDispatchToProps)(ActivityActions)

renderer/containers/Home/Home.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import { connect } from 'react-redux'
3+
import { resetApp } from 'reducers/app'
34
import { neutrinoSelectors } from 'reducers/neutrino'
45
import {
56
setActiveWallet,
@@ -51,6 +52,7 @@ const mapDispatchToProps = {
5152
clearStartLndError,
5253
stopLnd,
5354
putWallet,
55+
resetApp,
5456
showNotification,
5557
startLnd,
5658
unlockWallet,

renderer/reducers/activity/reducer.js

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { send } from 'redux-electron-ipc'
2-
import groupBy from 'lodash/groupBy'
32
import createReducer from '@zap/utils/createReducer'
43
import { getIntl } from '@zap/i18n'
54
import { mainLog } from '@zap/utils/log'
@@ -11,8 +10,9 @@ import { settingsSelectors } from 'reducers/settings'
1110
import { fetchBalance } from 'reducers/balance'
1211
import { fetchChannels } from 'reducers/channels'
1312
import { fetchInfo, infoSelectors } from 'reducers/info'
13+
import { walletSelectors } from 'reducers/wallet'
1414
import { showError, showNotification } from 'reducers/notification'
15-
import { createActivityPaginator, getItemType } from './utils'
15+
import { createActivityPaginator } from './utils'
1616
import { hasNextPage, isPageLoading } from './selectors'
1717
import messages from './messages'
1818
import * as constants from './constants'
@@ -77,17 +77,21 @@ const initialState = {
7777
// activity paginator object. must be reset for each wallet login
7878
/** @type {Function | null} */
7979
let paginator = null
80-
let loadedPages = 0
8180

8281
/**
8382
* getPaginator - Returns current activity paginator object. This acts as a singleton
8483
* and creates paginator if it's not initialized.
8584
*
85+
* @param {{
86+
* invoiceHandler: function,
87+
* paymentHandler: function,
88+
* transactionHandler: function
89+
* }} handlers Pagination handlers
8690
* @returns {Function} Paginator
8791
*/
88-
export const getPaginator = () => {
92+
export const getPaginator = handlers => {
8993
if (!paginator) {
90-
paginator = createActivityPaginator()
94+
paginator = createActivityPaginator(handlers)
9195
}
9296
return paginator
9397
}
@@ -237,22 +241,12 @@ export const hideActivityModal = () => dispatch => {
237241
*/
238242
export const resetActivity = () => () => {
239243
paginator = null
240-
loadedPages = 0
241-
}
242-
243-
/**
244-
* resetPaginator - Reset activity paginator.
245-
*
246-
* @returns {() => void} Thunk
247-
*/
248-
export const resetPaginator = () => () => {
249-
paginator = null
250244
}
251245

252246
/**
253247
* loadPage - Loads next activity page if it's available.
254248
*
255-
* @param {boolean} reload Boolean indicating if this page load is part of a reload.
249+
* @param {boolean} reload Boolean indicating wether to load first page.
256250
* @returns {(dispatch:Function, getState:Function) => Promise<void>} Thunk
257251
*/
258252
export const loadPage = (reload = false) => async (dispatch, getState) => {
@@ -262,38 +256,45 @@ export const loadPage = (reload = false) => async (dispatch, getState) => {
262256
dispatch(setPageLoading(true))
263257

264258
await dispatch(fetchInfo())
265-
const config = settingsSelectors.currentConfig(getState())
266-
const blockHeight = infoSelectors.blockHeight(getState())
267-
const thisPaginator = getPaginator()
259+
const state = getState()
260+
const config = settingsSelectors.currentConfig(state)
261+
const blockHeight = infoSelectors.blockHeight(state)
262+
const activeWallet = walletSelectors.activeWallet(state)
263+
264+
// Checks to see if the currently active wallet is the same one as when the page load started.
265+
const isStllActiveWallet = () => walletSelectors.activeWallet(getState()) === activeWallet
266+
267+
// It's possible that the grpc response is received after the wallet has been disconnected.
268+
// See https://github.com/grpc/grpc-node/issues/1603
269+
// Ensure that the items received belong to the currently active wallet.
270+
const shouldUpdate = items => items && isStllActiveWallet()
271+
272+
const handlers = {
273+
invoiceHandler: items => shouldUpdate(items) && dispatch(receiveInvoices(items)),
274+
paymentHandler: items => shouldUpdate(items) && dispatch(receivePayments(items)),
275+
transactionHandler: items => shouldUpdate(items) && dispatch(receiveTransactions(items)),
276+
}
277+
const thisPaginator = reload ? createActivityPaginator(handlers) : getPaginator(handlers)
268278

269-
if (reload || hasNextPage(getState())) {
279+
if (hasNextPage(getState())) {
270280
const { pageSize } = config.activity
271-
const { items, hasNextPage: paginatorHasNextPage } = await thisPaginator(pageSize, blockHeight)
272-
273-
if (!reload) {
274-
loadedPages += 1
275-
dispatch({ type: SET_HAS_NEXT_PAGE, value: paginatorHasNextPage })
281+
const { hasNextPage } = await thisPaginator(pageSize, blockHeight)
282+
if (!isStllActiveWallet()) {
283+
return
276284
}
277-
278-
const { invoices, payments, transactions } = groupBy(items, getItemType)
279-
280-
invoices && dispatch(receiveInvoices(invoices))
281-
payments && dispatch(receivePayments(payments))
282-
transactions && dispatch(receiveTransactions(transactions))
285+
dispatch({ type: SET_HAS_NEXT_PAGE, value: hasNextPage })
283286
}
284287

285288
dispatch(setPageLoading(false))
286289
}
287290

288291
/**
289-
* reloadPages - Reloads all already loaded activity pages.
292+
* reloadActivityHead - Reloads activity head.
290293
*
291294
* @returns {(dispatch:Function) => Promise<void>} Thunk
292295
*/
293-
export const reloadPages = () => async dispatch => {
294-
const pageCount = loadedPages
295-
mainLog.debug(`reloading ${pageCount} activity pages`)
296-
dispatch(resetPaginator())
296+
export const reloadActivityHead = () => async dispatch => {
297+
mainLog.debug(`reloading activity pages`)
297298
await dispatch(loadPage(true))
298299
}
299300

@@ -321,7 +322,7 @@ export const reloadActivityHistory = () => async dispatch => {
321322
dispatch({ type: FETCH_ACTIVITY_HISTORY })
322323
try {
323324
await Promise.all([
324-
dispatch(reloadPages()),
325+
dispatch(reloadActivityHead()),
325326
dispatch(fetchChannels()),
326327
dispatch(fetchBalance()),
327328
])

renderer/reducers/activity/selectors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,12 @@ export const isCustomFilter = createSelector(filter, currentFilter => {
278278
* All selectors to export.
279279
*/
280280
export default {
281+
errorDialogDetails,
281282
filter,
282283
filters,
283284
searchText,
284285
hasNextPage,
285-
errorDialogDetails,
286+
isPageLoading,
286287
isErrorDialogOpen,
287288
currentActivity,
288289
activityModalItem,

0 commit comments

Comments
 (0)