From eca212eee04a8c0c302e1ab070488acc7d37f106 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Tue, 28 Mar 2023 15:28:15 +0100 Subject: [PATCH] feat(governance): see user stake on validators table (#3250) Co-authored-by: Joe --- .../src/integration/view/validators.cy.ts | 18 +- .../src/support/staking.functions.ts | 8 +- .../governance/src/i18n/translations/dev.json | 3 + apps/governance/src/routes/home/index.tsx | 4 +- .../src/routes/rewards/home/rewards-page.tsx | 4 +- .../routes/staking/{node => }/Staking.graphql | 17 +- .../{node => }/__generated__/Staking.ts | 25 ++- .../staking/__generated___/PreviousEpoch.ts | 106 ---------- .../__generated___/PartyStakeLinkings.ts | 114 ----------- .../associate/associate-transaction.tsx | 2 +- .../src/routes/staking/associate/hooks.ts | 4 +- .../staking/home/__generated___/Nodes.ts | 149 -------------- .../src/routes/staking/home/epoch-data.tsx | 45 +++-- .../consensus-validators-table.spec.tsx | 13 +- .../consensus-validators-table.tsx | 72 +++++-- .../home/validator-tables/shared.spec.tsx | 70 ------- .../staking/home/validator-tables/shared.tsx | 181 ++++++++++++++++-- .../standby-pending-validators-table.tsx | 53 +++-- .../validator-tables/validator-tables.tsx | 136 ++++++++++--- .../src/routes/staking/node/node.tsx | 18 +- .../routes/staking/node/nodes-container.tsx | 8 +- .../routes/staking/node/validator-table.tsx | 14 +- .../src/routes/staking/node/your-stake.tsx | 2 +- apps/governance/src/routes/staking/shared.ts | 2 +- libs/types/.env | 2 +- libs/types/src/global-types-mappings.ts | 1 + 26 files changed, 475 insertions(+), 596 deletions(-) rename apps/governance/src/routes/staking/{node => }/Staking.graphql (85%) rename apps/governance/src/routes/staking/{node => }/__generated__/Staking.ts (71%) delete mode 100644 apps/governance/src/routes/staking/__generated___/PreviousEpoch.ts delete mode 100644 apps/governance/src/routes/staking/associate/__generated___/PartyStakeLinkings.ts delete mode 100644 apps/governance/src/routes/staking/home/__generated___/Nodes.ts delete mode 100644 apps/governance/src/routes/staking/home/validator-tables/shared.spec.tsx diff --git a/apps/governance-e2e/src/integration/view/validators.cy.ts b/apps/governance-e2e/src/integration/view/validators.cy.ts index 40a8ed437..8f1e22fff 100644 --- a/apps/governance-e2e/src/integration/view/validators.cy.ts +++ b/apps/governance-e2e/src/integration/view/validators.cy.ts @@ -74,7 +74,7 @@ context('Validators Page - verify elements on page', function () { }); it('Should be able to see validator stake', function () { - cy.get('[col-id="stake"] > div > span > span') + cy.getByTestId('total-stake') .should('have.length.at.least', 1) .each(($stake) => { cy.wrap($stake).should('not.be.empty'); @@ -82,7 +82,7 @@ context('Validators Page - verify elements on page', function () { }); it('Should be able to see validator stake tooltip', function () { - cy.get('[col-id="stake"] > div > span > span').first().realHover(); + cy.getByTestId('total-stake').first().realHover(); cy.get(stakedByOperatorToolTip) .invoke('text') @@ -96,17 +96,15 @@ context('Validators Page - verify elements on page', function () { }); it('Should be able to see validator normalised voting power', function () { - cy.get('[col-id="normalisedVotingPower"] > div > span > span') + cy.getByTestId('normalised-voting-power') .should('have.length.at.least', 1) .each(($vPower) => { cy.wrap($vPower).should('not.be.empty'); }); }); - it('Should be able to see validator voting power tooltip', function () { - cy.get('[col-id="normalisedVotingPower"] > div > span > span') - .first() - .realHover(); + it('Should be able to see validator normalised voting power tooltip', function () { + cy.getByTestId('normalised-voting-power').first().realHover(); cy.get(unnormalisedVotingPowerToolTip) .invoke('text') @@ -118,7 +116,7 @@ context('Validators Page - verify elements on page', function () { // 2002-SINC-018 it('Should be able to see validator total penalties', function () { - cy.get('[col-id="totalPenalties"] > div > span > span') + cy.getByTestId('total-penalty') .should('have.length.at.least', 1) .each(($penalties) => { cy.wrap($penalties).should('contain.text', '0%'); @@ -126,7 +124,7 @@ context('Validators Page - verify elements on page', function () { }); it('Should be able to see validator penalties tooltip', function () { - cy.get('[col-id="totalPenalties"] > div > span > span').realHover(); + cy.getByTestId('total-penalty').realHover(); cy.get(performancePenaltyToolTip) .invoke('text') @@ -140,7 +138,7 @@ context('Validators Page - verify elements on page', function () { }); it('Should be able to see validator pending stake', function () { - cy.get('[col-id="pendingStake"] > div > span') + cy.getByTestId('total-pending-stake') .should('have.length.at.least', 1) .each(($pendingStake) => { cy.wrap($pendingStake).should('contain.text', '0.00'); diff --git a/apps/governance-e2e/src/support/staking.functions.ts b/apps/governance-e2e/src/support/staking.functions.ts index 0064e087f..c2fa2f4d1 100644 --- a/apps/governance-e2e/src/support/staking.functions.ts +++ b/apps/governance-e2e/src/support/staking.functions.ts @@ -14,8 +14,8 @@ const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]'; const associateContractRadioButton = '[data-testid="associate-radio-contract"]'; const stakeMaximumTokens = '[data-testid="token-amount-use-maximum"]'; const stakeValidatorListPendingStake = '[col-id="pendingStake"]'; -const stakeValidatorListTotalStake = '[col-id="stake"] > div > span'; -const stakeValidatorListTotalShare = '[col-id="stakeShare"] > div > span'; +const stakeValidatorListTotalStake = 'total-stake'; +const stakeValidatorListTotalShare = 'total-stake-share'; const stakeValidatorListName = '[col-id="validator"]'; const vegaKeySelector = '#vega-key-selector'; const dialogCloseButton = '[data-testid="dialog-close"]'; @@ -185,11 +185,11 @@ export function validateValidatorListTotalStakeAndShare( cy.contains('Loading...', epochTimeout).should('not.exist'); waitForBeginningOfEpoch(); cy.get(`[row-id="${positionOnList}"]`).within(() => { - cy.get(stakeValidatorListTotalStake, epochTimeout).should( + cy.getByTestId(stakeValidatorListTotalStake, epochTimeout).should( 'have.text', expectedTotalStake ); - cy.get(stakeValidatorListTotalShare, epochTimeout).should( + cy.getByTestId(stakeValidatorListTotalShare, epochTimeout).should( 'have.text', expectedTotalShare ); diff --git a/apps/governance/src/i18n/translations/dev.json b/apps/governance/src/i18n/translations/dev.json index 239b02022..7b6c78d00 100644 --- a/apps/governance/src/i18n/translations/dev.json +++ b/apps/governance/src/i18n/translations/dev.json @@ -602,11 +602,14 @@ "noValidators": "No validators", "validator": "Validator", "stake": "Stake", + "myStake": "My stake", "stakeShare": "Stake share", "stakedByOperator": "Staked by operator", "stakedByDelegates": "Staked by delegates", + "stakedByMe": "Staked by me", "totalStake": "Total stake", "pendingStake": "Pending stake", + "myPendingStake": "My pending stake", "totalPenalties": "Total penalties", "noPenaltyDataFromLastEpoch": "No penalty data from last epoch", "stakeNeededForPromotion": "Stake needed for promotion", diff --git a/apps/governance/src/routes/home/index.tsx b/apps/governance/src/routes/home/index.tsx index e69e31712..6f3b2acb1 100644 --- a/apps/governance/src/routes/home/index.tsx +++ b/apps/governance/src/routes/home/index.tsx @@ -12,14 +12,14 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch'; import { ProposalsListItem } from '../proposals/components/proposals-list-item'; import Routes from '../routes'; import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils'; -import { useNodesQuery } from '../staking/home/__generated___/Nodes'; +import { useNodesQuery } from '../staking/home/__generated__/Nodes'; import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals'; import { getNotRejectedProposals } from '../proposals/proposals/proposals-container'; import { Heading } from '../../components/heading'; import * as Schema from '@vegaprotocol/types'; import type { RouteChildProps } from '..'; import type { ProposalFieldsFragment } from '../proposals/proposals/__generated__/Proposals'; -import type { NodesFragmentFragment } from '../staking/home/__generated___/Nodes'; +import type { NodesFragmentFragment } from '../staking/home/__generated__/Nodes'; const nodesToShow = 6; diff --git a/apps/governance/src/routes/rewards/home/rewards-page.tsx b/apps/governance/src/routes/rewards/home/rewards-page.tsx index bc4d04f29..47d84fdec 100644 --- a/apps/governance/src/routes/rewards/home/rewards-page.tsx +++ b/apps/governance/src/routes/rewards/home/rewards-page.tsx @@ -104,7 +104,7 @@ export const RewardsPage = () => { )} -
+

@@ -114,7 +114,7 @@ export const RewardsPage = () => {

-
+
; }>; -export type StakingQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string } } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } }, nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, infoUrl: string, location: string, ethereumAddress: string, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null, rankingScore: { __typename?: 'RankingScore', rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string, status: Types.ValidatorStatus } } } | null> | null }, nodeData?: { __typename?: 'NodeData', stakedTotal: string, totalNodes: number, inactiveNodes: number, uptime: number } | null }; +export type StakingQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string }, party: { __typename?: 'Party', id: string } } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } }, nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, infoUrl: string, location: string, ethereumAddress: string, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null, rankingScore: { __typename?: 'RankingScore', rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string, status: Types.ValidatorStatus } } } | null> | null }, nodeData?: { __typename?: 'NodeData', stakedTotal: string, totalNodes: number, inactiveNodes: number, uptime: number } | null }; export const StakingNodeFieldsFragmentDoc = gql` fragment StakingNodeFields on Node { @@ -39,6 +41,18 @@ export const StakingNodeFieldsFragmentDoc = gql` } } `; +export const StakingDelegationFieldsFragmentDoc = gql` + fragment StakingDelegationFields on Delegation { + amount + epoch + node { + id + } + party { + id + } +} + `; export const StakingDocument = gql` query Staking($partyId: ID!, $delegationsPagination: Pagination) { party(id: $partyId) { @@ -49,11 +63,7 @@ export const StakingDocument = gql` delegationsConnection(pagination: $delegationsPagination) { edges { node { - amount - epoch - node { - id - } + ...StakingDelegationFields } } } @@ -80,7 +90,8 @@ export const StakingDocument = gql` uptime } } - ${StakingNodeFieldsFragmentDoc}`; + ${StakingDelegationFieldsFragmentDoc} +${StakingNodeFieldsFragmentDoc}`; /** * __useStakingQuery__ diff --git a/apps/governance/src/routes/staking/__generated___/PreviousEpoch.ts b/apps/governance/src/routes/staking/__generated___/PreviousEpoch.ts deleted file mode 100644 index 6618cfd89..000000000 --- a/apps/governance/src/routes/staking/__generated___/PreviousEpoch.ts +++ /dev/null @@ -1,106 +0,0 @@ -import * as Types from '@vegaprotocol/types'; - -import { gql } from '@apollo/client'; -import * as Apollo from '@apollo/client'; -const defaultOptions = {} as const; -export type PreviousEpochQueryVariables = Types.Exact<{ - epochId?: Types.InputMaybe; -}>; - -export type PreviousEpochQuery = { - __typename?: 'Query'; - epoch: { - __typename?: 'Epoch'; - id: string; - validatorsConnection?: { - __typename?: 'NodesConnection'; - edges?: Array<{ - __typename?: 'NodeEdge'; - node: { - __typename?: 'Node'; - id: string; - rewardScore?: { - __typename?: 'RewardScore'; - rawValidatorScore: string; - } | null; - rankingScore: { - __typename?: 'RankingScore'; - performanceScore: string; - }; - }; - } | null> | null; - } | null; - }; -}; - -export const PreviousEpochDocument = gql` - query PreviousEpoch($epochId: ID) { - epoch(id: $epochId) { - id - validatorsConnection { - edges { - node { - id - rewardScore { - rawValidatorScore - } - rankingScore { - performanceScore - } - } - } - } - } - } -`; - -/** - * __usePreviousEpochQuery__ - * - * To run a query within a React component, call `usePreviousEpochQuery` and pass it any options that fit your needs. - * When your component renders, `usePreviousEpochQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = usePreviousEpochQuery({ - * variables: { - * epochId: // value for 'epochId' - * }, - * }); - */ -export function usePreviousEpochQuery( - baseOptions?: Apollo.QueryHookOptions< - PreviousEpochQuery, - PreviousEpochQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useQuery( - PreviousEpochDocument, - options - ); -} -export function usePreviousEpochLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions< - PreviousEpochQuery, - PreviousEpochQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useLazyQuery( - PreviousEpochDocument, - options - ); -} -export type PreviousEpochQueryHookResult = ReturnType< - typeof usePreviousEpochQuery ->; -export type PreviousEpochLazyQueryHookResult = ReturnType< - typeof usePreviousEpochLazyQuery ->; -export type PreviousEpochQueryResult = Apollo.QueryResult< - PreviousEpochQuery, - PreviousEpochQueryVariables ->; diff --git a/apps/governance/src/routes/staking/associate/__generated___/PartyStakeLinkings.ts b/apps/governance/src/routes/staking/associate/__generated___/PartyStakeLinkings.ts deleted file mode 100644 index 84460d5f4..000000000 --- a/apps/governance/src/routes/staking/associate/__generated___/PartyStakeLinkings.ts +++ /dev/null @@ -1,114 +0,0 @@ -import * as Types from '@vegaprotocol/types'; - -import { gql } from '@apollo/client'; -import * as Apollo from '@apollo/client'; -const defaultOptions = {} as const; -export type LinkingsFieldsFragment = { - __typename?: 'StakeLinking'; - id: string; - txHash: string; - status: Types.StakeLinkingStatus; -}; - -export type PartyStakeLinkingsQueryVariables = Types.Exact<{ - partyId: Types.Scalars['ID']; -}>; - -export type PartyStakeLinkingsQuery = { - __typename?: 'Query'; - party?: { - __typename?: 'Party'; - id: string; - stakingSummary: { - __typename?: 'StakingSummary'; - linkings: { - __typename?: 'StakesConnection'; - edges?: Array<{ - __typename?: 'StakeLinkingEdge'; - node: { - __typename?: 'StakeLinking'; - id: string; - txHash: string; - status: Types.StakeLinkingStatus; - }; - } | null> | null; - }; - }; - } | null; -}; - -export const LinkingsFieldsFragmentDoc = gql` - fragment LinkingsFields on StakeLinking { - id - txHash - status - } -`; -export const PartyStakeLinkingsDocument = gql` - query PartyStakeLinkings($partyId: ID!) { - party(id: $partyId) { - id - stakingSummary { - linkings { - edges { - node { - ...LinkingsFields - } - } - } - } - } - } - ${LinkingsFieldsFragmentDoc} -`; - -/** - * __usePartyStakeLinkingsQuery__ - * - * To run a query within a React component, call `usePartyStakeLinkingsQuery` and pass it any options that fit your needs. - * When your component renders, `usePartyStakeLinkingsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = usePartyStakeLinkingsQuery({ - * variables: { - * partyId: // value for 'partyId' - * }, - * }); - */ -export function usePartyStakeLinkingsQuery( - baseOptions: Apollo.QueryHookOptions< - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useQuery< - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables - >(PartyStakeLinkingsDocument, options); -} -export function usePartyStakeLinkingsLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions< - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useLazyQuery< - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables - >(PartyStakeLinkingsDocument, options); -} -export type PartyStakeLinkingsQueryHookResult = ReturnType< - typeof usePartyStakeLinkingsQuery ->; -export type PartyStakeLinkingsLazyQueryHookResult = ReturnType< - typeof usePartyStakeLinkingsLazyQuery ->; -export type PartyStakeLinkingsQueryResult = Apollo.QueryResult< - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables ->; diff --git a/apps/governance/src/routes/staking/associate/associate-transaction.tsx b/apps/governance/src/routes/staking/associate/associate-transaction.tsx index d6bd7d62f..167e494c3 100644 --- a/apps/governance/src/routes/staking/associate/associate-transaction.tsx +++ b/apps/governance/src/routes/staking/associate/associate-transaction.tsx @@ -15,7 +15,7 @@ import { } from '../../../hooks/transaction-reducer'; import Routes from '../../routes'; import { truncateMiddle } from '../../../lib/truncate-middle'; -import type { LinkingsFieldsFragment } from './__generated___/PartyStakeLinkings'; +import type { LinkingsFieldsFragment } from './__generated__/PartyStakeLinkings'; export const AssociateTransaction = ({ amount, diff --git a/apps/governance/src/routes/staking/associate/hooks.ts b/apps/governance/src/routes/staking/associate/hooks.ts index 65d967556..394fb7918 100644 --- a/apps/governance/src/routes/staking/associate/hooks.ts +++ b/apps/governance/src/routes/staking/associate/hooks.ts @@ -15,8 +15,8 @@ import type { LinkingsFieldsFragment, PartyStakeLinkingsQuery, PartyStakeLinkingsQueryVariables, -} from './__generated___/PartyStakeLinkings'; -import { PartyStakeLinkingsDocument } from './__generated___/PartyStakeLinkings'; +} from './__generated__/PartyStakeLinkings'; +import { PartyStakeLinkingsDocument } from './__generated__/PartyStakeLinkings'; export const useAddStake = ( address: string, diff --git a/apps/governance/src/routes/staking/home/__generated___/Nodes.ts b/apps/governance/src/routes/staking/home/__generated___/Nodes.ts deleted file mode 100644 index a611de245..000000000 --- a/apps/governance/src/routes/staking/home/__generated___/Nodes.ts +++ /dev/null @@ -1,149 +0,0 @@ -import * as Types from '@vegaprotocol/types'; - -import { gql } from '@apollo/client'; -import * as Apollo from '@apollo/client'; -const defaultOptions = {} as const; -export type NodesFragmentFragment = { - __typename?: 'Node'; - avatarUrl?: string | null; - id: string; - name: string; - pubkey: string; - stakedByOperator: string; - stakedByDelegates: string; - stakedTotal: string; - pendingStake: string; - rankingScore: { - __typename?: 'RankingScore'; - rankingScore: string; - stakeScore: string; - performanceScore: string; - votingPower: string; - status: Types.ValidatorStatus; - }; -}; - -export type NodesQueryVariables = Types.Exact<{ [key: string]: never }>; - -export type NodesQuery = { - __typename?: 'Query'; - epoch: { - __typename?: 'Epoch'; - id: string; - timestamps: { - __typename?: 'EpochTimestamps'; - start?: any | null; - end?: any | null; - expiry?: any | null; - }; - }; - nodesConnection: { - __typename?: 'NodesConnection'; - edges?: Array<{ - __typename?: 'NodeEdge'; - node: { - __typename?: 'Node'; - avatarUrl?: string | null; - id: string; - name: string; - pubkey: string; - stakedByOperator: string; - stakedByDelegates: string; - stakedTotal: string; - pendingStake: string; - rankingScore: { - __typename?: 'RankingScore'; - rankingScore: string; - stakeScore: string; - performanceScore: string; - votingPower: string; - status: Types.ValidatorStatus; - }; - }; - } | null> | null; - }; - nodeData?: { __typename?: 'NodeData'; stakedTotal: string } | null; -}; - -export const NodesFragmentFragmentDoc = gql` - fragment NodesFragment on Node { - avatarUrl - id - name - pubkey - stakedByOperator - stakedByDelegates - stakedTotal - pendingStake - rankingScore { - rankingScore - stakeScore - performanceScore - votingPower - status - } - } -`; -export const NodesDocument = gql` - query Nodes { - epoch { - id - timestamps { - start - end - expiry - } - } - nodesConnection { - edges { - node { - ...NodesFragment - } - } - } - nodeData { - stakedTotal - } - } - ${NodesFragmentFragmentDoc} -`; - -/** - * __useNodesQuery__ - * - * To run a query within a React component, call `useNodesQuery` and pass it any options that fit your needs. - * When your component renders, `useNodesQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useNodesQuery({ - * variables: { - * }, - * }); - */ -export function useNodesQuery( - baseOptions?: Apollo.QueryHookOptions -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useQuery( - NodesDocument, - options - ); -} -export function useNodesLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useLazyQuery( - NodesDocument, - options - ); -} -export type NodesQueryHookResult = ReturnType; -export type NodesLazyQueryHookResult = ReturnType; -export type NodesQueryResult = Apollo.QueryResult< - NodesQuery, - NodesQueryVariables ->; diff --git a/apps/governance/src/routes/staking/home/epoch-data.tsx b/apps/governance/src/routes/staking/home/epoch-data.tsx index 1bd0c47f0..ff6c5f9f0 100644 --- a/apps/governance/src/routes/staking/home/epoch-data.tsx +++ b/apps/governance/src/routes/staking/home/epoch-data.tsx @@ -1,36 +1,53 @@ import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { EpochCountdown } from '../../../components/epoch-countdown'; -import { useNodesQuery } from './__generated___/Nodes'; -import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch'; +import { useNodesQuery } from './__generated__/Nodes'; +import { useStakingQuery } from '../__generated__/Staking'; +import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch'; import { ValidatorTables } from './validator-tables'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; +import { useVegaWallet } from '@vegaprotocol/wallet'; export const EpochData = () => { // errorPolicy due to vegaprotocol/vega issue 5898 - const { data, error, loading, refetch } = useNodesQuery(); + const { pubKey } = useVegaWallet(); + const { + data: nodesData, + error: nodesError, + loading: nodesLoading, + refetch, + } = useNodesQuery(); + const { data: userStakingData } = useStakingQuery({ + variables: { + partyId: pubKey || '', + }, + }); const { data: previousEpochData } = usePreviousEpochQuery({ variables: { - epochId: (Number(data?.epoch.id) - 1).toString(), + epochId: (Number(nodesData?.epoch.id) - 1).toString(), }, - skip: !data?.epoch.id, + skip: !nodesData?.epoch.id, }); - useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch); + useRefreshAfterEpoch(nodesData?.epoch.timestamps.expiry, refetch); return ( - - {data?.epoch && - data.epoch.timestamps.start && - data?.epoch.timestamps.expiry && ( + + {nodesData?.epoch && + nodesData.epoch.timestamps.start && + nodesData?.epoch.timestamps.expiry && (
)} - +
); }; diff --git a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.spec.tsx b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.spec.tsx index e9f291511..5ab20e63a 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.spec.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.spec.tsx @@ -3,14 +3,15 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import { ConsensusValidatorsTable } from './consensus-validators-table'; import { MockedProvider } from '@apollo/client/testing'; import { MemoryRouter } from 'react-router-dom'; -import { NodesDocument } from '../__generated___/Nodes'; -import { PreviousEpochDocument } from '../../__generated___/PreviousEpoch'; +import { NodesDocument } from '../__generated__/Nodes'; +import { PreviousEpochDocument } from '../../__generated__/PreviousEpoch'; import * as Schema from '@vegaprotocol/types'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import type { MockedResponse } from '@apollo/client/testing'; import type { PartialDeep } from 'type-fest'; -import type { NodesFragmentFragment } from '../__generated___/Nodes'; -import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch'; +import type { NodesFragmentFragment } from '../__generated__/Nodes'; +import type { PreviousEpochQuery } from '../../__generated__/PreviousEpoch'; +import type { ValidatorsView } from './validator-tables'; const nodeFactory = ( overrides?: PartialDeep @@ -142,7 +143,8 @@ const MOCK_TOTAL_STAKE = '28832590188747439203824'; const renderValidatorsTable = ( data = MOCK_NODES, - previousEpochData = MOCK_PREVIOUS_EPOCH + previousEpochData = MOCK_PREVIOUS_EPOCH, + validatorsView: ValidatorsView = 'all' ) => { return render( @@ -152,6 +154,7 @@ const renderValidatorsTable = ( data={data} previousEpochData={previousEpochData} totalStake={MOCK_TOTAL_STAKE} + validatorsView={validatorsView} /> diff --git a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx index ed6caebfc..1609f75cf 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx @@ -19,7 +19,9 @@ import { import { defaultColDef, NODE_LIST_GRID_STYLES, + PendingStakeRenderer, stakedTotalPercentage, + StakeShareRenderer, TotalPenaltiesRenderer, TotalStakeRenderer, ValidatorFields, @@ -54,6 +56,9 @@ interface CanonisedConsensusNodeProps { [ValidatorFields.OVERSTAKING_PENALTY]: string; [ValidatorFields.TOTAL_PENALTIES]: string; [ValidatorFields.PENDING_STAKE]: string; + [ValidatorFields.STAKED_BY_USER]: string | undefined; + [ValidatorFields.PENDING_USER_STAKE]: string | undefined; + [ValidatorFields.USER_STAKE_SHARE]: string | undefined; } const getRowHeight = (params: RowHeightParams) => { @@ -61,7 +66,7 @@ const getRowHeight = (params: RowHeightParams) => { // Note: this value will change if the height of the top third cell renderer changes return 138; } - return 52; + return 68; }; const TopThirdCellRenderer = ( @@ -117,6 +122,7 @@ export const ConsensusValidatorsTable = ({ data, previousEpochData, totalStake, + validatorsView, }: ValidatorsTableProps) => { const { t } = useTranslation(); const { @@ -129,7 +135,7 @@ export const ConsensusValidatorsTable = ({ const nodes = useMemo(() => { if (!data || !previousEpochData) return []; - const canonisedNodes = data + let canonisedNodes = data .sort((a, b) => { const aVotingPower = new BigNumber(a.rankingScore.votingPower); const bVotingPower = new BigNumber(b.rankingScore.votingPower); @@ -154,6 +160,9 @@ export const ConsensusValidatorsTable = ({ rankingScore: { stakeScore, votingPower }, pendingStake, votingPowerRanking, + stakedByUser, + pendingUserStake, + userStakeShare, }) => { const { rawValidatorScore, performanceScore } = getLastEpochScoreAndPerformance(previousEpochData, id); @@ -201,12 +210,28 @@ export const ConsensusValidatorsTable = ({ totalStake ), [ValidatorFields.PENDING_STAKE]: pendingStake, - decimals, + [ValidatorFields.STAKED_BY_USER]: stakedByUser + ? formatNumber(toBigNum(stakedByUser, decimals), 2) + : undefined, + [ValidatorFields.PENDING_USER_STAKE]: pendingUserStake, + [ValidatorFields.USER_STAKE_SHARE]: userStakeShare + ? stakedTotalPercentage(userStakeShare) + : undefined, }; } ); - if (canonisedNodes.length < 3 || !hideTopThird) { + if (validatorsView === 'myStake') { + canonisedNodes = canonisedNodes.filter( + (node) => node[ValidatorFields.STAKED_BY_USER] !== undefined + ); + } + + if ( + canonisedNodes.length < 3 || + !hideTopThird || + validatorsView === 'myStake' + ) { return canonisedNodes; } @@ -291,7 +316,14 @@ export const ConsensusValidatorsTable = ({ }, ...remaining, ]; - }, [data, decimals, hideTopThird, previousEpochData, totalStake]); + }, [ + data, + decimals, + hideTopThird, + previousEpochData, + totalStake, + validatorsView, + ]); const ConsensusTable = forwardRef((_, gridRef) => { const colDefs = useMemo( @@ -311,7 +343,7 @@ export const ConsensusValidatorsTable = ({ return a > b ? 1 : -1; }, pinned: 'left', - width: 240, + width: 260, }, { field: ValidatorFields.STAKE, @@ -320,6 +352,20 @@ export const ConsensusValidatorsTable = ({ cellRenderer: TotalStakeRenderer, width: 120, }, + { + field: ValidatorFields.PENDING_STAKE, + headerName: t(ValidatorFields.PENDING_STAKE).toString(), + headerTooltip: t('PendingStakeDescription').toString(), + cellRenderer: PendingStakeRenderer, + width: 120, + }, + { + field: ValidatorFields.STAKE_SHARE, + headerName: t(ValidatorFields.STAKE_SHARE).toString(), + headerTooltip: t('StakeShareDescription').toString(), + cellRenderer: StakeShareRenderer, + width: 120, + }, { field: ValidatorFields.NORMALISED_VOTING_POWER, headerName: t(ValidatorFields.NORMALISED_VOTING_POWER).toString(), @@ -328,12 +374,6 @@ export const ConsensusValidatorsTable = ({ width: 200, sort: 'desc', }, - { - field: ValidatorFields.STAKE_SHARE, - headerName: t(ValidatorFields.STAKE_SHARE).toString(), - headerTooltip: t('StakeShareDescription').toString(), - width: 100, - }, { field: ValidatorFields.TOTAL_PENALTIES, headerName: t(ValidatorFields.TOTAL_PENALTIES).toString(), @@ -341,14 +381,6 @@ export const ConsensusValidatorsTable = ({ cellRenderer: TotalPenaltiesRenderer, width: 120, }, - { - field: ValidatorFields.PENDING_STAKE, - headerName: t(ValidatorFields.PENDING_STAKE).toString(), - headerTooltip: t('PendingStakeDescription').toString(), - valueFormatter: ({ value }) => - formatNumber(toBigNum(value, decimals), 2), - width: 110, - }, ], [] ); diff --git a/apps/governance/src/routes/staking/home/validator-tables/shared.spec.tsx b/apps/governance/src/routes/staking/home/validator-tables/shared.spec.tsx deleted file mode 100644 index 791da185c..000000000 --- a/apps/governance/src/routes/staking/home/validator-tables/shared.spec.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { - getLastEpochScoreAndPerformance, - getTotalPenalties, -} from '../../shared'; -import { stakedTotalPercentage } from './shared'; - -const MOCK_PREVIOUS_EPOCH = { - epoch: { - id: '1', - validatorsConnection: { - edges: [ - { - node: { - id: '0x123', - rewardScore: { - rawValidatorScore: '0.25', - }, - rankingScore: { - performanceScore: '0.75', - }, - }, - }, - ], - }, - }, -}; - -describe('stakedTotalPercentage', () => { - it('should return the correct percentage as a string, 2dp', () => { - expect(stakedTotalPercentage('1.2345')).toBe('123.45%'); - }); -}); - -describe('totalPenalties', () => { - it('should return the correct penalty based on arbitrary values, test 1', () => { - expect( - getTotalPenalties( - getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123') - .rawValidatorScore, - '0.1', - '5000', - '100000' - ) - ).toBe('50.00%'); - }); - - it('should return the correct penalty based on lower performance score than first test', () => { - expect( - getTotalPenalties( - getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123') - .rawValidatorScore, - '0.05', - '5000', - '100000' - ) - ).toBe('75.00%'); - }); - - it('should return the correct penalty based on higher amount of stake than other tests (great penalty due to anti-whaling)', () => { - expect( - getTotalPenalties( - getLastEpochScoreAndPerformance(MOCK_PREVIOUS_EPOCH, '0x123') - .rawValidatorScore, - '0.1', - '5000', - '5500' - ) - ).toBe('97.25%'); - }); -}); diff --git a/apps/governance/src/routes/staking/home/validator-tables/shared.tsx b/apps/governance/src/routes/staking/home/validator-tables/shared.tsx index 49086e297..e666eaf15 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/shared.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/shared.tsx @@ -10,9 +10,12 @@ import { Tooltip, TooltipCellComponent, } from '@vegaprotocol/ui-toolkit'; -import type { NodesFragmentFragment } from '../__generated___/Nodes'; -import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch'; +import { BigNumber } from '../../../../lib/bignumber'; +import type { NodesFragmentFragment } from '../__generated__/Nodes'; +import type { PreviousEpochQuery } from '../../__generated__/PreviousEpoch'; import { useAppState } from '../../../../contexts/app-state/app-state-context'; +import type { StakingDelegationFieldsFragment } from '../../__generated__/Staking'; +import type { ValidatorsView } from './validator-tables'; export enum ValidatorFields { RANKING_INDEX = 'rankingIndex', @@ -31,12 +34,49 @@ export enum ValidatorFields { PERFORMANCE_PENALTY = 'performancePenalty', OVERSTAKED_AMOUNT = 'overstakedAmount', OVERSTAKING_PENALTY = 'overstakingPenalty', + // the following are additional fields added to the validator object displaying user data + STAKED_BY_USER = 'stakedByUser', + PENDING_USER_STAKE = 'pendingUserStake', + USER_STAKE_SHARE = 'userStakeShare', } +export const addUserDataToValidator = ( + validator: NodesFragmentFragment, + currentEpochUserStaking: StakingDelegationFieldsFragment | undefined, + nextEpochUserStaking: StakingDelegationFieldsFragment | undefined, + currentUserStakeAvailable: string +) => { + return { + ...validator, + [ValidatorFields.STAKED_BY_USER]: + currentEpochUserStaking && Number(currentEpochUserStaking?.amount) > 0 + ? currentEpochUserStaking.amount + : undefined, + [ValidatorFields.PENDING_USER_STAKE]: nextEpochUserStaking + ? new BigNumber(nextEpochUserStaking?.amount) + .minus(new BigNumber(currentEpochUserStaking?.amount || 0)) + .toString() + : undefined, + [ValidatorFields.USER_STAKE_SHARE]: + currentEpochUserStaking && Number(currentEpochUserStaking.amount) > 0 + ? new BigNumber(currentEpochUserStaking.amount).dividedBy( + new BigNumber(currentUserStakeAvailable) + ) + : undefined, + }; +}; + +export type ValidatorWithUserData = NodesFragmentFragment & { + stakedByUser?: string; + pendingUserStake?: string; + userStakeShare?: string; +}; + export interface ValidatorsTableProps { - data: NodesFragmentFragment[] | undefined; + data: ValidatorWithUserData[] | undefined; previousEpochData: PreviousEpochQuery | undefined; totalStake: string; + validatorsView: ValidatorsView; } // Custom styling to account for the scrollbar. This is needed because the @@ -58,19 +98,23 @@ export const defaultColDef = { resizable: true, autoHeight: true, comparator: (a: string, b: string) => parseFloat(a) - parseFloat(b), - cellStyle: { margin: '10px 0', padding: '0 12px' }, tooltipComponent: TooltipCellComponent, + cellStyle: { display: 'flex', alignItems: 'center', padding: '0 10px' }, }; interface ValidatorRendererProps { - data: { id: string; validator: { avatarUrl: string; name: string } }; + data: { + id: string; + validator: { avatarUrl: string; name: string }; + stakedByUser: string | undefined; + }; } export const ValidatorRenderer = ({ data }: ValidatorRendererProps) => { const { t } = useTranslation(); const { avatarUrl, name } = data.validator; return ( -
+
{avatarUrl && ( { {name} - + {data.stakedByUser ? ( + + ) : ( + + )}
); @@ -102,8 +156,14 @@ export const StakeNeededForPromotionRenderer = ({ data, }: StakeNeededForPromotionRendererProps) => { return ( - - + + {data.stakeNeededForPromotionDescription} + + } + > + {data.stakeNeededForPromotion && formatNumber(data.stakeNeededForPromotion, 2)} @@ -134,7 +194,59 @@ export const VotingPowerRenderer = ({ data }: VotingPowerRendererProps) => { } > - {data.normalisedVotingPower} + + {data.normalisedVotingPower} + + + ); +}; + +interface PendingStakeRendererProps { + data: { + pendingStake: string; + pendingUserStake: string | undefined; + }; +} + +export const PendingStakeRenderer = ({ data }: PendingStakeRendererProps) => { + const { t } = useTranslation(); + const { + appState: { decimals }, + } = useAppState(); + + return ( + +
+ {t('pendingStake')}:{' '} + {formatNumber(toBigNum(data.pendingStake, decimals), decimals)} +
+ {data.pendingUserStake && ( +
+ {t('myPendingStake')}:{' '} + {formatNumber( + toBigNum(data.pendingUserStake, decimals), + decimals + )} +
+ )} + + } + > +
+ {data.pendingUserStake && data.pendingStake !== '0' && ( + + {formatNumber(toBigNum(data.pendingUserStake, decimals), 2)} + + )} + + {formatNumber(toBigNum(data.pendingStake, decimals), 2)} + +
); }; @@ -144,6 +256,7 @@ interface TotalStakeRendererProps { stake: string; stakedByDelegates: string; stakedByOperator: string; + stakedByUser: string | undefined; }; } @@ -165,18 +278,52 @@ export const TotalStakeRenderer = ({ data }: TotalStakeRendererProps) => {
{t('stakedByDelegates')}: {data.stakedByDelegates.toString()}
-
- {t('totalStake')}:{' '} - {formattedStake} +
+ {t('totalStake')}: {formattedStake}
+ {data.stakedByUser && ( +
+ {t('stakedByMe')}: {data.stakedByUser} +
+ )} } > - {formattedStake} +
+ {data.stakedByUser && ( + + {data.stakedByUser} + + )} + {formattedStake} +
); }; +interface StakeShareRendererProps { + data: { + stakeShare: string; + userStakeShare: string | undefined; + }; +} + +export const StakeShareRenderer = ({ data }: StakeShareRendererProps) => { + return ( +
+ {data.userStakeShare && ( + + {data.userStakeShare} + + )} + {data.stakeShare} +
+ ); +}; + interface TotalPenaltiesRendererProps { data: { performanceScore: string; @@ -209,7 +356,7 @@ export const TotalPenaltiesRenderer = ({ } > - {data.totalPenalties} + {data.totalPenalties} ); }; diff --git a/apps/governance/src/routes/staking/home/validator-tables/standby-pending-validators-table.tsx b/apps/governance/src/routes/staking/home/validator-tables/standby-pending-validators-table.tsx index b93d4b543..2a49c36e8 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/standby-pending-validators-table.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/standby-pending-validators-table.tsx @@ -21,6 +21,8 @@ import { ValidatorRenderer, TotalPenaltiesRenderer, TotalStakeRenderer, + StakeShareRenderer, + PendingStakeRenderer, } from './shared'; import type { AgGridReact } from 'ag-grid-react'; import type { ColDef } from 'ag-grid-community'; @@ -38,6 +40,7 @@ export const StandbyPendingValidatorsTable = ({ totalStake, stakeNeededForPromotion, stakeNeededForPromotionDescription, + validatorsView, }: StandbyPendingValidatorsTableProps) => { const { t } = useTranslation(); const { @@ -47,7 +50,7 @@ export const StandbyPendingValidatorsTable = ({ const gridRef = useRef(null); - const nodes = useMemo(() => { + let nodes = useMemo(() => { if (!data) return []; return data @@ -75,6 +78,9 @@ export const StandbyPendingValidatorsTable = ({ rankingScore: { stakeScore }, pendingStake, votingPowerRanking, + stakedByUser, + pendingUserStake, + userStakeShare, }) => { const { rawValidatorScore, performanceScore } = getLastEpochScoreAndPerformance(previousEpochData, id); @@ -152,6 +158,13 @@ export const StandbyPendingValidatorsTable = ({ totalStake ), [ValidatorFields.PENDING_STAKE]: pendingStake, + [ValidatorFields.STAKED_BY_USER]: stakedByUser + ? formatNumber(toBigNum(stakedByUser, decimals), 2) + : undefined, + [ValidatorFields.PENDING_USER_STAKE]: pendingUserStake, + [ValidatorFields.USER_STAKE_SHARE]: userStakeShare + ? stakedTotalPercentage(userStakeShare) + : undefined, }; } ); @@ -165,6 +178,12 @@ export const StandbyPendingValidatorsTable = ({ totalStake, ]); + if (validatorsView === 'myStake') { + nodes = nodes.filter( + (node) => node[ValidatorFields.STAKED_BY_USER] !== undefined + ); + } + const StandbyPendingTable = forwardRef((_, gridRef) => { const colDefs = useMemo( () => [ @@ -180,7 +199,7 @@ export const StandbyPendingValidatorsTable = ({ cellRenderer: ValidatorRenderer, comparator: ({ name: a }, { name: b }) => Math.sign(a - b), pinned: 'left', - width: 240, + width: 260, }, { field: ValidatorFields.STAKE, @@ -189,6 +208,20 @@ export const StandbyPendingValidatorsTable = ({ cellRenderer: TotalStakeRenderer, width: 120, }, + { + field: ValidatorFields.PENDING_STAKE, + headerName: t(ValidatorFields.PENDING_STAKE).toString(), + headerTooltip: t('PendingStakeDescription').toString(), + cellRenderer: PendingStakeRenderer, + width: 120, + }, + { + field: ValidatorFields.STAKE_SHARE, + headerName: t(ValidatorFields.STAKE_SHARE).toString(), + headerTooltip: t('StakeShareDescription').toString(), + cellRenderer: StakeShareRenderer, + width: 100, + }, { field: ValidatorFields.STAKE_NEEDED_FOR_PROMOTION, headerName: t(ValidatorFields.STAKE_NEEDED_FOR_PROMOTION).toString(), @@ -199,12 +232,6 @@ export const StandbyPendingValidatorsTable = ({ width: 210, sort: 'asc', }, - { - field: ValidatorFields.STAKE_SHARE, - headerName: t(ValidatorFields.STAKE_SHARE).toString(), - headerTooltip: t('StakeShareDescription').toString(), - width: 100, - }, { field: ValidatorFields.TOTAL_PENALTIES, headerName: t(ValidatorFields.TOTAL_PENALTIES).toString(), @@ -212,14 +239,6 @@ export const StandbyPendingValidatorsTable = ({ cellRenderer: TotalPenaltiesRenderer, width: 120, }, - { - field: ValidatorFields.PENDING_STAKE, - headerName: t(ValidatorFields.PENDING_STAKE).toString(), - headerTooltip: t('PendingStakeDescription').toString(), - valueFormatter: ({ value }) => - formatNumber(toBigNum(value, decimals), 2), - width: 110, - }, ], [] ); @@ -230,7 +249,7 @@ export const StandbyPendingValidatorsTable = ({ domLayout="autoHeight" style={{ width: '100%' }} customThemeParams={NODE_LIST_GRID_STYLES} - rowHeight={52} + rowHeight={68} defaultColDef={defaultColDef} tooltipShowDelay={0} animateRows={true} diff --git a/apps/governance/src/routes/staking/home/validator-tables/validator-tables.tsx b/apps/governance/src/routes/staking/home/validator-tables/validator-tables.tsx index df5523311..1d7cf9e97 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/validator-tables.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/validator-tables.tsx @@ -1,27 +1,30 @@ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { ConsensusValidatorsTable } from './consensus-validators-table'; -import { StandbyPendingValidatorsTable } from './standby-pending-validators-table'; +import BigNumber from 'bignumber.js'; import * as Schema from '@vegaprotocol/types'; -import { formatNumber } from '../../../../lib/format-number'; import { createDocsLinks, removePaginationWrapper, toBigNum, } from '@vegaprotocol/utils'; -import { Link as UTLink } from '@vegaprotocol/ui-toolkit'; -import { SubHeading } from '../../../../components/heading'; import { useEnvironment } from '@vegaprotocol/environment'; +import { Link as UTLink, Toggle } from '@vegaprotocol/ui-toolkit'; + +import { formatNumber } from '../../../../lib/format-number'; +import { SubHeading } from '../../../../components/heading'; import { useAppState } from '../../../../contexts/app-state/app-state-context'; -import type { - NodesQuery, - NodesFragmentFragment, -} from '../__generated___/Nodes'; -import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch'; -import BigNumber from 'bignumber.js'; +import { addUserDataToValidator } from './shared'; +import { ConsensusValidatorsTable } from './consensus-validators-table'; +import { StandbyPendingValidatorsTable } from './standby-pending-validators-table'; +import type { NodesQuery, NodesFragmentFragment } from '../__generated__/Nodes'; +import type { PreviousEpochQuery } from '../../__generated__/PreviousEpoch'; +import type { StakingQuery } from '../../__generated__/Staking'; +import type { StakingDelegationFieldsFragment } from '../../__generated__/Staking'; +import type { ValidatorWithUserData } from './shared'; export interface ValidatorsTableProps { - data: NodesQuery | undefined; + nodesData: NodesQuery | undefined; + userStakingData: StakingQuery | undefined; previousEpochData: PreviousEpochQuery | undefined; } @@ -31,8 +34,11 @@ interface SortedValidatorsProps { pendingValidators: NodesFragmentFragment[]; } +export type ValidatorsView = 'all' | 'myStake'; + export const ValidatorTables = ({ - data, + nodesData, + userStakingData, previousEpochData, }: ValidatorsTableProps) => { const { t } = useTranslation(); @@ -40,25 +46,69 @@ export const ValidatorTables = ({ const { appState: { decimals }, } = useAppState(); + + const [validatorsView, setValidatorsView] = useState('all'); const totalStake = useMemo( - () => data?.nodeData?.stakedTotal || '0', - [data?.nodeData?.stakedTotal] + () => nodesData?.nodeData?.stakedTotal || '0', + [nodesData?.nodeData?.stakedTotal] ); + const epochId = useMemo(() => nodesData?.epoch.id, [nodesData?.epoch.id]); + const currentUserStakeAvailable = useMemo( + () => userStakingData?.party?.stakingSummary.currentStakeAvailable || '0', + [userStakingData?.party?.stakingSummary.currentStakeAvailable] + ); + let stakeNeededForPromotion = undefined; + let delegations: StakingDelegationFieldsFragment[] | undefined = undefined; + + if (userStakingData) { + delegations = removePaginationWrapper( + userStakingData?.party?.delegationsConnection?.edges + ); + } const { consensusValidators, standbyValidators, pendingValidators } = useMemo( () => - removePaginationWrapper(data?.nodesConnection.edges).reduce( + removePaginationWrapper(nodesData?.nodesConnection.edges).reduce( (acc: SortedValidatorsProps, validator) => { + const validatorId = validator.id; + const currentDelegation = delegations?.find( + (d) => d.node.id === validatorId && d.epoch === Number(epochId) + ); + const nextDelegation = delegations?.find( + (d) => d.node.id === validatorId && d.epoch === Number(epochId) + 1 + ); + switch (validator.rankingScore?.status) { case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT: - acc.consensusValidators.push(validator); + acc.consensusValidators.push( + addUserDataToValidator( + validator, + currentDelegation, + nextDelegation, + currentUserStakeAvailable + ) + ); break; case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ: - acc.standbyValidators.push(validator); + acc.standbyValidators.push( + addUserDataToValidator( + validator, + currentDelegation, + nextDelegation, + currentUserStakeAvailable + ) + ); break; case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_PENDING: - acc.pendingValidators.push(validator); + acc.pendingValidators.push( + addUserDataToValidator( + validator, + currentDelegation, + nextDelegation, + currentUserStakeAvailable + ) + ); } return acc; }, @@ -68,7 +118,12 @@ export const ValidatorTables = ({ pendingValidators: [], } ), - [data?.nodesConnection.edges] + [ + currentUserStakeAvailable, + delegations, + epochId, + nodesData?.nodesConnection.edges, + ] ); if ( @@ -76,7 +131,7 @@ export const ValidatorTables = ({ (standbyValidators.length || pendingValidators.length) ) { const lowestRankingConsensusScore = consensusValidators.reduce( - (lowest: NodesFragmentFragment, validator: NodesFragmentFragment) => { + (lowest: ValidatorWithUserData, validator: ValidatorWithUserData) => { if ( Number(validator.rankingScore.rankingScore) < Number(lowest.rankingScore.rankingScore) @@ -98,19 +153,42 @@ export const ValidatorTables = ({ ).toString(); } return ( -
+
+
+
+ + setValidatorsView(e.target.value as ValidatorsView) + } + /> +
+
+ {consensusValidators.length > 0 && ( - <> +
- +
)} {standbyValidators.length > 0 && ( - <> +

- +

)} {pendingValidators.length > 0 && ( <> @@ -155,9 +234,10 @@ export const ValidatorTables = ({ totalStake={totalStake} stakeNeededForPromotion={stakeNeededForPromotion} stakeNeededForPromotionDescription="StakeNeededForPromotionCandidateDescription" + validatorsView={validatorsView} /> )} -
+
); }; diff --git a/apps/governance/src/routes/staking/node/node.tsx b/apps/governance/src/routes/staking/node/node.tsx index 888f9ab12..b466c7c5b 100644 --- a/apps/governance/src/routes/staking/node/node.tsx +++ b/apps/governance/src/routes/staking/node/node.tsx @@ -20,8 +20,8 @@ import NodeContainer from './nodes-container'; import { useAppState } from '../../../contexts/app-state/app-state-context'; import { Heading, SubHeading } from '../../../components/heading'; import Routes from '../../routes'; -import type { StakingQuery } from './__generated__/Staking'; -import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch'; +import type { StakingQuery } from '../__generated__/Staking'; +import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; interface StakingNodeProps { data?: StakingQuery; @@ -116,13 +116,6 @@ export const StakingNode = ({ data, previousEpochData }: StakingNodeProps) => { t('validatorTitle', { nodeName: t('validatorTitleFallback') }) } /> -
- -
{data?.epoch.timestamps.start && data?.epoch.timestamps.expiry && (
{ )} +
+ +
); }; diff --git a/apps/governance/src/routes/staking/node/nodes-container.tsx b/apps/governance/src/routes/staking/node/nodes-container.tsx index de2d8f85a..50706a9e7 100644 --- a/apps/governance/src/routes/staking/node/nodes-container.tsx +++ b/apps/governance/src/routes/staking/node/nodes-container.tsx @@ -4,11 +4,11 @@ import { useVegaWallet } from '@vegaprotocol/wallet'; import { useTranslation } from 'react-i18next'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { SplashLoader } from '../../../components/splash-loader'; -import { useStakingQuery } from './__generated__/Staking'; -import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch'; +import { useStakingQuery } from '../__generated__/Staking'; +import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch'; import type { ReactElement } from 'react'; -import type { StakingQuery } from './__generated__/Staking'; -import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch'; +import type { StakingQuery } from '../__generated__/Staking'; +import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; // TODO should only request a single node. When migrating from deprecated APIs we should address this. diff --git a/apps/governance/src/routes/staking/node/validator-table.tsx b/apps/governance/src/routes/staking/node/validator-table.tsx index e47deae89..a2ae3632f 100644 --- a/apps/governance/src/routes/staking/node/validator-table.tsx +++ b/apps/governance/src/routes/staking/node/validator-table.tsx @@ -28,8 +28,8 @@ import { getStakePercentage, } from '../shared'; import type { ReactNode } from 'react'; -import type { StakingNodeFieldsFragment } from './__generated__/Staking'; -import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch'; +import type { StakingNodeFieldsFragment } from '../__generated__/Staking'; +import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; const statuses = { [Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz', @@ -111,7 +111,7 @@ export const ValidatorTable = ({
- + {t('id')} @@ -156,7 +156,7 @@ export const ValidatorTable = ({
- + {t('VEGA ADDRESS / PUBLIC KEY')} @@ -187,7 +187,7 @@ export const ValidatorTable = ({ - + {t('STAKED BY OPERATOR')} @@ -238,7 +238,7 @@ export const ValidatorTable = ({ - + {t('OVERSTAKED PENALTY')} @@ -270,7 +270,7 @@ export const ValidatorTable = ({ - + {t('UNNORMALISED VOTING POWER')} diff --git a/apps/governance/src/routes/staking/node/your-stake.tsx b/apps/governance/src/routes/staking/node/your-stake.tsx index acce75d62..6c5126f99 100644 --- a/apps/governance/src/routes/staking/node/your-stake.tsx +++ b/apps/governance/src/routes/staking/node/your-stake.tsx @@ -23,7 +23,7 @@ export const YourStake = ({ return (
- + {t('Your Stake On Node (This Epoch)')} diff --git a/apps/governance/src/routes/staking/shared.ts b/apps/governance/src/routes/staking/shared.ts index 520af5798..bbecce72c 100644 --- a/apps/governance/src/routes/staking/shared.ts +++ b/apps/governance/src/routes/staking/shared.ts @@ -2,7 +2,7 @@ import { formatNumberPercentage, removePaginationWrapper, } from '@vegaprotocol/utils'; -import type { PreviousEpochQuery } from './__generated___/PreviousEpoch'; +import type { PreviousEpochQuery } from './__generated__/PreviousEpoch'; import { BigNumber } from '../../lib/bignumber'; export const getLastEpochScoreAndPerformance = ( diff --git a/libs/types/.env b/libs/types/.env index 6c26508ad..0c3680567 100644 --- a/libs/types/.env +++ b/libs/types/.env @@ -1 +1 @@ -GRAPHQL_SCHEMA_PATH=https://api.n06.testnet.vega.xyz/graphql +GRAPHQL_SCHEMA_PATH=https://api.n07.testnet.vega.xyz/graphql diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index f96271a8c..15a225c0b 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -88,6 +88,7 @@ export const DepositStatusMapping: { export const IntervalMapping: { [T in Interval]: string; } = { + // @ts-ignore - temporarily suppressing this as it's a valid value INTERVAL_BLOCK: '1 block', INTERVAL_I15M: 'I15M', INTERVAL_I1D: 'I1D',