Skip to content

Commit

Permalink
Merge pull request #648 from curvefi/feat/table-headers
Browse files Browse the repository at this point in the history
feat: separate graph columns
  • Loading branch information
DanielSchiavini authored Jan 30, 2025
2 parents 6024e75 + 2e15900 commit 427d672
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const TableSettings = ({
searchText={searchParams.searchText}
handleInputChange={(val) => updatePath({ searchText: val })}
handleClose={() => updatePath({ searchText: '' })}
testId="search-pools"
/>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Stack from '@mui/material/Stack'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import { TableFilters, useColumnFilters } from '@ui-kit/shared/ui/TableFilters'
import { t } from '@lingui/macro'
import { CompactUsdCell, LineGraphCell, PoolTitleCell, UtilizationCell } from './cells'
import { CompactUsdCell, LineGraphCell, PoolTitleCell, RateCell, UtilizationCell } from './cells'
import { DataTable } from '@ui-kit/shared/ui/DataTable'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import {
Expand All @@ -16,11 +16,7 @@ import {
import { LendingMarketsFilters } from '@/loan/components/PageLlamaMarkets/LendingMarketsFilters'
import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString'
import { DeepKeys } from '@tanstack/table-core/build/lib/utils'
import {
isFeatureVisible,
useVisibilitySettings,
VisibilityGroup,
} from '@ui-kit/shared/ui/TableVisibilitySettingsPopover'
import { useVisibilitySettings, VisibilityGroup } from '@ui-kit/shared/ui/TableVisibilitySettingsPopover'

const { ColumnWidth, Spacing, MaxWidth } = SizesAndSpaces

Expand All @@ -43,17 +39,32 @@ const columns = [
size: ColumnWidth.lg,
}),
columnHelper.accessor('rates.borrow', {
header: t`7D Borrow Rate`,
cell: (c) => <LineGraphCell market={c.row.original} type="borrow" showChart={isFeatureVisible(c, borrowChartId)} />,
header: t`7D Avg Borrow Rate`,
cell: (c) => <RateCell market={c.row.original} type="borrow" />,
meta: { type: 'numeric' },
size: ColumnWidth.sm,
}),
columnHelper.accessor('rates.borrow', {
id: borrowChartId,
header: t`7D Borrow Rate Chart`,
cell: (c) => <LineGraphCell market={c.row.original} type="borrow" />,
size: ColumnWidth.md,
enableSorting: false,
}),
columnHelper.accessor('rates.lend', {
header: t`7D Supply Yield`,
cell: (c) => <LineGraphCell market={c.row.original} type="lend" showChart={isFeatureVisible(c, lendChartId)} />,
header: t`7D Avg Supply Yield`,
cell: (c) => <RateCell market={c.row.original} type="lend" />,
meta: { type: 'numeric' },
size: ColumnWidth.sm,
sortUndefined: 'last',
}),
columnHelper.accessor('rates.lend', {
id: lendChartId,
header: t`7D Supply Yield Chart`,
cell: (c) => <LineGraphCell market={c.row.original} type="lend" />,
size: ColumnWidth.md,
sortUndefined: 'last',
enableSorting: false,
}),
columnHelper.accessor('utilizationPercent', {
header: t`Utilization`,
Expand All @@ -79,17 +90,17 @@ const DEFAULT_VISIBILITY: VisibilityGroup[] = [
{
label: t`Markets`,
options: [
{ label: t`Available Liquidity`, id: 'totalSupplied.usdTotal', active: true, type: 'column' },
{ label: t`Utilization`, id: 'utilizationPercent', active: true, type: 'column' },
{ label: t`Available Liquidity`, id: 'totalSupplied.usdTotal', active: true },
{ label: t`Utilization`, id: 'utilizationPercent', active: true },
],
},
{
label: t`Borrow`,
options: [{ label: t`Chart`, id: borrowChartId, active: true, type: 'feature' }],
options: [{ label: t`Chart`, id: borrowChartId, active: true }],
},
{
label: t`Lend`,
options: [{ label: t`Chart`, id: lendChartId, active: true, type: 'feature' }],
options: [{ label: t`Chart`, id: lendChartId, active: true }],
},
]

Expand All @@ -103,8 +114,7 @@ export const LendingMarketsTable = ({
headerHeight: string
}) => {
const [columnFilters, columnFiltersById, setColumnFilter] = useColumnFilters()
const { columnSettings, columnVisibility, featureVisibility, toggleVisibility } =
useVisibilitySettings(DEFAULT_VISIBILITY)
const { columnSettings, columnVisibility, toggleVisibility } = useVisibilitySettings(DEFAULT_VISIBILITY)

const [sorting, onSortingChange] = useSortFromQueryString(DEFAULT_SORT)
const table = useReactTable({
Expand All @@ -113,7 +123,7 @@ export const LendingMarketsTable = ({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { sorting, columnVisibility, featureVisibility, columnFilters },
state: { sorting, columnVisibility, columnFilters },
onSortingChange,
maxMultiSortColCount: 3, // allow 3 columns to be sorted at once
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { LendingSnapshot, useLendingSnapshots } from '@/loan/entities/lending-snapshots'
import { LlamaMarket, LlamaMarketType } from '@/loan/entities/llama-markets'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import { Line, LineChart, YAxis } from 'recharts'
import { useTheme } from '@mui/material/styles'
import { DesignSystem } from '@ui-kit/themes/design'
import Stack from '@mui/material/Stack'
import Skeleton from '@mui/material/Skeleton'
import Typography from '@mui/material/Typography'
import { t } from '@lingui/macro'
import { useMemo } from 'react'
import { meanBy } from 'lodash'
import Box from '@mui/material/Box'
import { useCrvUsdSnapshots } from '@/loan/entities/crvusd'
import { GraphType, useSnapshots } from '../hooks/useSnapshots'

const graphSize = { width: 172, height: 48 }

type GraphType = 'borrow' | 'lend'

/**
* Get the color for the line graph. Will be green if the last value is higher than the first, red if lower, and blue if equal.
*/
Expand All @@ -34,65 +28,40 @@ const calculateDomain =
return [first - diff, first + diff]
}

type LineGraphCellProps = {
type RateCellProps = {
market: LlamaMarket
type: GraphType
showChart: boolean // chart is hidden depending on the chart settings
}

function useSnapshots({ address, blockchainId, controllerAddress, type: marketType }: LlamaMarket, type: GraphType) {
const isPool = marketType == LlamaMarketType.Pool
const showMintGraph = !isPool && type === 'borrow'
const contractAddress = isPool ? controllerAddress : address
const params = { blockchainId: blockchainId, contractAddress }
const { data: poolSnapshots, isLoading: poolIsLoading } = useLendingSnapshots(params, isPool)
const { data: mintSnapshots, isLoading: mintIsLoading } = useCrvUsdSnapshots(params, showMintGraph)
if (isPool) {
return { snapshots: poolSnapshots, isLoading: poolIsLoading, snapshotKey: `${type}_apy` as const }
}
return { snapshots: showMintGraph ? mintSnapshots : null, isLoading: mintIsLoading, snapshotKey: 'rate' as const }
}

/**
* Line graph cell that displays the average historical APY for a vault and a given type (borrow or lend).
*/
export const LineGraphCell = ({ market, type, showChart }: LineGraphCellProps) => {
const { snapshots, snapshotKey, isLoading } = useSnapshots(market, type)
const currentValue = market.rates[type]
const rate = useMemo(
() => (snapshots?.length ? meanBy(snapshots, (row) => row[snapshotKey]) : currentValue),
[snapshots, currentValue, snapshotKey],
)
export const LineGraphCell = ({ market, type }: RateCellProps) => {
const { snapshots, snapshotKey, isLoading, rate } = useSnapshots(market, type)
const { design } = useTheme()
if (rate == null) {
return '-'
}

return (
<Stack direction="row" alignItems="center" justifyContent="end" gap={3} data-testid={`line-graph-cell-${type}`}>
{rate.toPrecision(4)}%
{showChart && (
<Box data-testid={`line-graph-${type}`}>
{snapshots?.length ? (
<LineChart data={snapshots} {...graphSize} compact>
<YAxis hide type="number" domain={calculateDomain(snapshots[0][snapshotKey])} />
<Line
type="monotone"
dataKey={snapshotKey}
stroke={getColor(design, snapshots, snapshotKey)}
strokeWidth={1}
dot={<></>}
/>
</LineChart>
) : isLoading ? (
<Skeleton {...graphSize} />
) : (
<Typography sx={{ ...graphSize, alignContent: 'center', textAlign: 'left' }} variant="bodyXsBold">
{t`No historical data`}
</Typography>
)}
</Box>
<Box data-testid={`line-graph-${type}`}>
{snapshots?.length ? (
<LineChart data={snapshots} {...graphSize} compact>
<YAxis hide type="number" domain={calculateDomain(snapshots[0][snapshotKey] as number)} />
<Line
type="monotone"
dataKey={snapshotKey}
stroke={getColor(design, snapshots, snapshotKey)}
strokeWidth={1}
dot={<></>}
/>
</LineChart>
) : isLoading ? (
<Skeleton {...graphSize} />
) : (
<Typography sx={{ ...graphSize, alignContent: 'center', textAlign: 'left' }} variant="bodyXsBold">
{t`No historical data`}
</Typography>
)}
</Stack>
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LlamaMarket } from '@/loan/entities/llama-markets'
import { useSnapshots } from '../hooks/useSnapshots'
import { GraphType } from '@/loan/components/PageLlamaMarkets/hooks/useSnapshots'

export const RateCell = ({ market, type }: { market: LlamaMarket; type: GraphType }) => {
const { rate } = useSnapshots(market, type)
return rate == null ? '-' : `${rate.toPrecision(4)}%`
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './CompactUsdCell'
export * from './PoolTitleCell'
export * from './LineGraphCell'
export * from './RateCell'
export * from './UtilizationCell'
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { LlamaMarket, LlamaMarketType } from '@/loan/entities/llama-markets'
import { LendingSnapshot, useLendingSnapshots } from '@/loan/entities/lending-snapshots'
import { CrvUsdSnapshot, useCrvUsdSnapshots } from '@/loan/entities/crvusd'
import { useMemo } from 'react'
import { meanBy } from 'lodash'

export type GraphType = 'borrow' | 'lend'

type UseSnapshotsResult<T> = {
snapshots: T[] | null
isLoading: boolean
snapshotKey: keyof T
rate: number | null
}

export function useSnapshots<T = CrvUsdSnapshot | LendingSnapshot>(
{ address, blockchainId, controllerAddress, type: marketType, rates }: LlamaMarket,
type: GraphType,
): UseSnapshotsResult<T> {
const isPool = marketType == LlamaMarketType.Pool
const showMintGraph = !isPool && type === 'borrow'
const contractAddress = isPool ? controllerAddress : address
const params = { blockchainId: blockchainId, contractAddress }
const { data: poolSnapshots, isLoading: poolIsLoading } = useLendingSnapshots(params, isPool)
const { data: mintSnapshots, isLoading: mintIsLoading } = useCrvUsdSnapshots(params, showMintGraph)

const currentValue = rates[type]
const { snapshots, isLoading, snapshotKey } = isPool
? {
snapshots: poolSnapshots ?? null,
isLoading: poolIsLoading,
snapshotKey: `${type}_apy` as const,
}
: {
snapshots: (showMintGraph && mintSnapshots) || null,
isLoading: mintIsLoading,
snapshotKey: 'rate' as const,
}

const rate = useMemo(
() => (snapshots?.length ? meanBy(snapshots as T[], (row) => row[snapshotKey as keyof T]) : (currentValue ?? null)),
[snapshots, currentValue, snapshotKey],
)

return { snapshots, isLoading, snapshotKey, rate } as UseSnapshotsResult<T>
}
62 changes: 13 additions & 49 deletions apps/main/src/loan/entities/crvusd.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,20 @@
import { ContractParams, PoolParams, PoolQuery, queryFactory, rootKeys } from '@ui-kit/lib/model/query'
import { ContractParams, ContractQuery, queryFactory, rootKeys } from '@ui-kit/lib/model/query'
import { contractValidationSuite } from '@ui-kit/lib/model/query/contract-validation'
import { getSnapshots } from '@curvefi/prices-api/crvusd'
import { Chain } from '@curvefi/prices-api'
import type { Snapshot } from '@curvefi/prices-api/crvusd/models'

type CrvUsdSnapshotFromApi = {
rate: number
borrow_apy: number
lend_apy: number
liquidation_discount: number
loan_discount: number
n_loans: number
price_oracle: number
amm_price: number
base_price: number
total_debt: number
total_assets: number
total_debt_usd: number
total_assets_usd: number
minted: number
redeemed: number
minted_usd: number
redeemed_usd: number
min_band: number
max_band: number
collateral_balance: number
borrowed_balance: number
collateral_balance_usd: number
borrowed_balance_usd: number
sum_debt_squared: number
timestamp: string
}

type CrvUsdSnapshotsFromApi = {
chain: string
market_id: number
data: CrvUsdSnapshotFromApi[]
}

export const _getCrvUsdSnapshots = async ({
blockchainId,
contractAddress,
}: ContractParams): Promise<CrvUsdSnapshotFromApi[]> => {
const url = `https://prices.curve.fi/v1/crvusd/markets/${blockchainId}/${contractAddress}/snapshots`
const response = await fetch(url)
const { data } = (await response.json()) as CrvUsdSnapshotsFromApi
if (!data) {
throw new Error('Failed to fetch crvUSD snapshots')
}
return data
}
export type CrvUsdSnapshot = Snapshot

export const { useQuery: useCrvUsdSnapshots } = queryFactory({
queryKey: (params: ContractParams) => [...rootKeys.contract(params), 'crvUsd', 'snapshots'] as const,
queryFn: _getCrvUsdSnapshots,
staleTime: '1d',
queryFn: async ({ blockchainId, contractAddress }: ContractQuery): Promise<CrvUsdSnapshot[]> => {
const snapshots = await getSnapshots(blockchainId as Chain, contractAddress)
return snapshots.map((snapshot) => ({
...snapshot,
rate: snapshot.rate * 100, // Convert to percentage for consistency with lending snapshots
}))
},
staleTime: '10m',
validationSuite: contractValidationSuite,
})
19 changes: 9 additions & 10 deletions packages/curve-ui-kit/src/shared/ui/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,15 @@ const HeaderCell = <T extends unknown>({ header }: { header: Header<T, unknown>
variant="tableHeaderS"
>
{flexRender(column.columnDef.header, header.getContext())}
{canSort && (
<ArrowDownIcon
sx={{
...(sort === 'asc' && { transform: `rotate(180deg)` }),
verticalAlign: 'text-bottom',
fontSize: sort ? 20 : 0,
transition: `transform ${TransitionFunction}, font-size ${TransitionFunction}`,
}}
/>
)}
<ArrowDownIcon
sx={{
...(sort === 'asc' && { transform: `rotate(180deg)` }),
verticalAlign: 'text-bottom',
fontSize: sort ? 20 : 0,
transition: `transform ${TransitionFunction}, font-size ${TransitionFunction}`,
visibility: canSort ? 'visible' : 'hidden', // render it invisible to avoid layout shift
}}
/>
</Typography>
)
)
Expand Down
Loading

0 comments on commit 427d672

Please sign in to comment.