From 927b2a86d1abfd0288cee3066ac76dd5ef81d1da Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Mon, 7 Nov 2022 17:46:35 +0000 Subject: [PATCH] Feat/1924: Reward details API queries refactor (#1966) * Feat/1924: Reward details API queries refactor. Also now shows rewards of any asset type * Lint fix --- .../src/routes/rewards/home/Rewards.graphql | 61 +++--- .../rewards/home/__generated__/Rewards.ts | 174 ------------------ .../rewards/home/__generated___/Rewards.ts | 73 +++++--- apps/token/src/routes/rewards/home/index.tsx | 169 +---------------- .../src/routes/rewards/home/reward-info.tsx | 68 +++---- .../src/routes/rewards/home/rewards-page.tsx | 116 ++++++++++++ apps/token/src/routes/rewards/index.tsx | 4 +- 7 files changed, 224 insertions(+), 441 deletions(-) delete mode 100644 apps/token/src/routes/rewards/home/__generated__/Rewards.ts create mode 100644 apps/token/src/routes/rewards/home/rewards-page.tsx diff --git a/apps/token/src/routes/rewards/home/Rewards.graphql b/apps/token/src/routes/rewards/home/Rewards.graphql index c847f6573..e9e842e24 100644 --- a/apps/token/src/routes/rewards/home/Rewards.graphql +++ b/apps/token/src/routes/rewards/home/Rewards.graphql @@ -1,34 +1,43 @@ +fragment RewardFields on Reward { + rewardType + asset { + id + symbol + } + party { + id + } + epoch { + id + } + amount + amountFormatted @client + percentageOfTotal + receivedAt +} + +fragment DelegationFields on Delegation { + amount + amountFormatted @client + epoch +} + query Rewards($partyId: ID!) { party(id: $partyId) { id - rewardDetails { - asset { - id - symbol + rewardsConnection { + edges { + node { + ...RewardFields + } } - rewards { - rewardType - asset { - id - } - party { - id - } - epoch { - id - } - amount - amountFormatted @client - percentageOfTotal - receivedAt - } - totalAmount - totalAmountFormatted @client } - delegations { - amount - amountFormatted @client - epoch + delegationsConnection { + edges { + node { + ...DelegationFields + } + } } } epoch { diff --git a/apps/token/src/routes/rewards/home/__generated__/Rewards.ts b/apps/token/src/routes/rewards/home/__generated__/Rewards.ts deleted file mode 100644 index cb7e5c797..000000000 --- a/apps/token/src/routes/rewards/home/__generated__/Rewards.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -import { AccountType } from "@vegaprotocol/types"; - -// ==================================================== -// GraphQL query operation: Rewards -// ==================================================== - -export interface Rewards_party_rewardDetails_asset { - __typename: "Asset"; - /** - * The ID of the asset - */ - id: string; - /** - * The symbol of the asset (e.g: GBP) - */ - symbol: string; -} - -export interface Rewards_party_rewardDetails_rewards_asset { - __typename: "Asset"; - /** - * The ID of the asset - */ - id: string; -} - -export interface Rewards_party_rewardDetails_rewards_party { - __typename: "Party"; - /** - * Party identifier - */ - id: string; -} - -export interface Rewards_party_rewardDetails_rewards_epoch { - __typename: "Epoch"; - /** - * Numeric sequence number used to identify the epoch - */ - id: string; -} - -export interface Rewards_party_rewardDetails_rewards { - __typename: "Reward"; - /** - * The type of reward - */ - rewardType: AccountType; - /** - * The asset this reward is paid in - */ - asset: Rewards_party_rewardDetails_rewards_asset; - /** - * Party receiving the reward - */ - party: Rewards_party_rewardDetails_rewards_party; - /** - * Epoch for which this reward was distributed - */ - epoch: Rewards_party_rewardDetails_rewards_epoch; - /** - * Amount received for this reward - */ - amount: string; - /** - * The amount field formatted by the client - */ - amountFormatted: string; - /** - * Percentage out of the total distributed reward - */ - percentageOfTotal: string; - /** - * Time at which the rewards was received - */ - receivedAt: string; -} - -export interface Rewards_party_rewardDetails { - __typename: "RewardPerAssetDetail"; - /** - * Asset in which the reward was paid - */ - asset: Rewards_party_rewardDetails_asset; - /** - * A list of rewards received for this asset - */ - rewards: (Rewards_party_rewardDetails_rewards | null)[] | null; - /** - * The total amount of rewards received for this asset. - */ - totalAmount: string; - /** - * The total amount field formatted by the client - */ - totalAmountFormatted: string; -} - -export interface Rewards_party_delegations { - __typename: "Delegation"; - /** - * Amount delegated - */ - amount: string; - /** - * The amount field formatted by the client - */ - amountFormatted: string; - /** - * Epoch of delegation - */ - epoch: number; -} - -export interface Rewards_party { - __typename: "Party"; - /** - * Party identifier - */ - id: string; - /** - * Return reward information - */ - rewardDetails: (Rewards_party_rewardDetails | null)[] | null; - delegations: Rewards_party_delegations[] | null; -} - -export interface Rewards_epoch_timestamps { - __typename: "EpochTimestamps"; - /** - * RFC3339 timestamp - Vega time of epoch start, null if not started - */ - start: string | null; - /** - * RFC3339 timestamp - Vega time of epoch end, null if not ended - */ - end: string | null; - /** - * RFC3339 timestamp - Vega time of epoch expiry - */ - expiry: string | null; -} - -export interface Rewards_epoch { - __typename: "Epoch"; - /** - * Numeric sequence number used to identify the epoch - */ - id: string; - /** - * Timestamps for start and end of epochs - */ - timestamps: Rewards_epoch_timestamps; -} - -export interface Rewards { - /** - * An entity that is trading on the Vega network - */ - party: Rewards_party | null; - /** - * Get data for a specific epoch, if ID omitted it gets the current epoch. If the string is 'next', fetch the next epoch - */ - epoch: Rewards_epoch; -} - -export interface RewardsVariables { - partyId: string; -} diff --git a/apps/token/src/routes/rewards/home/__generated___/Rewards.ts b/apps/token/src/routes/rewards/home/__generated___/Rewards.ts index 49ca05f70..08edba4a6 100644 --- a/apps/token/src/routes/rewards/home/__generated___/Rewards.ts +++ b/apps/token/src/routes/rewards/home/__generated___/Rewards.ts @@ -3,46 +3,60 @@ import { Schema as Types } from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; +export type RewardFieldsFragment = { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, amountFormatted: string, percentageOfTotal: string, receivedAt: string, asset: { __typename?: 'Asset', id: string, symbol: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } }; + +export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: string, amountFormatted: string, epoch: number }; + export type RewardsQueryVariables = Types.Exact<{ partyId: Types.Scalars['ID']; }>; -export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardDetails?: Array<{ __typename?: 'RewardPerAssetDetail', totalAmount: string, totalAmountFormatted: string, asset: { __typename?: 'Asset', id: string, symbol: string }, rewards?: Array<{ __typename?: 'Reward', rewardType: Types.AccountType, amount: string, amountFormatted: string, percentageOfTotal: string, receivedAt: string, asset: { __typename?: 'Asset', id: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } | null> | null } | null> | null, delegations?: Array<{ __typename?: 'Delegation', amount: string, amountFormatted: string, epoch: number }> | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: string | null, end?: string | null, expiry?: string | null } } }; - +export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, amountFormatted: string, percentageOfTotal: string, receivedAt: string, asset: { __typename?: 'Asset', id: string, symbol: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, amountFormatted: string, epoch: number } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: string | null, end?: string | null, expiry?: string | null } } }; +export const RewardFieldsFragmentDoc = gql` + fragment RewardFields on Reward { + rewardType + asset { + id + symbol + } + party { + id + } + epoch { + id + } + amount + amountFormatted @client + percentageOfTotal + receivedAt +} + `; +export const DelegationFieldsFragmentDoc = gql` + fragment DelegationFields on Delegation { + amount + amountFormatted @client + epoch +} + `; export const RewardsDocument = gql` query Rewards($partyId: ID!) { party(id: $partyId) { id - rewardDetails { - asset { - id - symbol + rewardsConnection { + edges { + node { + ...RewardFields + } } - rewards { - rewardType - asset { - id - } - party { - id - } - epoch { - id - } - amount - amountFormatted @client - percentageOfTotal - receivedAt - } - totalAmount - totalAmountFormatted @client } - delegations { - amount - amountFormatted @client - epoch + delegationsConnection { + edges { + node { + ...DelegationFields + } + } } } epoch { @@ -54,7 +68,8 @@ export const RewardsDocument = gql` } } } - `; + ${RewardFieldsFragmentDoc} +${DelegationFieldsFragmentDoc}`; /** * __useRewardsQuery__ diff --git a/apps/token/src/routes/rewards/home/index.tsx b/apps/token/src/routes/rewards/home/index.tsx index 9e0ca2408..4816ee343 100644 --- a/apps/token/src/routes/rewards/home/index.tsx +++ b/apps/token/src/routes/rewards/home/index.tsx @@ -1,168 +1 @@ -import { useQuery } from '@apollo/client'; -import { Button, Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; -import { formatDistance } from 'date-fns'; -// @ts-ignore No types available for duration-js -import Duration from 'duration-js'; -import gql from 'graphql-tag'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -import { EpochCountdown } from '../../../components/epoch-countdown'; -import { Heading } from '../../../components/heading'; -import { SplashLoader } from '../../../components/splash-loader'; -import { - AppStateActionType, - useAppState, -} from '../../../contexts/app-state/app-state-context'; -import type { Rewards } from './__generated__/Rewards'; -import { RewardInfo } from './reward-info'; -import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; -import { useNetworkParams, NetworkParams } from '@vegaprotocol/react-helpers'; - -export const REWARDS_QUERY = gql` - query Rewards($partyId: ID!) { - party(id: $partyId) { - id - rewardDetails { - asset { - id - symbol - } - rewards { - rewardType - asset { - id - } - party { - id - } - epoch { - id - } - amount - amountFormatted @client - percentageOfTotal - receivedAt - } - totalAmount - totalAmountFormatted @client - } - delegations { - amount - amountFormatted @client - epoch - } - } - epoch { - id - timestamps { - start - end - expiry - } - } - } -`; - -export const RewardsIndex = () => { - const { t } = useTranslation(); - const { pubKey, pubKeys } = useVegaWallet(); - const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ - openVegaWalletDialog: store.openVegaWalletDialog, - })); - const { appDispatch } = useAppState(); - const { data, loading, error } = useQuery(REWARDS_QUERY, { - variables: { partyId: pubKey }, - skip: !pubKey, - }); - const { params } = useNetworkParams([ - NetworkParams.reward_asset, - NetworkParams.reward_staking_delegation_payoutDelay, - ]); - - const payoutDuration = React.useMemo(() => { - if (!params) { - return 0; - } - return new Duration( - params.reward_staking_delegation_payoutDelay - ).milliseconds(); - }, [params]); - - if (error) { - return ( -
-

{t('Something went wrong')}

- {error &&
{error.message}
} -
- ); - } - - if (loading || !params) { - return ( - - - - ); - } - - return ( -
- -

{t('rewardsPara1')}

-

{t('rewardsPara2')}

- {payoutDuration ? ( -
- -

{t('rewardsPara3')}

-
-
- ) : null} - {!loading && - data && - !error && - data.epoch.timestamps.start && - data.epoch.timestamps.expiry && ( -
- -
- )} -
- {pubKey && pubKeys?.length ? ( - - ) : ( -
- -
- )} -
-
- ); -}; +export * from './rewards-page'; diff --git a/apps/token/src/routes/rewards/home/reward-info.tsx b/apps/token/src/routes/rewards/home/reward-info.tsx index 640fb6cb8..14abd0b2a 100644 --- a/apps/token/src/routes/rewards/home/reward-info.tsx +++ b/apps/token/src/routes/rewards/home/reward-info.tsx @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/react'; +import compact from 'lodash/compact'; import { format } from 'date-fns'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,69 +7,53 @@ import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { BigNumber } from '../../../lib/bignumber'; import { DATE_FORMAT_DETAILED } from '../../../lib/date-formats'; import type { - Rewards, - Rewards_party_delegations, - Rewards_party_rewardDetails_rewards, -} from './__generated__/Rewards'; + RewardsQuery, + RewardFieldsFragment, + DelegationFieldsFragment, +} from './__generated___/Rewards'; interface RewardInfoProps { - data: Rewards | undefined; + data: RewardsQuery | undefined; currVegaKey: string; - rewardAssetId: string; } -export const RewardInfo = ({ - data, - currVegaKey, - rewardAssetId, -}: RewardInfoProps) => { +export const RewardInfo = ({ data, currVegaKey }: RewardInfoProps) => { const { t } = useTranslation(); - // Create array of rewards per epoch - const vegaTokenRewards = React.useMemo(() => { - if (!data?.party || !data.party.rewardDetails?.length) return []; + const rewards = React.useMemo(() => { + if (!data?.party || !data.party.rewardsConnection?.edges?.length) return []; - const vegaTokenRewards = data.party.rewardDetails.find( - (r) => r?.asset.id === rewardAssetId + return ( + compact(data.party.rewardsConnection.edges.map((edge) => edge?.node)) || + [] ); + }, [data]); - // We only issue rewards as Vega tokens for now so there should only be one - // item in the rewardDetails array - if (!vegaTokenRewards) { - const rewardAssets = data.party.rewardDetails - .map((r) => r?.asset.symbol) - .join(', '); - Sentry.captureMessage( - `Could not find VEGA token rewards ${rewardAssets}` - ); + const delegations = React.useMemo(() => { + if (!data?.party || !data.party.delegationsConnection?.edges?.length) { return []; } - if (!vegaTokenRewards?.rewards?.length) return []; - - const sorted = Array.from(vegaTokenRewards.rewards).sort((a, b) => { - if (!a || !b) return 0; - if (a.epoch > b.epoch) return -1; - if (a.epoch < b.epoch) return 1; - return 0; - }); - - return sorted; - }, [data, rewardAssetId]); + return ( + compact( + data.party.delegationsConnection.edges.map((edge) => edge?.node) + ) || [] + ); + }, [data]); return (

