Skip to content

Commit

Permalink
Revert "fix: handling of token page throttling (ripple#1100)"
Browse files Browse the repository at this point in the history
This reverts commit 8d503da.
  • Loading branch information
endz80513 authored Dec 21, 2024
1 parent a123225 commit 5fd7a59
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 86 deletions.
2 changes: 1 addition & 1 deletion src/containers/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { Transaction } from '../Transactions'
import { Network } from '../Network'
import { Validator } from '../Validators'
import { PayString } from '../PayStrings'
import { Token } from '../Token'
import Token from '../Token'
import { NFT } from '../NFT/NFT'
import { legacyRedirect } from './legacyRedirects'
import { useCustomNetworks } from '../shared/hooks'
Expand Down
4 changes: 4 additions & 0 deletions src/containers/Token/TokenHeader/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const START_LOADING_ACCOUNT_STATE = 'START_LOADING_ACCOUNT_STATE'
export const FINISHED_LOADING_ACCOUNT_STATE = 'FINISHED_LOADING_ACCOUNT_STATE'
export const ACCOUNT_STATE_LOAD_SUCCESS = 'ACCOUNT_STATE_LOAD_SUCCESS'
export const ACCOUNT_STATE_LOAD_FAIL = 'ACCOUNT_STATE_LOAD_FAIL'
43 changes: 43 additions & 0 deletions src/containers/Token/TokenHeader/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { isValidClassicAddress, isValidXAddress } from 'ripple-address-codec'
import { getToken } from '../../../rippled'
import { analytics } from '../../shared/analytics'
import { BAD_REQUEST } from '../../shared/utils'
import * as actionTypes from './actionTypes'

export const loadTokenState =
(currency, accountId, rippledSocket) => (dispatch) => {
if (!isValidClassicAddress(accountId) && !isValidXAddress(accountId)) {
dispatch({
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
status: BAD_REQUEST,
error: '',
})
return Promise.resolve()
}

dispatch({
type: actionTypes.START_LOADING_ACCOUNT_STATE,
})
return getToken(currency, accountId, rippledSocket)
.then((data) => {
dispatch({ type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE })
dispatch({
type: actionTypes.ACCOUNT_STATE_LOAD_SUCCESS,
data,
})
})
.catch((error) => {
const status = error.code
analytics.trackException(
`token ${currency}.${accountId} --- ${JSON.stringify(error)}`,
)
dispatch({ type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE })
dispatch({
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
error: status === 500 ? 'get_account_state_failed' : '',
status,
})
})
}

