Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finance: port agent filtering to finance #1102

Merged
merged 7 commits into from
Apr 9, 2020
2 changes: 1 addition & 1 deletion apps/agent/app/src/components/useFilteredTransactions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useCallback, useMemo } from 'react'
import { endOfDay, isAfter, isWithinInterval, startOfDay } from 'date-fns'
import { endOfDay, isWithinInterval, startOfDay } from 'date-fns'
import { TRANSACTION_TYPES } from '../transaction-types'
import { addressesEqual } from '../lib/web3-utils'

Expand Down
134 changes: 35 additions & 99 deletions apps/finance/app/src/components/Transfers.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React, { useMemo, useState, useCallback, useContext } from 'react'
import React, { useMemo, useCallback, useContext } from 'react'
import PropTypes from 'prop-types'
import {
compareDesc,
endOfDay,
format,
isWithinInterval,
startOfDay,
} from 'date-fns'
import { compareDesc, format } from 'date-fns'
import {
Button,
ContextMenu,
Expand All @@ -29,54 +23,16 @@ import {
useNetwork,
} from '@aragon/api-react'
import { saveAs } from 'file-saver'
import * as TransferTypes from '../transfer-types'
import { READABLE_TRANSFER_TYPES } from '../transfer-types'
import { addressesEqual, toChecksumAddress } from '../lib/web3-utils'
import { formatTokenAmount } from '../lib/utils'
import TransfersFilters from './TransfersFilters'
import { useIdentity, IdentityContext } from './IdentityManager/IdentityManager'
import LocalIdentityBadge from './LocalIdentityBadge/LocalIdentityBadge'
import useFilteredTransfers from './useFilteredTransfers'

const UNSELECTED_TOKEN_FILTER = -1
const UNSELECTED_TRANSFER_TYPE_FILTER = -1
const INITIAL_DATE_RANGE = { start: null, end: null }
const TRANSFER_TYPES = [
TransferTypes.All,
TransferTypes.Incoming,
TransferTypes.Outgoing,
]
const TRANSFER_TYPES_STRING = TRANSFER_TYPES.map(TransferTypes.convertToString)
const formatDate = date => format(date, 'dd/MM/yy')
const getTokenDetails = (details, { address, decimals, symbol }) => {
details[toChecksumAddress(address)] = {
decimals,
symbol,
}
return details
}
// Filter transfer based on the selected filters
const getFilteredTransfers = ({
transactions,
selectedToken,
selectedTransferType,
selectedDateRange,
}) => {
const transferType = TRANSFER_TYPES[selectedTransferType]
return transactions.filter(
({ token, isIncoming, date }) =>
(!selectedDateRange.start ||
!selectedDateRange.end ||
isWithinInterval(new Date(date), {
start: startOfDay(selectedDateRange.start),
end: endOfDay(selectedDateRange.end),
})) &&
(selectedToken === null ||
addressesEqual(token, selectedToken.address)) &&
(transferType === TransferTypes.All ||
selectedTransferType === UNSELECTED_TRANSFER_TYPE_FILTER ||
(transferType === TransferTypes.Incoming && isIncoming) ||
(transferType === TransferTypes.Outgoing && !isIncoming))
)
}

const getDownloadData = async (transfers, tokenDetails, resolveAddress) => {
const mappedData = await Promise.all(
transfers.map(
Expand Down Expand Up @@ -123,49 +79,36 @@ const Transfers = React.memo(({ tokens, transactions }) => {
const { appState } = useAragonApi()
const connectedAccount = useConnectedAccount()
const currentApp = useCurrentApp()
const toast = useToast()
const theme = useTheme()
const { layoutName } = useLayout()
const theme = useTheme()
const toast = useToast()

const [page, setPage] = useState(0)
const [selectedToken, setSelectedToken] = useState(UNSELECTED_TOKEN_FILTER)
const [selectedTransferType, setSelectedTransferType] = useState(
UNSELECTED_TRANSFER_TYPE_FILTER
)
const [selectedDateRange, setSelectedDateRange] = useState(INITIAL_DATE_RANGE)
const handleSelectedDateRangeChange = range => {
setPage(0)
setSelectedDateRange(range)
}
const handleTokenChange = useCallback(
index => {
setPage(0)
setSelectedToken(index || UNSELECTED_TOKEN_FILTER)
},
[setPage, setSelectedToken]
)
const handleTransferTypeChange = useCallback(
index => {
setPage(0)
setSelectedTransferType(index || UNSELECTED_TOKEN_FILTER)
},
[setPage, setSelectedTransferType]
)
const handleClearFilters = useCallback(() => {
setPage(0)
setSelectedTransferType(UNSELECTED_TRANSFER_TYPE_FILTER)
setSelectedToken(UNSELECTED_TOKEN_FILTER)
setSelectedDateRange(INITIAL_DATE_RANGE)
}, [setPage, setSelectedTransferType, setSelectedToken, setSelectedDateRange])
const filteredTransfers = getFilteredTransfers({
transactions,
selectedToken: selectedToken > 0 ? tokens[selectedToken - 1] : null,
selectedTransferType,
const {
emptyResultsViaFilters,
filteredTransfers,
handleClearFilters,
handleSelectedDateRangeChange,
handleTokenChange,
handleTransferTypeChange,
page,
setPage,
selectedDateRange,
})
selectedToken,
selectedTransferType,
symbols,
} = useFilteredTransfers({ transactions, tokens })

const { isSyncing } = appState
const symbols = tokens.map(({ symbol }) => symbol)
const tokenDetails = tokens.reduce(getTokenDetails, {})
const tokenDetails = tokens.reduce(
(details, { address, decimals, symbol }) => {
details[toChecksumAddress(address)] = {
decimals,
symbol,
}
return details
},
{}
)
sohkai marked this conversation as resolved.
Show resolved Hide resolved
const { resolve: resolveAddress } = useContext(IdentityContext)
const handleDownload = useCallback(async () => {
if (!currentApp || !currentApp.appAddress) {
Expand All @@ -184,12 +127,6 @@ const Transfers = React.memo(({ tokens, transactions }) => {
saveAs(new Blob([data], { type: 'text/csv;charset=utf-8' }), filename)
toast('Transfers data exported')
}, [currentApp, filteredTransfers, tokenDetails, resolveAddress])
const emptyResultsViaFilters =
!filteredTransfers.length &&
(selectedToken !== 0 ||
selectedTransferType !== 0 ||
selectedDateRange.start ||
selectedDateRange.end)

const compactMode = layoutName === 'small'

Expand Down Expand Up @@ -259,13 +196,12 @@ const Transfers = React.memo(({ tokens, transactions }) => {
<TransfersFilters
dateRangeFilter={selectedDateRange}
onDateRangeChange={handleSelectedDateRangeChange}
tokenFilter={selectedToken}
onTokenChange={handleTokenChange}
transferTypeFilter={selectedTransferType}
onTransferTypeChange={handleTransferTypeChange}
compactMode={compactMode}
symbols={['All tokens', ...symbols]}
transferTypes={TRANSFER_TYPES_STRING}
tokenFilter={selectedToken}
transferTypeFilter={selectedTransferType}
transferTypes={READABLE_TRANSFER_TYPES}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this value should be controlled by useFilteredTransfers(), to encapsulate all the filtering logic to be within it.

It would be bad if we re-ordered the list but forgot to update it here or in that hook, which suggests we should encapsulate the concepts :).

Copy link
Contributor Author

@Evalir Evalir Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this value should be controlled by useFilteredTransfers(), to encapsulate all the filtering logic to be within it.

Now that I think about it, it makes sense, as symbols is handled by useFilteredTransfers() itself. Now, would we just import the labels and export them back with another name, like this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's what I would've done!

symbols={symbols}
/>
)}
</React.Fragment>
Expand Down
2 changes: 0 additions & 2 deletions apps/finance/app/src/components/TransfersFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import React from 'react'
import { DropDown, GU, DateRangePicker } from '@aragon/ui'

const TransfersFilters = ({
compactMode,
opened,
dateRangeFilter,
onDateRangeChange,
onTokenChange,
Expand Down
106 changes: 106 additions & 0 deletions apps/finance/app/src/components/useFilteredTransfers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useState, useCallback, useMemo } from 'react'
import { endOfDay, isWithinInterval, startOfDay } from 'date-fns'
import { TRANSFER_TYPES, Incoming, Outgoing } from '../transfer-types'
import { addressesEqual } from '../lib/web3-utils'

const UNSELECTED_TOKEN_FILTER = -1
const UNSELECTED_TRANSFER_TYPE_FILTER = -1
const UNSELECTED_DATE_RANGE_FILTER = { start: null, end: null }

function useFilteredTransfers({ transactions, tokens }) {
const [page, setPage] = useState(0)
Evalir marked this conversation as resolved.
Show resolved Hide resolved
const [selectedDateRange, setSelectedDateRange] = useState(
UNSELECTED_DATE_RANGE_FILTER
)
const [selectedTransferType, setSelectedTransferType] = useState(
UNSELECTED_TRANSFER_TYPE_FILTER
)
const [selectedToken, setSelectedToken] = useState(UNSELECTED_TOKEN_FILTER)
Evalir marked this conversation as resolved.
Show resolved Hide resolved
const handleSelectedDateRangeChange = useCallback(range => {
setPage(0)
setSelectedDateRange(range)
}, [])
const handleTokenChange = useCallback(index => {
setPage(0)
setSelectedToken(index || UNSELECTED_TOKEN_FILTER)
}, [])
const handleTransferTypeChange = useCallback(index => {
setPage(0)
setSelectedTransferType(index || UNSELECTED_TRANSFER_TYPE_FILTER)
}, [])
const handleClearFilters = useCallback(() => {
setPage(0)
Evalir marked this conversation as resolved.
Show resolved Hide resolved
setSelectedTransferType(UNSELECTED_TRANSFER_TYPE_FILTER)
setSelectedToken(UNSELECTED_TOKEN_FILTER)
setSelectedDateRange(UNSELECTED_DATE_RANGE_FILTER)
}, [])

const tokensToFilter = useMemo(() => [{ symbol: 'All tokens' }, ...tokens], [
tokens,
])

const filteredTransfers = useMemo(
() =>
transactions.filter(({ date, isIncoming, token }) => {
const type = isIncoming ? Incoming : Outgoing
Evalir marked this conversation as resolved.
Show resolved Hide resolved
// Exclude by transaction type
if (
selectedTransferType !== -1 &&
TRANSFER_TYPES[selectedTransferType] !== type
Evalir marked this conversation as resolved.
Show resolved Hide resolved
) {
return false
}

// Exclude by date range
if (
// We're not checking for an end date because we will always
// have a start date for defining a range.
selectedDateRange.start &&
Evalir marked this conversation as resolved.
Show resolved Hide resolved
!isWithinInterval(new Date(date), {
start: startOfDay(selectedDateRange.start),
end: endOfDay(selectedDateRange.end),
})
) {
return false
}
// Exclude by token
if (
selectedToken > 0 &&
!addressesEqual(token, tokensToFilter[selectedToken].address)
) {
return false
}

// All good, we can include the transaction ✌️
return true
}),
[
selectedDateRange,
selectedTransferType,
selectedToken,
tokensToFilter,
transactions,
]
)
const symbols = tokensToFilter.map(({ symbol }) => symbol)
const emptyResultsViaFilters =
!filteredTransfers &&
(selectedToken > 0 || selectedTransferType > 0 || selectedDateRange.start)

return {
emptyResultsViaFilters,
filteredTransfers,
handleClearFilters,
handleSelectedDateRangeChange,
handleTokenChange,
handleTransferTypeChange,
page,
setPage,
selectedDateRange,
selectedToken,
selectedTransferType,
symbols,
}
}

export default useFilteredTransfers
21 changes: 6 additions & 15 deletions apps/finance/app/src/transfer-types.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
export const All = Symbol('All')
export const Incoming = Symbol('Incoming')
export const Outgoing = Symbol('Outgoing')
export const All = 'ALL_TRANSFER'
Evalir marked this conversation as resolved.
Show resolved Hide resolved
export const Incoming = 'INCOMING_TRANSFER'
export const Outgoing = 'OUTGOING_TRANSFER'

const symbolMapping = {
All,
Incoming,
Outgoing,
}
const stringMapping = {
export const TRANSFER_TYPES_LABELS = {
[All]: 'All',
[Incoming]: 'Incoming',
[Outgoing]: 'Outgoing',
}

export function convertFromString(str) {
return symbolMapping[str]
}
export function convertToString(symbol) {
return stringMapping[symbol]
}
export const TRANSFER_TYPES = Object.keys(TRANSFER_TYPES_LABELS)
export const READABLE_TRANSFER_TYPES = Object.values(TRANSFER_TYPES_LABELS)
Evalir marked this conversation as resolved.
Show resolved Hide resolved