{t('Connected Vega key')}: {currVegaKey}

- {vegaTokenRewards.length ? ( - vegaTokenRewards.map((reward, i) => { + {rewards.length ? ( + rewards.map((reward, i) => { if (!reward) return null; return ( ); }) @@ -81,8 +65,8 @@ export const RewardInfo = ({ }; interface RewardTableProps { - reward: Rewards_party_rewardDetails_rewards; - delegations: Rewards_party_delegations[]; + reward: RewardFieldsFragment; + delegations: DelegationFieldsFragment[] | []; } export const RewardTable = ({ reward, delegations }: RewardTableProps) => { diff --git a/apps/token/src/routes/rewards/home/rewards-page.tsx b/apps/token/src/routes/rewards/home/rewards-page.tsx new file mode 100644 index 000000000..09ed5ce6f --- /dev/null +++ b/apps/token/src/routes/rewards/home/rewards-page.tsx @@ -0,0 +1,116 @@ +import { Button, Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; +import { formatDistance } from 'date-fns'; +// @ts-ignore No types available for duration-js +import Duration from 'duration-js'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { EpochCountdown } from '../../../components/epoch-countdown'; +import { Heading } from '../../../components/heading'; +import { SplashLoader } from '../../../components/splash-loader'; +import { + AppStateActionType, + useAppState, +} from '../../../contexts/app-state/app-state-context'; +import { RewardInfo } from './reward-info'; +import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; +import { useNetworkParams, NetworkParams } from '@vegaprotocol/react-helpers'; +import { useRewardsQuery } from './__generated___/Rewards'; + +export const RewardsPage = () => { + const { t } = useTranslation(); + const { pubKey, pubKeys } = useVegaWallet(); + const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ + openVegaWalletDialog: store.openVegaWalletDialog, + })); + const { appDispatch } = useAppState(); + const { data, loading, error } = useRewardsQuery({ + variables: { partyId: pubKey || '' }, + skip: !pubKey, + }); + const { params } = useNetworkParams([ + NetworkParams.reward_staking_delegation_payoutDelay, + ]); + + const payoutDuration = React.useMemo(() => { + if (!params) { + return 0; + } + return new Duration( + params.reward_staking_delegation_payoutDelay + ).milliseconds(); + }, [params]); + + if (error) { + return ( +
+

{t('Something went wrong')}

+ {error &&
{error.message}
} +
+ ); + } + + if (loading || !params) { + return ( + + + + ); + } + + return ( +
+ +

{t('rewardsPara1')}

+

{t('rewardsPara2')}

+ {payoutDuration ? ( +
+ +

{t('rewardsPara3')}

+
+
+ ) : null} + {!loading && + data && + !error && + data.epoch.timestamps.start && + data.epoch.timestamps.expiry && ( +
+ +
+ )} +
+ {pubKey && pubKeys?.length ? ( + + ) : ( +
+ +
+ )} +
+
+ ); +}; diff --git a/apps/token/src/routes/rewards/index.tsx b/apps/token/src/routes/rewards/index.tsx index 72451ce15..a96626aa8 100644 --- a/apps/token/src/routes/rewards/index.tsx +++ b/apps/token/src/routes/rewards/index.tsx @@ -1,11 +1,11 @@ import { useDocumentTitle } from '../../hooks/use-document-title'; import type { RouteChildProps } from '..'; -import { RewardsIndex } from './home'; +import { RewardsPage } from './home'; const Rewards = ({ name }: RouteChildProps) => { useDocumentTitle(name); - return ; + return ; }; export default Rewards;