export default loadTokenState
60 changes: 52 additions & 8 deletions src/containers/Token/TokenHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { useContext, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { loadTokenState } from './actions'
import { Loader } from '../../shared/components/Loader'
import './styles.scss'
import { localizeNumber, formatLargeNumber } from '../../shared/utils'
import SocketContext from '../../shared/SocketContext'
import Currency from '../../shared/components/Currency'
import { Account } from '../../shared/components/Account'
import DomainLink from '../../shared/components/DomainLink'
import { TokenTableRow } from '../../shared/components/TokenTableRow'
import { useLanguage } from '../../shared/hooks'
import { LEDGER_ROUTE, TRANSACTION_ROUTE } from '../../App/routes'
import { RouteLink } from '../../shared/routing'
import { TokenData } from '../../../rippled/token'

const CURRENCY_OPTIONS = {
style: 'currency',
Expand All @@ -18,21 +23,44 @@ const CURRENCY_OPTIONS = {
}

interface TokenHeaderProps {
loading: boolean
accountId: string
currency: string
data: TokenData
data: {
balance: string
reserve: number
sequence: number
rate: number
obligations: string
domain: string
emailHash: string
previousLedger: number
previousTxn: string
flags: string[]
}
actions: {
loadTokenState: typeof loadTokenState
}
}

export const TokenHeader = ({
const TokenHeader = ({
actions,
accountId,
currency,
data,
loading,
}: TokenHeaderProps) => {
const language = useLanguage()
const { t } = useTranslation()
const { domain, rate, emailHash, previousLedger, previousTxn } = data
const rippledSocket = useContext(SocketContext)

useEffect(() => {
actions.loadTokenState(currency, accountId, rippledSocket)
}, [accountId, actions, currency, rippledSocket])

const renderDetails = () => {
const { domain, rate, emailHash, previousLedger, previousTxn } = data

const prevTxn = previousTxn && previousTxn.replace(/(.{20})..+/, '$1...')
const abbrvEmail = emailHash && emailHash.replace(/(.{20})..+/, '$1...')
return (
Expand Down Expand Up @@ -128,9 +156,7 @@ export const TokenHeader = ({
language,
CURRENCY_OPTIONS,
)
const obligationsBalance = formatLargeNumber(
Number.parseFloat(obligations || '0'),
)
const obligationsBalance = formatLargeNumber(Number.parseFloat(obligations))

return (
<div className="section header-container">
Expand Down Expand Up @@ -175,6 +201,7 @@ export const TokenHeader = ({
)
}

const { emailHash } = data
return (
<div className="box token-header">
<div className="section box-header">
Expand All @@ -186,7 +213,24 @@ export const TokenHeader = ({
/>
)}
</div>
<div className="box-content">{renderHeaderContent()}</div>
<div className="box-content">
{loading ? <Loader /> : renderHeaderContent()}
</div>
</div>
)
}

export default connect(
(state: any) => ({
loading: state.tokenHeader.loading,
data: state.tokenHeader.data,
}),
(dispatch) => ({
actions: bindActionCreators(
{
loadTokenState,
},
dispatch,
),
}),
)(TokenHeader)
33 changes: 33 additions & 0 deletions src/containers/Token/TokenHeader/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as actionTypes from './actionTypes'

export const initialState = {
loading: false,
data: {},
error: '',
status: null,
}

// eslint-disable-next-line default-param-last
const tokenReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.START_LOADING_ACCOUNT_STATE:
return { ...state, loading: true }
case actionTypes.FINISHED_LOADING_ACCOUNT_STATE:
return { ...state, loading: false }
case actionTypes.ACCOUNT_STATE_LOAD_SUCCESS:
return { ...state, error: '', data: action.data }
case actionTypes.ACCOUNT_STATE_LOAD_FAIL:
return {
...state,
error: action.error,
status: action.status,
data: state.data.length ? state.data : {},
}
case 'persist/REHYDRATE':
return { ...initialState }
default:
return state
}
}

export default tokenReducer
108 changes: 108 additions & 0 deletions src/containers/Token/TokenHeader/test/actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../actions'
import * as actionTypes from '../actionTypes'
import { initialState } from '../reducer'
import { NOT_FOUND, BAD_REQUEST, SERVER_ERROR } from '../../../shared/utils'
import rippledResponses from './rippledResponses.json'
import actNotFound from './actNotFound.json'
import MockWsClient from '../../../test/mockWsClient'

const TEST_ADDRESS = 'rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv'
const TEST_CURRENCY = 'abc'

describe('TokenHeader Actions', () => {
jest.setTimeout(10000)

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
let client
beforeEach(() => {
client = new MockWsClient()
})

afterEach(() => {
client.close()
})

it('should dispatch correct actions on successful loadTokenState', () => {
client.addResponses(rippledResponses)
const expectedData = {
name: undefined,
obligations: '100',
sequence: 2148991,
reserve: 10,
rate: undefined,
domain: undefined,
emailHash: undefined,
flags: [],
balance: '123456000',
previousTxn:
'6B6F2CA1633A22247058E988372BA9EFFFC5BF10212230B67341CA32DC9D4A82',
previousLedger: 68990183,
}
const expectedActions = [
{ type: actionTypes.START_LOADING_ACCOUNT_STATE },
{ type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE },
{ type: actionTypes.ACCOUNT_STATE_LOAD_SUCCESS, data: expectedData },
]
const store = mockStore({ news: initialState })
return store
.dispatch(actions.loadTokenState(TEST_CURRENCY, TEST_ADDRESS, client))
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})

it('should dispatch correct actions on server error', () => {
client.setReturnError()
const expectedActions = [
{ type: actionTypes.START_LOADING_ACCOUNT_STATE },
{ type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE },
{
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
status: SERVER_ERROR,
error: 'get_account_state_failed',
},
]
const store = mockStore({ news: initialState })
return store
.dispatch(actions.loadTokenState(TEST_CURRENCY, TEST_ADDRESS, client))
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})

it('should dispatch correct actions on ripple address not found', () => {
client.addResponse('account_info', { result: actNotFound })
const expectedActions = [
{ type: actionTypes.START_LOADING_ACCOUNT_STATE },
{ type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE },
{
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
status: NOT_FOUND,
error: '',
},
]
const store = mockStore({ news: initialState })
return store
.dispatch(actions.loadTokenState(TEST_CURRENCY, TEST_ADDRESS, client))
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})

it('should dispatch correct actions on invalid ripple address', () => {
const expectedActions = [
{
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
status: BAD_REQUEST,
error: '',
},
]
const store = mockStore({ news: initialState })
store.dispatch(actions.loadTokenState('ZZZ', null, client)).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
79 changes: 79 additions & 0 deletions src/containers/Token/TokenHeader/test/reducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as actionTypes from '../actionTypes'
import reducer, { initialState } from '../reducer'

describe('AccountHeader reducers', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual(initialState)
})

it('should handle START_LOADING_ACCOUNT_STATE', () => {
const nextState = { ...initialState, loading: true }
expect(
reducer(initialState, { type: actionTypes.START_LOADING_ACCOUNT_STATE }),
).toEqual(nextState)
})

it('should handle FINISHED_LOADING_ACCOUNT_STATE', () => {
const nextState = { ...initialState, loading: false }
expect(
reducer(initialState, {
type: actionTypes.FINISHED_LOADING_ACCOUNT_STATE,
}),
).toEqual(nextState)
})

it('should handle ACCOUNT_STATE_LOAD_SUCCESS', () => {
const data = [['XRP', 123.456]]
const nextState = { ...initialState, data }
expect(
reducer(initialState, {
data,
type: actionTypes.ACCOUNT_STATE_LOAD_SUCCESS,
}),
).toEqual(nextState)
})

it('should handle ACCOUNT_STATE_LOAD_FAIL', () => {
const status = 500
const error = 'error'
const nextState = { ...initialState, status, error }
expect(
reducer(initialState, {
status,
error,
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
}),
).toEqual(nextState)
})

it('will not clear previous data on ACCOUNT_STATE_LOAD_FAIL', () => {
const data = [['XRP', 123.456]]
const error = 'error'
const status = 500
const stateWithData = { ...initialState, data }
const nextState = { ...stateWithData, error, status }
expect(
reducer(stateWithData, {
status,
error,
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
}),
).toEqual(nextState)
})

it('should clear data on rehydration', () => {
const error = 'error'
const status = 500
const nextState = { ...initialState, error, status }
expect(
reducer(initialState, {
type: actionTypes.ACCOUNT_STATE_LOAD_FAIL,
error,
status,
}),
).toEqual(nextState)
expect(reducer(nextState, { type: 'persist/REHYDRATE' })).toEqual(
initialState,
)
})
})
Loading

0 comments on commit 5fd7a59

Please sign in to comment.