Feat/score changes (#696)
* chore: add query and generate types for shwoing scores * fix: add key prop * chore: add tests for new functionality * style: remove debug * Update libs/types/apollo.config.js
This commit is contained in:
parent
80e6b3b8fc
commit
c7e65080b7
@ -545,5 +545,9 @@
|
|||||||
"proposalTerms": "Proposal terms",
|
"proposalTerms": "Proposal terms",
|
||||||
"currentlySetTo": "Currently set to ",
|
"currentlySetTo": "Currently set to ",
|
||||||
"pass": "Pass",
|
"pass": "Pass",
|
||||||
"fail": "Fail"
|
"fail": "Fail",
|
||||||
|
"rankingScore": "Ranking score",
|
||||||
|
"stakeScore": "Stake score",
|
||||||
|
"performanceScore": "Performance score",
|
||||||
|
"votingPower": "Voting score"
|
||||||
}
|
}
|
||||||
|
24
apps/token/src/routes/staking/__generated__/Nodes.ts
generated
24
apps/token/src/routes/staking/__generated__/Nodes.ts
generated
@ -25,6 +25,26 @@ export interface Nodes_nodes_epochData {
|
|||||||
online: number;
|
online: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Nodes_nodes_rankingScore {
|
||||||
|
__typename: "RankingScore";
|
||||||
|
/**
|
||||||
|
* The ranking score of the validator
|
||||||
|
*/
|
||||||
|
rankingScore: string;
|
||||||
|
/**
|
||||||
|
* The stake based score of the validator (no anti-whaling)
|
||||||
|
*/
|
||||||
|
stakeScore: string;
|
||||||
|
/**
|
||||||
|
* The performance score of the validator
|
||||||
|
*/
|
||||||
|
performanceScore: string;
|
||||||
|
/**
|
||||||
|
* The tendermint voting power of the validator (uint32)
|
||||||
|
*/
|
||||||
|
votingPower: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Nodes_nodes {
|
export interface Nodes_nodes {
|
||||||
__typename: "Node";
|
__typename: "Node";
|
||||||
/**
|
/**
|
||||||
@ -66,6 +86,10 @@ export interface Nodes_nodes {
|
|||||||
pendingStakeFormatted: string;
|
pendingStakeFormatted: string;
|
||||||
epochData: Nodes_nodes_epochData | null;
|
epochData: Nodes_nodes_epochData | null;
|
||||||
status: NodeStatus;
|
status: NodeStatus;
|
||||||
|
/**
|
||||||
|
* Ranking scores and status for the validator for the current epoch
|
||||||
|
*/
|
||||||
|
rankingScore: Nodes_nodes_rankingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Nodes_nodeData {
|
export interface Nodes_nodeData {
|
||||||
|
@ -100,6 +100,26 @@ export interface Staking_nodes_epochData {
|
|||||||
online: number;
|
online: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Staking_nodes_rankingScore {
|
||||||
|
__typename: "RankingScore";
|
||||||
|
/**
|
||||||
|
* The ranking score of the validator
|
||||||
|
*/
|
||||||
|
rankingScore: string;
|
||||||
|
/**
|
||||||
|
* The stake based score of the validator (no anti-whaling)
|
||||||
|
*/
|
||||||
|
stakeScore: string;
|
||||||
|
/**
|
||||||
|
* The performance score of the validator
|
||||||
|
*/
|
||||||
|
performanceScore: string;
|
||||||
|
/**
|
||||||
|
* The tendermint voting power of the validator (uint32)
|
||||||
|
*/
|
||||||
|
votingPower: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Staking_nodes {
|
export interface Staking_nodes {
|
||||||
__typename: "Node";
|
__typename: "Node";
|
||||||
/**
|
/**
|
||||||
@ -145,6 +165,10 @@ export interface Staking_nodes {
|
|||||||
pendingStakeFormatted: string;
|
pendingStakeFormatted: string;
|
||||||
epochData: Staking_nodes_epochData | null;
|
epochData: Staking_nodes_epochData | null;
|
||||||
status: NodeStatus;
|
status: NodeStatus;
|
||||||
|
/**
|
||||||
|
* Ranking scores and status for the validator for the current epoch
|
||||||
|
*/
|
||||||
|
rankingScore: Staking_nodes_rankingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Staking_nodeData {
|
export interface Staking_nodeData {
|
||||||
|
204
apps/token/src/routes/staking/node-list.spec.tsx
Normal file
204
apps/token/src/routes/staking/node-list.spec.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import { NodeList, NODES_QUERY } from './node-list';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { Nodes_nodes } from './__generated__/Nodes';
|
||||||
|
|
||||||
|
jest.mock('../../components/epoch-countdown', () => ({
|
||||||
|
EpochCountdown: () => <div data-testid="epoch-info"></div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nodeFactory = (overrides?: Partial<Nodes_nodes>) => ({
|
||||||
|
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
|
||||||
|
name: 'Skynet',
|
||||||
|
pubkey: '6abc23391a9f888ab240415bf63d6844b03fc360be822f4a1d2cd832d87b2917',
|
||||||
|
infoUrl: 'https://en.wikipedia.org/wiki/Skynet_(Terminator)',
|
||||||
|
location: '',
|
||||||
|
stakedByOperator: '3000000000000000000000',
|
||||||
|
stakedByDelegates: '11182454495731682635157',
|
||||||
|
stakedTotal: '14182454495731682635157',
|
||||||
|
pendingStake: '0',
|
||||||
|
stakedByOperatorFormatted: addDecimal(
|
||||||
|
overrides?.stakedByOperator || '3000000000000000000000',
|
||||||
|
18
|
||||||
|
),
|
||||||
|
stakedByDelegatesFormatted: addDecimal(
|
||||||
|
overrides?.stakedByDelegates || '11182454495731682635157',
|
||||||
|
18
|
||||||
|
),
|
||||||
|
stakedTotalFormatted: addDecimal(
|
||||||
|
overrides?.stakedTotal || '14182454495731682635157',
|
||||||
|
18
|
||||||
|
),
|
||||||
|
pendingStakeFormatted: addDecimal(overrides?.pendingStake || '0', 18),
|
||||||
|
epochData: null,
|
||||||
|
status: 'Validator',
|
||||||
|
rankingScore: {
|
||||||
|
rankingScore: '0.67845061012234727427532760837568',
|
||||||
|
stakeScore: '0.3392701644525644',
|
||||||
|
performanceScore: '0.9998677767864936',
|
||||||
|
votingPower: '2407',
|
||||||
|
__typename: 'RankingScore',
|
||||||
|
},
|
||||||
|
__typename: 'Node',
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
const MOCK_NODES = {
|
||||||
|
nodes: [
|
||||||
|
nodeFactory(),
|
||||||
|
nodeFactory({
|
||||||
|
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
|
||||||
|
name: 'T-800 Terminator',
|
||||||
|
pubkey:
|
||||||
|
'ccc3b8362c25b09d20df8ea407b0a476d6b24a0e72bc063d0033c8841652ddd4',
|
||||||
|
infoUrl: 'https://en.wikipedia.org/wiki/Terminator_(character)',
|
||||||
|
stakedByOperator: '3000000000000000000000',
|
||||||
|
stakedByDelegates: '6618711883996159534058',
|
||||||
|
stakedTotal: '9618711883996159534058',
|
||||||
|
rankingScore: {
|
||||||
|
rankingScore: '0.4601942440481428',
|
||||||
|
stakeScore: '0.2300971220240714',
|
||||||
|
performanceScore: '1',
|
||||||
|
votingPower: '2408',
|
||||||
|
__typename: 'RankingScore',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
nodeFactory({
|
||||||
|
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
|
||||||
|
name: 'NCC-1701-E',
|
||||||
|
pubkey:
|
||||||
|
'0931a8fd8cc935458f470e435a05414387cea6f329d648be894fcd44bd517a2b',
|
||||||
|
infoUrl: 'https://en.wikipedia.org/wiki/USS_Enterprise_(NCC-1701-E)',
|
||||||
|
stakedByOperator: '3000000000000000000000',
|
||||||
|
stakedByDelegates: '1041343338923442976709',
|
||||||
|
stakedTotal: '4041343338923442976709',
|
||||||
|
pendingStake: '0',
|
||||||
|
rankingScore: {
|
||||||
|
rankingScore: '0.1932810100133910357676209647912',
|
||||||
|
stakeScore: '0.0966762995515676',
|
||||||
|
performanceScore: '0.999629748500531',
|
||||||
|
votingPower: '1163',
|
||||||
|
__typename: 'RankingScore',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
nodeData: {
|
||||||
|
stakedTotal: '27842509718651285145924',
|
||||||
|
stakedTotalFormatted: addDecimal('27842509718651285145924', 18),
|
||||||
|
totalNodes: 3,
|
||||||
|
inactiveNodes: 0,
|
||||||
|
validatingNodes: 3,
|
||||||
|
uptime: 1560.266845703125,
|
||||||
|
__typename: 'NodeData',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNodeList = () => {
|
||||||
|
return render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider
|
||||||
|
mocks={[
|
||||||
|
{
|
||||||
|
request: { query: NODES_QUERY },
|
||||||
|
result: { data: MOCK_NODES },
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<NodeList
|
||||||
|
epoch={{
|
||||||
|
__typename: 'Epoch',
|
||||||
|
id: '1',
|
||||||
|
timestamps: {
|
||||||
|
__typename: 'EpochTimestamps',
|
||||||
|
start: new Date(0).toISOString(),
|
||||||
|
end: '',
|
||||||
|
expiry: new Date(1000 * 60 * 60 * 24).toISOString(),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
party={{
|
||||||
|
__typename: 'Party',
|
||||||
|
id: 'foo',
|
||||||
|
delegations: [
|
||||||
|
{
|
||||||
|
__typename: 'Delegation',
|
||||||
|
amount: '0',
|
||||||
|
amountFormatted: '0',
|
||||||
|
epoch: 1,
|
||||||
|
node: {
|
||||||
|
__typename: 'Node',
|
||||||
|
id: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stake: {
|
||||||
|
__typename: 'PartyStake',
|
||||||
|
currentStakeAvailable: '0',
|
||||||
|
currentStakeAvailableFormatted: '0',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Nodes list', () => {
|
||||||
|
it('should render epoch info', async () => {
|
||||||
|
renderNodeList();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(screen.getByTestId('epoch-info')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should a lit of all nodes', async () => {
|
||||||
|
renderNodeList();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
const items = screen.queryAllByTestId('node-list-item');
|
||||||
|
expect(items).toHaveLength(3);
|
||||||
|
for (const item of MOCK_NODES.nodes) {
|
||||||
|
expect(screen.getByText(item.name)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list the total stake and rewards of each node', async () => {
|
||||||
|
renderNodeList();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
const items = screen.queryAllByTestId('node-list-item');
|
||||||
|
const item = within(items[0]);
|
||||||
|
const rows = item.getAllByRole('row');
|
||||||
|
|
||||||
|
const expectedValues = [
|
||||||
|
['Total stake', '14,182.45 (50.94%)'],
|
||||||
|
['Ranking score', '0.6785'],
|
||||||
|
['Stake score', '0.3393'],
|
||||||
|
['Performance score', '0.9999'],
|
||||||
|
['Voting score', '2,407.0000'],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [i, r] of rows.entries()) {
|
||||||
|
const row = within(r);
|
||||||
|
const cell = row.getByRole('cell');
|
||||||
|
const header = row.getByRole('rowheader');
|
||||||
|
expect(header).toHaveTextContent(expectedValues[i][0]);
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i][1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -8,7 +8,7 @@ import { EpochCountdown } from '../../components/epoch-countdown';
|
|||||||
import { BigNumber } from '../../lib/bignumber';
|
import { BigNumber } from '../../lib/bignumber';
|
||||||
import { formatNumber } from '../../lib/format-number';
|
import { formatNumber } from '../../lib/format-number';
|
||||||
import { truncateMiddle } from '../../lib/truncate-middle';
|
import { truncateMiddle } from '../../lib/truncate-middle';
|
||||||
import type { Nodes } from './__generated__/Nodes';
|
import type { Nodes, Nodes_nodes_rankingScore } from './__generated__/Nodes';
|
||||||
import type { Staking_epoch, Staking_party } from './__generated__/Staking';
|
import type { Staking_epoch, Staking_party } from './__generated__/Staking';
|
||||||
|
|
||||||
export const NODES_QUERY = gql`
|
export const NODES_QUERY = gql`
|
||||||
@ -33,6 +33,13 @@ export const NODES_QUERY = gql`
|
|||||||
online
|
online
|
||||||
}
|
}
|
||||||
status
|
status
|
||||||
|
rankingScore {
|
||||||
|
rankingScore
|
||||||
|
stakeScore
|
||||||
|
performanceScore
|
||||||
|
votingPower
|
||||||
|
stakeScore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nodeData {
|
nodeData {
|
||||||
stakedTotal
|
stakedTotal
|
||||||
@ -54,7 +61,10 @@ const NodeListTr = ({ children }: { children: React.ReactNode }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const NodeListTh = ({ children }: { children: React.ReactNode }) => (
|
const NodeListTh = ({ children }: { children: React.ReactNode }) => (
|
||||||
<th className="flex-1 break-words py-1 pr-4 pl-0 text-white-60 font-normal">
|
<th
|
||||||
|
role="rowheader"
|
||||||
|
className="flex-1 break-words py-1 pr-4 pl-0 text-white-60 font-normal"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
@ -113,6 +123,7 @@ export const NodeList = ({ epoch, party }: NodeListProps) => {
|
|||||||
userStake,
|
userStake,
|
||||||
userStakePercentage,
|
userStakePercentage,
|
||||||
epoch,
|
epoch,
|
||||||
|
scores: node.rankingScore,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,6 +171,7 @@ export interface NodeListItemProps {
|
|||||||
stakedTotalPercentage: string;
|
stakedTotalPercentage: string;
|
||||||
userStake: BigNumber;
|
userStake: BigNumber;
|
||||||
userStakePercentage: string;
|
userStakePercentage: string;
|
||||||
|
scores: Nodes_nodes_rankingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeListItem = ({
|
export const NodeListItem = ({
|
||||||
@ -169,6 +181,7 @@ export const NodeListItem = ({
|
|||||||
stakedTotalPercentage,
|
stakedTotalPercentage,
|
||||||
userStake,
|
userStake,
|
||||||
userStakePercentage,
|
userStakePercentage,
|
||||||
|
scores,
|
||||||
}: NodeListItemProps) => {
|
}: NodeListItemProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -186,24 +199,36 @@ export const NodeListItem = ({
|
|||||||
<span
|
<span
|
||||||
className="uppercase text-white-60"
|
className="uppercase text-white-60"
|
||||||
title={`${t('id')}: ${id}`}
|
title={`${t('id')}: ${id}`}
|
||||||
|
data-testid="node-list-item-name"
|
||||||
>
|
>
|
||||||
{truncateMiddle(id)}
|
{truncateMiddle(id)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<table className="flex-1 text-body border-collapse mt-4">
|
<table
|
||||||
|
className="flex-1 text-body border-collapse mt-4"
|
||||||
|
data-testid="node-list-item-table"
|
||||||
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
<NodeListTr>
|
<NodeListTr>
|
||||||
<NodeListTh>{t('Total stake')}</NodeListTh>
|
<NodeListTh>{t('Total stake')}</NodeListTh>
|
||||||
<NodeListTd>{formatNumber(stakedOnNode, 2)}</NodeListTd>
|
<NodeListTd>
|
||||||
<NodeListTd>{stakedTotalPercentage}</NodeListTd>
|
{formatNumber(stakedOnNode, 2)} ({stakedTotalPercentage})
|
||||||
|
</NodeListTd>
|
||||||
</NodeListTr>
|
</NodeListTr>
|
||||||
<NodeListTr>
|
{scores
|
||||||
<NodeListTh>{t('Your stake')}</NodeListTh>
|
? Object.entries(scores)
|
||||||
<NodeListTd>{formatNumber(userStake, 2)}</NodeListTd>
|
.filter(([key]) => key !== '__typename')
|
||||||
<NodeListTd>{userStakePercentage}</NodeListTd>
|
.map(([key, value]) => (
|
||||||
|
<NodeListTr key={`${id}_${key}`}>
|
||||||
|
<NodeListTh>{t(key)}</NodeListTh>
|
||||||
|
<NodeListTd>
|
||||||
|
{formatNumber(new BigNumber(value), 4)}
|
||||||
|
</NodeListTd>
|
||||||
</NodeListTr>
|
</NodeListTr>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</li>
|
</li>
|
||||||
|
@ -53,6 +53,13 @@ export const STAKING_QUERY = gql`
|
|||||||
online
|
online
|
||||||
}
|
}
|
||||||
status
|
status
|
||||||
|
rankingScore {
|
||||||
|
rankingScore
|
||||||
|
stakeScore
|
||||||
|
performanceScore
|
||||||
|
votingPower
|
||||||
|
stakeScore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nodeData {
|
nodeData {
|
||||||
stakedTotal
|
stakedTotal
|
||||||
|
Loading…
Reference in New Issue
Block a user