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

Leaderboard Page Displays Top 10 Accounts by Balance in XUS #106

Merged
merged 4 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions end2end/Leaderboard_test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Feature('leaderboard-page')

function seeRowHeaders(I) {
I.see('Ranking')
I.see('Version')
I.see('Amount (XUS)')
I.seeInsideTestId('Ranking', 'top-10-transactions')
I.seeInsideTestId('Version', 'top-10-transactions')
I.seeInsideTestId('Amount (XUS)', 'top-10-transactions')
}

function seeRowData(I) {
I.see('1')
I.see('2345')
I.see('5432')
I.seeInsideTestId('1', 'top-10-transactions')
I.seeInsideTestId('2345', 'top-10-transactions')
I.seeInsideTestId('5432', 'top-10-transactions')
}

Scenario('navigating to the leaderboard page', ({ I }) => {
Expand All @@ -26,3 +26,23 @@ Scenario('displaying the top 10 transactions in the past 24 hours', ({ I }) => {
seeRowHeaders(I)
seeRowData(I)
})

Scenario('displaying the top 10 accounts in the past 24 hours', ({ I }) => {
const seeRowHeaders = (I) => {
I.seeInsideTestId('Rank', 'top-10-accounts')
I.seeInsideTestId('Address', 'top-10-accounts')
I.seeInsideTestId('Amount (XUS)', 'top-10-accounts')
}

const seeRowData = (I) => {
I.seeInsideTestId('1', 'top-10-accounts')
I.seeInsideTestId('0000000000000000000000000B1E55ED', 'top-10-accounts')
I.seeInsideTestId('5432', 'top-10-accounts')
}

I.amOnPage('/leaderboard')
I.see('Top 10 Accounts (XUS)')

seeRowHeaders(I)
seeRowData(I)
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{
"matchesJsonPath": {
"expression": "$.query",
"contains": "query{accounts(limit:"
"contains": "query{accounts(limit:10,order_by:[{transaction_version:"
}
}
]
Expand Down
35 changes: 35 additions & 0 deletions end2end/wiremock/mappings/analyticsApi/postForTop10Accounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"priority": 1,
"request": {
"method": "POST",
"url": "/v1/graphql",
"bodyPatterns" : [ {
"matchesJsonPath" : {
"expression": "$.query",
"contains": "query{accounts_balances(limit:10,where:{currency:{_eq:\"XUS\"}},order_by:[{balance:desc}]){address balance}"
}
} ]
},
"response": {
"status": 200,
"headers": {
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Methods" : "*",
"Access-Control-Allow-Headers": "*"
},
"jsonBody": {
"data": {
"accounts_balances": [
{
"address": "0000000000000000000000000B1E55ED",
"balance": 5432
},
{
"address": "00000000000000000000000000000001",
"balance": 789
}
]
}
}
}
}
41 changes: 27 additions & 14 deletions src/ApiRequestComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { ReactElement, useEffect, useState } from 'react'
import { ReactJSXElement } from '@emotion/react/types/jsx-namespace'
import MainWrapper from './MainWrapper'
import { DataOrErrors, FetchError } from './api_clients/FetchTypes'
import { Card } from 'react-bootstrap'

interface ApiRequestPageProps<T> {
children: ReactElement
Expand All @@ -13,37 +14,49 @@ interface ApiRequestPageProps<T> {

export function PlainErrorComponent() {
return (
<span role='dialog' className='network-error'>
<span role="dialog" className="network-error">
Something went wrong. Please try again later
</span>
)
}

export function FullPageErrorComponent() {
return (
<MainWrapper>
export const FullPageErrorComponent = () => (
<MainWrapper>
<PlainErrorComponent />
</MainWrapper>
)
export const ErrorCardComponent = ({ title = '' }: { title?: string }) => (
<>
<Card.Header>{title}</Card.Header>
<Card.Body>
<PlainErrorComponent />
</MainWrapper>
)
}
</Card.Body>
</>
)

const DefaultErrorComponent = FullPageErrorComponent

export function PlainLoadingComponent() {
return (
<span className='loading' role='loading'>
<span className="loading" role="loading">
Loading, please wait
</span>
)
}

export function FullPageLoadingComponent() {
return (
<MainWrapper>
export const FullPageLoadingComponent = () => (
<MainWrapper>
<PlainLoadingComponent />
</MainWrapper>
)
export const LoadingCardComponent = ({ title = '' }: { title?: string }) => (
<>
<Card.Header>{title}</Card.Header>
<Card.Body>
<PlainLoadingComponent />
</MainWrapper>
)
}
</Card.Body>
</>
)

const DefaultLoadingComponent = FullPageLoadingComponent

Expand Down
87 changes: 87 additions & 0 deletions src/Pages/LeaderboardPage/Cards/Top10AccountsCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import '@testing-library/jest-dom' // provides `expect(...).toBeInTheDocument()`
import { render, screen, waitForElementToBeRemoved } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { postQueryToAnalyticsApi } from '../../../api_clients/AnalyticsClient'
import { top10AccountsQuery } from '../../../api_clients/AnalyticsQueries'
import Top10AccountsCard, { TopAccountEvent } from './Top10AccountsCard'

jest.mock('../../../api_clients/AnalyticsClient', () => ({
postQueryToAnalyticsApi: jest.fn(),
}))

jest.useFakeTimers().setSystemTime(new Date('2021-01-01').getTime())

const renderSubject = async (
accounts: TopAccountEvent[] = [],
) => {
// @ts-ignore TS is bad at mocking
postQueryToAnalyticsApi.mockResolvedValue({
data: accounts,
})

render(<BrowserRouter>
<Top10AccountsCard />
</BrowserRouter>)
await waitForElementToBeRemoved(screen.queryByRole('loading'))
}

describe('Top10AccountsCard', () => {
beforeEach(() => {
// @ts-ignore TS is bad at mocking
postQueryToAnalyticsApi.mockReset()
})

it('should render the data', async () => {
await renderSubject([
{
address: '0000000000000000000000000B1E55ED',
balance: 54321,
},
])

const table: HTMLTableElement | null | undefined = screen.queryByTestId('top-10-accounts')?.querySelector('table')
expect(table).toBeInTheDocument()
const cardBody = table!.tBodies.item(0)!
expect(cardBody.rows).toHaveLength(1)
expect(cardBody.rows[0].cells[0].textContent).toEqual('1')
expect(cardBody.rows[0].cells[1].textContent).toEqual('0000000000000000000000000b1e55ed')
expect(cardBody.rows[0].cells[2].textContent).toEqual('54321')
})
it('should link to the corresponding accounts', async () => {
await renderSubject([
{
address: '0000000000000000000010000B1E55ED',
balance: 54321,
},
])

const transactionCell = screen.queryByTestId('top-10-accounts')!.querySelector('table tbody tr td:nth-child(2)')
expect(transactionCell).toBeInTheDocument()
const transactionLink: HTMLAnchorElement | null = transactionCell!.querySelector('a')
expect(transactionLink).toBeInTheDocument()
expect(transactionLink!.href).toMatch('http://localhost/address/0000000000000000000010000b1e55ed')
})
it('should render the data in the order provided by the API', async () => {
await renderSubject([
{
address: '0000000000000000000000000B0E55ED',
balance: 1000,
},
{
address: '0000000000000000000000000B1E552D',
balance: 2000,
},
])

const table: HTMLTableElement = screen.queryByTestId('top-10-accounts')!.querySelector('table')!
const cardBody = table.tBodies.item(0)!
expect(cardBody!.rows).toHaveLength(2)
expect(cardBody!.rows[0].cells[1].textContent).toEqual('0000000000000000000000000b0e55ed')
expect(cardBody!.rows[1].cells[1].textContent).toEqual('0000000000000000000000000b1e552d')
})
it('should query the Analytics API correctly', async () => {
await renderSubject()
const expectedQuery = top10AccountsQuery('XUS')
expect(postQueryToAnalyticsApi).toHaveBeenCalledWith(expectedQuery, 'accounts_balances')
})
})
80 changes: 80 additions & 0 deletions src/Pages/LeaderboardPage/Cards/Top10AccountsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import ApiRequestComponent, {
ErrorCardComponent,
LoadingCardComponent,
} from '../../../ApiRequestComponent'
import { DataOrErrors } from '../../../api_clients/FetchTypes'
import { postQueryToAnalyticsApi } from '../../../api_clients/AnalyticsClient'
import { top10AccountsQuery } from '../../../api_clients/AnalyticsQueries'
import { KnownCurrency } from '../../../api_clients/BlockchainRestTypes'
import { Card } from 'react-bootstrap'
import Table, { column } from '../../../Table'
import ReactTooltip from 'react-tooltip'
import { AccountAddress } from '../../../TableComponents/Link'

export interface TopAccountEvent {
address: string
balance: number
}

type Top10AccountsTableProps = { topAccounts: TopAccountEvent[] }

function Top10AccountsTable({ data }: { data: Top10AccountsTableProps }) {
const { topAccounts } = data
console.log(data)
const paymentData = topAccounts.map((item, index) => ({
...item,
rank: index + 1,
}))

return (
<>
<Card.Header>
<div data-tip data-for="top-10-definition-xus">
Top 10 Accounts (XUS)
</div>
<ReactTooltip id="top-10-definition-xus">
10 largest accounts in XUS currently
</ReactTooltip>
</Card.Header>
<Card.Body>
<Table
columns={[
column('Ranking', 'rank'),
column('Address', 'address', AccountAddress),
column('Amount (XUS)', 'balance'),
]}
data={paymentData}
/>
</Card.Body>
</>
)
}

async function getTopAccounts(
currency: KnownCurrency
): Promise<DataOrErrors<Top10AccountsTableProps>> {
const result: DataOrErrors<TopAccountEvent[]> = await postQueryToAnalyticsApi(
top10AccountsQuery(currency),
'accounts_balances'
)
if ('data' in result) {
return { data: { topAccounts: result.data } }
} else {
return result
}
}

export default function Top10AccountsCard() {
return (
<Card data-testid="top-10-accounts">
<ApiRequestComponent
request={getTopAccounts}
args={['XUS']}
errorComponent={<ErrorCardComponent title={'Top 10 Accounts (XUS)'}/>}
loadingComponent={<LoadingCardComponent title={'Top 10 Accounts (XUS)'}/>}
>
<Top10AccountsTable data={{ topAccounts: [] }} />
</ApiRequestComponent>
</Card>
)
}
7 changes: 3 additions & 4 deletions src/Pages/LeaderboardPage/Cards/Top10TransactionsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ApiRequestComponent, {
PlainErrorComponent,
PlainLoadingComponent,
ErrorCardComponent, LoadingCardComponent,
} from '../../../ApiRequestComponent'
import { DataOrErrors } from '../../../api_clients/FetchTypes'
import { postQueryToAnalyticsApi } from '../../../api_clients/AnalyticsClient'
Expand Down Expand Up @@ -84,8 +83,8 @@ export default function Top10TransactionsCard() {
<ApiRequestComponent
request={getTopTransactions}
args={['XUS']}
errorComponent={<PlainErrorComponent />}
loadingComponent={<PlainLoadingComponent />}
errorComponent={<ErrorCardComponent title={'Top 10 Transactions (XUS)'}/>}
loadingComponent={<LoadingCardComponent title={'Top 10 Transactions (XUS)'}/>}
>
<Top10TransactionsTable data={{ topPayments: [] }} />
</ApiRequestComponent>
Expand Down
6 changes: 6 additions & 0 deletions src/Pages/LeaderboardPage/LeaderboardPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ describe('LeaderboardPage', () => {
mockTop10TransactionsCardText
)
})
it('should display the top 10 account balances', async () => {
await renderSubject()
const top10TransactionsCard = screen.queryByTestId('top-10-transactions')
expect(top10TransactionsCard).toBeInTheDocument()
expect(top10TransactionsCard!.textContent).toContain(mockTop10TransactionsCardText)
})
})
2 changes: 2 additions & 0 deletions src/Pages/LeaderboardPage/LeaderboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import Top10TransactionsCard from './Cards/Top10TransactionsCard'
import MainWrapper from '../../MainWrapper'
import Top10AccountsCard from './Cards/Top10AccountsCard'

export default function LeaderboardPage() {
return (
Expand All @@ -9,6 +10,7 @@ export default function LeaderboardPage() {
<header data-testid='leaderboard-page-header'>
<h2>Diem Leaderboard</h2>
</header>
<Top10AccountsCard />
<Top10TransactionsCard />
</main>
</MainWrapper>
Expand Down
Loading