diff --git a/apps/governance/src/hooks/use-refresh-after-epoch.ts b/apps/governance/src/hooks/use-refresh-after-epoch.ts
index 539643737..a37643718 100644
--- a/apps/governance/src/hooks/use-refresh-after-epoch.ts
+++ b/apps/governance/src/hooks/use-refresh-after-epoch.ts
@@ -1,9 +1,8 @@
-import type { ObservableQuery } from '@apollo/client';
import { useEffect } from 'react';
export const useRefreshAfterEpoch = (
epochExpiry: string | undefined,
- refetch: ObservableQuery['refetch']
+ refetch: () => void
) => {
return useEffect(() => {
const epochInterval = setInterval(() => {
diff --git a/apps/governance/src/lib/epoch-pagination.ts b/apps/governance/src/lib/epoch-pagination.ts
new file mode 100644
index 000000000..1da896886
--- /dev/null
+++ b/apps/governance/src/lib/epoch-pagination.ts
@@ -0,0 +1,15 @@
+export const calculateEpochOffset = ({
+ epochId,
+ page,
+ size,
+}: {
+ epochId: number;
+ page: number;
+ size: number;
+}) => {
+ // offset the epoch by the current page number times the page size while making sure it doesn't go below the minimum epoch value
+ return {
+ fromEpoch: Math.max(0, epochId - size * page) + 1,
+ toEpoch: epochId - size * page + size,
+ };
+};
diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx
index 4b84c3436..3e34d05ca 100644
--- a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx
+++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx
@@ -3,7 +3,7 @@ import { AppStateProvider } from '../../../contexts/app-state/app-state-provider
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
const mockData = {
- epoch: '4441',
+ epoch: 4441,
rewards: [
{
asset: 'tDAI',
diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx
index 1dd812357..e70016de7 100644
--- a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx
+++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx
@@ -1,32 +1,43 @@
-import { useMemo } from 'react';
+import { useMemo, useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
+import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
+import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
import { useRewardsQuery } from '../home/__generated__/Rewards';
import { ENV } from '../../../config';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
+import { calculateEpochOffset } from '../../../lib/epoch-pagination';
-export const EpochIndividualRewards = () => {
+const EPOCHS_PAGE_SIZE = 10;
+
+type EpochTotalRewardsProps = {
+ currentEpoch: EpochFieldsFragment;
+};
+
+export const EpochIndividualRewards = ({
+ currentEpoch,
+}: EpochTotalRewardsProps) => {
+ // we start from the previous epoch when displaying rewards data, because the current one has no calculated data while ongoing
+ const epochId = Number(currentEpoch.id) - 1;
+ const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
+ const [page, setPage] = useState(1);
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV;
- const { data, loading, error } = useRewardsQuery({
+ const { data, loading, error, refetch } = useRewardsQuery({
+ notifyOnNetworkStatusChange: true,
variables: {
partyId: pubKey || '',
+ fromEpoch: epochId - EPOCHS_PAGE_SIZE,
+ toEpoch: epochId,
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
- // we can use the same value for rewardsPagination as delegationsPagination
- rewardsPagination: delegationsPagination
- ? {
- first: Number(delegationsPagination),
- }
- : undefined,
},
skip: !pubKey,
});
@@ -39,8 +50,37 @@ export const EpochIndividualRewards = () => {
const epochIndividualRewardSummaries = useMemo(() => {
if (!data?.party) return [];
- return generateEpochIndividualRewardsList(rewards);
- }, [data?.party, rewards]);
+ return generateEpochIndividualRewardsList({
+ rewards,
+ epochId,
+ page,
+ size: EPOCHS_PAGE_SIZE,
+ });
+ }, [data?.party, epochId, page, rewards]);
+
+ const refetchData = useCallback(
+ async (toPage?: number) => {
+ const targetPage = toPage ?? page;
+ await refetch({
+ partyId: pubKey || '',
+ ...calculateEpochOffset({ epochId, page, size: EPOCHS_PAGE_SIZE }),
+ delegationsPagination: delegationsPagination
+ ? {
+ first: Number(delegationsPagination),
+ }
+ : undefined,
+ });
+ setPage(targetPage);
+ },
+ [epochId, page, refetch, delegationsPagination, pubKey]
+ );
+
+ useEffect(() => {
+ // when the epoch changes, we want to refetch the data to update the current page
+ if (data) {
+ refetchData();
+ }
+ }, [epochId, data, refetchData]);
return (
{
{t('Connected Vega key')}:{' '}
{pubKey}
- {epochIndividualRewardSummaries.length ? (
- epochIndividualRewardSummaries.map(
- (epochIndividualRewardSummary) => (
-
- )
+ {epochIndividualRewardSummaries.map(
+ (epochIndividualRewardSummary) => (
+
)
- ) : (
- {t('noRewards')}
)}
+ 1}
+ hasNextPage={page < totalPages}
+ onBack={() => refetchData(page - 1)}
+ onNext={() => refetchData(page + 1)}
+ onFirst={() => refetchData(1)}
+ onLast={() => refetchData(totalPages)}
+ >
+ {t('Page')} {page}
+
)}
/>
diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts
index f0e390136..3f6c1ce18 100644
--- a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts
+++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts
@@ -43,6 +43,16 @@ describe('generateEpochIndividualRewardsList', () => {
epoch: { id: '1' },
};
+ const reward5: RewardFieldsFragment = {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '150',
+ percentageOfTotal: '0.15',
+ receivedAt: new Date(),
+ asset: { id: 'usd', symbol: 'USD', name: 'USD' },
+ party: { id: 'blah' },
+ epoch: { id: '3' },
+ };
+
const rewardWrongType: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_INSURANCE,
amount: '50',
@@ -54,20 +64,38 @@ describe('generateEpochIndividualRewardsList', () => {
};
it('should return an empty array if no rewards are provided', () => {
- expect(generateEpochIndividualRewardsList([])).toEqual([]);
+ expect(
+ generateEpochIndividualRewardsList({ rewards: [], epochId: 1 })
+ ).toEqual([
+ {
+ epoch: 1,
+ rewards: [],
+ },
+ ]);
});
it('should filter out any rewards of the wrong type', () => {
- const result = generateEpochIndividualRewardsList([rewardWrongType]);
+ const result = generateEpochIndividualRewardsList({
+ rewards: [rewardWrongType],
+ epochId: 1,
+ });
- expect(result).toEqual([]);
+ expect(result).toEqual([
+ {
+ epoch: 1,
+ rewards: [],
+ },
+ ]);
});
it('should return reward in the correct format', () => {
- const result = generateEpochIndividualRewardsList([reward1]);
+ const result = generateEpochIndividualRewardsList({
+ rewards: [reward1],
+ epochId: 1,
+ });
expect(result[0]).toEqual({
- epoch: '1',
+ epoch: 1,
rewards: [
{
asset: 'USD',
@@ -105,21 +133,24 @@ describe('generateEpochIndividualRewardsList', () => {
it('should return an array sorted by epoch descending', () => {
const rewards = [reward1, reward2, reward3, reward4];
- const result1 = generateEpochIndividualRewardsList(rewards);
+ const result1 = generateEpochIndividualRewardsList({ rewards, epochId: 2 });
- expect(result1[0].epoch).toEqual('2');
- expect(result1[1].epoch).toEqual('1');
+ expect(result1[0].epoch).toEqual(2);
+ expect(result1[1].epoch).toEqual(1);
const reorderedRewards = [reward4, reward3, reward2, reward1];
- const result2 = generateEpochIndividualRewardsList(reorderedRewards);
+ const result2 = generateEpochIndividualRewardsList({
+ rewards: reorderedRewards,
+ epochId: 2,
+ });
- expect(result2[0].epoch).toEqual('2');
- expect(result2[1].epoch).toEqual('1');
+ expect(result2[0].epoch).toEqual(2);
+ expect(result2[1].epoch).toEqual(1);
});
it('correctly calculates the total value of rewards for an asset', () => {
const rewards = [reward1, reward4];
- const result = generateEpochIndividualRewardsList(rewards);
+ const result = generateEpochIndividualRewardsList({ rewards, epochId: 1 });
expect(result[0].rewards[0].totalAmount).toEqual('200');
});
@@ -127,11 +158,11 @@ describe('generateEpochIndividualRewardsList', () => {
it('returns data in the expected shape', () => {
// Just sanity checking the whole structure here
const rewards = [reward1, reward2, reward3, reward4];
- const result = generateEpochIndividualRewardsList(rewards);
+ const result = generateEpochIndividualRewardsList({ rewards, epochId: 2 });
expect(result).toEqual([
{
- epoch: '2',
+ epoch: 2,
rewards: [
{
asset: 'GBP',
@@ -196,7 +227,165 @@ describe('generateEpochIndividualRewardsList', () => {
],
},
{
- epoch: '1',
+ epoch: 1,
+ rewards: [
+ {
+ asset: 'USD',
+ totalAmount: '200',
+ rewardTypes: {
+ [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
+ amount: '100',
+ percentageOfTotal: '0.1',
+ },
+ [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
+ amount: '100',
+ percentageOfTotal: '0.1',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ },
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('returns data correctly for the requested epoch range', () => {
+ const rewards = [reward1, reward2, reward3, reward4, reward5];
+ const resultPageOne = generateEpochIndividualRewardsList({
+ rewards,
+ epochId: 3,
+ page: 1,
+ size: 2,
+ });
+
+ expect(resultPageOne).toEqual([
+ {
+ epoch: 3,
+ rewards: [
+ {
+ asset: 'USD',
+ totalAmount: '150',
+ rewardTypes: {
+ [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
+ amount: '150',
+ percentageOfTotal: '0.15',
+ },
+ [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ },
+ },
+ ],
+ },
+ {
+ epoch: 2,
+ rewards: [
+ {
+ asset: 'GBP',
+ totalAmount: '200',
+ rewardTypes: {
+ [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
+ amount: '200',
+ percentageOfTotal: '0.2',
+ },
+ [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ },
+ },
+ {
+ asset: 'EUR',
+ totalAmount: '50',
+ rewardTypes: {
+ [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
+ amount: '50',
+ percentageOfTotal: '0.05',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
+ amount: '0',
+ percentageOfTotal: '0',
+ },
+ },
+ },
+ ],
+ },
+ ]);
+
+ const resultPageTwo = generateEpochIndividualRewardsList({
+ rewards,
+ epochId: 3,
+ page: 2,
+ size: 2,
+ });
+
+ expect(resultPageTwo).toEqual([
+ {
+ epoch: 1,
rewards: [
{
asset: 'USD',
diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts
index 285afa1a2..edf35d72b 100644
--- a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts
+++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts
@@ -2,9 +2,10 @@ import { BigNumber } from '../../../lib/bignumber';
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
import type { RewardFieldsFragment } from '../home/__generated__/Rewards';
import type { AccountType } from '@vegaprotocol/types';
+import { calculateEpochOffset } from '../../../lib/epoch-pagination';
export interface EpochIndividualReward {
- epoch: string;
+ epoch: number;
rewards: {
asset: string;
totalAmount: string;
@@ -27,11 +28,29 @@ const emptyRowAccountTypes = accountTypes.map((type) => [
},
]);
-export const generateEpochIndividualRewardsList = (
- rewards: RewardFieldsFragment[]
-) => {
+export const generateEpochIndividualRewardsList = ({
+ rewards,
+ epochId,
+ page = 1,
+ size = 10,
+}: {
+ rewards: RewardFieldsFragment[];
+ epochId: number;
+ page?: number;
+ size?: number;
+}) => {
+ const map: Map = new Map();
+ const { fromEpoch, toEpoch } = calculateEpochOffset({ epochId, page, size });
+
+ for (let i = toEpoch; i >= fromEpoch; i--) {
+ map.set(i.toString(), {
+ epoch: i,
+ rewards: [],
+ });
+ }
+
// We take the rewards and aggregate them by epoch and asset.
- const epochIndividualRewards = rewards.reduce((map, reward) => {
+ const epochIndividualRewards = rewards.reduce((acc, reward) => {
const epochId = reward.epoch.id;
const assetName = reward.asset.name;
const rewardType = reward.rewardType;
@@ -40,14 +59,14 @@ export const generateEpochIndividualRewardsList = (
// if the rewardType is not of a type we display in the table, we skip it.
if (!accountTypes.includes(rewardType)) {
- return map;
+ return acc;
}
- if (!map.has(epochId)) {
- map.set(epochId, { epoch: epochId, rewards: [] });
+ if (!acc.has(epochId)) {
+ return acc;
}
- const epoch = map.get(epochId);
+ const epoch = acc.get(epochId);
let asset = epoch?.rewards.find((r) => r.asset === assetName);
@@ -76,8 +95,8 @@ export const generateEpochIndividualRewardsList = (
});
}
- return map;
- }, new Map());
+ return acc;
+ }, map);
return Array.from(epochIndividualRewards.values()).sort(
(a, b) => Number(b.epoch) - Number(a.epoch)
diff --git a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx
index 6c8cb7394..b06f6a42d 100644
--- a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx
+++ b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx
@@ -1,44 +1,64 @@
import { render } from '@testing-library/react';
import { AppStateProvider } from '../../../contexts/app-state/app-state-provider';
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
+import type {
+ AggregatedEpochRewardSummary,
+ RewardType,
+ RewardItem,
+} from './generate-epoch-total-rewards-list';
import { AccountType } from '@vegaprotocol/types';
+const assetId =
+ 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663';
+
+const rewardsList = [
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '295',
+ },
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '0',
+ },
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '0',
+ },
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+];
+
+const rewards: Map = new Map();
+
+rewardsList.forEach((r) => {
+ rewards.set(r.rewardType, r);
+});
+
+const assetRewards: Map<
+ AggregatedEpochRewardSummary['assetId'],
+ AggregatedEpochRewardSummary
+> = new Map();
+
+assetRewards.set(assetId, {
+ assetId,
+ name: 'tDAI TEST',
+ rewards,
+ totalAmount: '295',
+});
+
const mockData = {
epoch: 4431,
- assetRewards: [
- {
- assetId:
- 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
- name: 'tDAI TEST',
- rewards: [
- {
- rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
- amount: '295',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
- amount: '0',
- },
- ],
- totalAmount: '295',
- },
- ],
+ assetRewards,
};
describe('EpochTotalRewardsTable', () => {
diff --git a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx
index 45f5caa1f..365ab0d03 100644
--- a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx
+++ b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx
@@ -48,17 +48,19 @@ export const EpochTotalRewardsTable = ({
}: EpochTotalRewardsGridProps) => {
return (
- {data.assetRewards.map(({ name, rewards, totalAmount }, i) => (
-
-
- {name}
+ {Array.from(data.assetRewards.values()).map(
+ ({ name, rewards, totalAmount }, i) => (
+
+
+ {name}
+
+ {Array.from(rewards.values()).map(({ rewardType, amount }, i) => (
+
+ ))}
+
- {rewards.map(({ rewardType, amount }, i) => (
-
- ))}
-
-
- ))}
+ )
+ )}
);
};
diff --git a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx
index 10410e5b7..f4c8c3ef5 100644
--- a/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx
+++ b/apps/governance/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx
@@ -1,21 +1,62 @@
-import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
+import { useState, useCallback, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
+import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
-import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
-import { NoRewards } from '../no-rewards';
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
+import { calculateEpochOffset } from '../../../lib/epoch-pagination';
-export const EpochTotalRewards = () => {
+const EPOCHS_PAGE_SIZE = 10;
+
+type EpochTotalRewardsProps = {
+ currentEpoch: EpochFieldsFragment;
+};
+
+export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
+ // we start from the previous epoch when displaying rewards data, because the current one has no calculated data while ongoing
+ const epochId = Number(currentEpoch.id) - 1;
+ const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
+ const { t } = useTranslation();
+ const [page, setPage] = useState(1);
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
+ notifyOnNetworkStatusChange: true,
variables: {
- epochRewardSummariesPagination: {
- first: 10,
+ epochRewardSummariesFilter: {
+ fromEpoch: epochId - EPOCHS_PAGE_SIZE,
},
},
});
- useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch);
- const epochTotalRewardSummaries = generateEpochTotalRewardsList(data) || [];
+ const refetchData = useCallback(
+ async (toPage?: number) => {
+ const targetPage = toPage ?? page;
+ await refetch({
+ epochRewardSummariesFilter: calculateEpochOffset({
+ epochId,
+ page: targetPage,
+ size: EPOCHS_PAGE_SIZE,
+ }),
+ });
+ setPage(targetPage);
+ },
+ [epochId, page, refetch]
+ );
+
+ useEffect(() => {
+ // when the epoch changes, we want to refetch the data to update the current page
+ if (data) {
+ refetchData();
+ }
+ }, [epochId, data, refetchData]);
+
+ const epochTotalRewardSummaries =
+ generateEpochTotalRewardsList({
+ data,
+ epochId,
+ page,
+ size: EPOCHS_PAGE_SIZE,
+ }) || [];
return (
{
className="max-w-full overflow-auto"
data-testid="epoch-rewards-total"
>
- {epochTotalRewardSummaries.length === 0 ? (
-
- ) : (
- <>
- {epochTotalRewardSummaries.map((epochTotalSummary, index) => (
-
- ))}
- >
+ {Array.from(epochTotalRewardSummaries.values()).map(
+ (epochTotalSummary, index) => (
+
+ )
)}
+ 1}
+ hasNextPage={page < totalPages}
+ onBack={() => refetchData(page - 1)}
+ onNext={() => refetchData(page + 1)}
+ onFirst={() => refetchData(1)}
+ onLast={() => refetchData(totalPages)}
+ >
+ {t('Page')} {page}
+
)}
/>
diff --git a/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts b/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts
index 51b9d6e31..6d5d114b9 100644
--- a/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts
+++ b/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts
@@ -3,13 +3,23 @@ import { AccountType } from '@vegaprotocol/types';
describe('generateEpochAssetRewardsList', () => {
it('should return an empty array if data is undefined', () => {
- const result = generateEpochTotalRewardsList(undefined);
+ const result = generateEpochTotalRewardsList({ epochId: 1 });
- expect(result).toEqual([]);
+ expect(result).toEqual(
+ new Map([
+ [
+ '1',
+ {
+ epoch: 1,
+ assetRewards: new Map(),
+ },
+ ],
+ ])
+ );
});
- it('should return an empty array if empty data is provided', () => {
- const epochData = {
+ it('should return an empty map if empty data is provided', () => {
+ const data = {
assetsConnection: {
edges: [],
},
@@ -23,13 +33,23 @@ describe('generateEpochAssetRewardsList', () => {
},
};
- const result = generateEpochTotalRewardsList(epochData);
+ const result = generateEpochTotalRewardsList({ data, epochId: 1 });
- expect(result).toEqual([]);
+ expect(result).toEqual(
+ new Map([
+ [
+ '1',
+ {
+ epoch: 1,
+ assetRewards: new Map(),
+ },
+ ],
+ ])
+ );
});
- it('should return an empty array if no epochRewardSummaries are provided', () => {
- const epochData = {
+ it('should return an empty map if no epochRewardSummaries are provided', () => {
+ const data = {
assetsConnection: {
edges: [
{
@@ -56,13 +76,23 @@ describe('generateEpochAssetRewardsList', () => {
},
};
- const result = generateEpochTotalRewardsList(epochData);
+ const result = generateEpochTotalRewardsList({ data, epochId: 1 });
- expect(result).toEqual([]);
+ expect(result).toEqual(
+ new Map([
+ [
+ '1',
+ {
+ epoch: 1,
+ assetRewards: new Map(),
+ },
+ ],
+ ])
+ );
});
- it('should return an array of unnamed assets if no asset names are provided (should not happen)', () => {
- const epochData = {
+ it('should return a map of unnamed assets if no asset names are provided (should not happen)', () => {
+ const data = {
assetsConnection: {
edges: [],
},
@@ -85,50 +115,80 @@ describe('generateEpochAssetRewardsList', () => {
},
};
- const result = generateEpochTotalRewardsList(epochData);
+ const result = generateEpochTotalRewardsList({ data, epochId: 1 });
- expect(result).toEqual([
- {
- epoch: 1,
- assetRewards: [
+ expect(result).toEqual(
+ new Map([
+ [
+ '1',
{
- assetId: '1',
- name: '',
- rewards: [
- {
- rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
- amount: '123',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
- amount: '0',
- },
- ],
- totalAmount: '123',
+ epoch: 1,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: '',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '123',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '123',
+ },
+ ],
+ ]),
},
],
- },
- ]);
+ ])
+ );
});
- it('should return an array of aggregated epoch summaries', () => {
- const epochData = {
+ it('should return the aggregated epoch summaries', () => {
+ const data = {
assetsConnection: {
edges: [
{
@@ -180,81 +240,425 @@ describe('generateEpochAssetRewardsList', () => {
},
};
- const result = generateEpochTotalRewardsList(epochData);
+ const result = generateEpochTotalRewardsList({ data, epochId: 2 });
- expect(result).toEqual([
- {
- epoch: 1,
- assetRewards: [
+ expect(result).toEqual(
+ new Map([
+ [
+ '1',
{
- assetId: '1',
- name: 'Asset 1',
- rewards: [
- {
- rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
- amount: '100',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
- amount: '123',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
- amount: '0',
- },
- ],
- totalAmount: '223',
+ epoch: 1,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: 'Asset 1',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '100',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '123',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '223',
+ },
+ ],
+ ]),
+ },
+ ],
+ [
+ '2',
+ {
+ epoch: 2,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: 'Asset 1',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '5',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '5',
+ },
+ ],
+ ]),
+ },
+ ],
+ ])
+ );
+ });
+
+ it('should return the requested range for aggregated epoch summaries', () => {
+ const data = {
+ assetsConnection: {
+ edges: [
+ {
+ node: {
+ id: '1',
+ name: 'Asset 1',
+ },
+ },
+ {
+ node: {
+ id: '2',
+ name: 'Asset 2',
+ },
},
],
},
- {
- epoch: 2,
- assetRewards: [
+ epochRewardSummaries: {
+ edges: [
{
- assetId: '1',
- name: 'Asset 1',
- rewards: [
- {
- rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
- amount: '0',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
- amount: '5',
- },
- {
- rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
- amount: '0',
- },
- ],
- totalAmount: '5',
+ node: {
+ epoch: 1,
+ assetId: '1',
+ rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '123',
+ },
+ },
+ {
+ node: {
+ epoch: 1,
+ assetId: '1',
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '100',
+ },
+ },
+ {
+ node: {
+ epoch: 2,
+ assetId: '1',
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '6',
+ },
+ },
+ {
+ node: {
+ epoch: 2,
+ assetId: '1',
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '27',
+ },
+ },
+ {
+ node: {
+ epoch: 3,
+ assetId: '1',
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '15',
+ },
},
],
},
- ]);
+ epoch: {
+ timestamps: {
+ expiry: null,
+ },
+ },
+ };
+
+ const resultPageOne = generateEpochTotalRewardsList({
+ data,
+ epochId: 3,
+ page: 1,
+ size: 2,
+ });
+
+ expect(resultPageOne).toEqual(
+ new Map([
+ [
+ '2',
+ {
+ epoch: 2,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: 'Asset 1',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '33',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '33',
+ },
+ ],
+ ]),
+ },
+ ],
+ [
+ '3',
+ {
+ epoch: 3,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: 'Asset 1',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '15',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '15',
+ },
+ ],
+ ]),
+ },
+ ],
+ ])
+ );
+
+ const resultPageTwo = generateEpochTotalRewardsList({
+ data,
+ epochId: 3,
+ page: 2,
+ size: 2,
+ });
+
+ expect(resultPageTwo).toEqual(
+ new Map([
+ [
+ '1',
+ {
+ epoch: 1,
+ assetRewards: new Map([
+ [
+ '1',
+ {
+ assetId: '1',
+ name: 'Asset 1',
+ rewards: new Map([
+ [
+ AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
+ amount: '100',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ amount: '123',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ {
+ rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
+ amount: '0',
+ },
+ ],
+ [
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ {
+ rewardType:
+ AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
+ amount: '0',
+ },
+ ],
+ ]),
+ totalAmount: '223',
+ },
+ ],
+ ]),
+ },
+ ],
+ ])
+ );
});
});
diff --git a/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts b/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts
index 87c51b010..ddf56cac2 100644
--- a/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts
+++ b/apps/governance/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts
@@ -5,127 +5,97 @@ import type {
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
import type { AccountType } from '@vegaprotocol/types';
-import { BigNumber } from '../../../lib/bignumber';
+import { calculateEpochOffset } from '../../../lib/epoch-pagination';
interface EpochSummaryWithNamedReward extends EpochRewardSummaryFieldsFragment {
name: string;
}
-export interface AggregatedEpochRewardSummary {
+export type RewardType = EpochRewardSummaryFieldsFragment['rewardType'];
+export type RewardItem = Pick<
+ EpochRewardSummaryFieldsFragment,
+ 'rewardType' | 'amount'
+>;
+
+export type AggregatedEpochRewardSummary = {
assetId: EpochRewardSummaryFieldsFragment['assetId'];
name: EpochSummaryWithNamedReward['name'];
- rewards: {
- rewardType: EpochRewardSummaryFieldsFragment['rewardType'];
- amount: EpochRewardSummaryFieldsFragment['amount'];
- }[];
+ rewards: Map;
totalAmount: string;
-}
+};
-export interface EpochTotalSummary {
+export type EpochTotalSummary = {
epoch: EpochRewardSummaryFieldsFragment['epoch'];
- assetRewards: AggregatedEpochRewardSummary[];
-}
+ assetRewards: Map<
+ EpochRewardSummaryFieldsFragment['assetId'],
+ AggregatedEpochRewardSummary
+ >;
+};
-const emptyRowAccountTypes = Object.keys(RowAccountTypes).map((type) => ({
- rewardType: type as AccountType,
- amount: '0',
-}));
+const emptyRowAccountTypes: Map = new Map();
-export const generateEpochTotalRewardsList = (
- epochData: EpochAssetsRewardsQuery | undefined
-) => {
+Object.keys(RowAccountTypes).forEach((type) => {
+ emptyRowAccountTypes.set(type as AccountType, {
+ rewardType: type as AccountType,
+ amount: '0',
+ });
+});
+
+export const generateEpochTotalRewardsList = ({
+ data,
+ epochId,
+ page = 1,
+ size = 10,
+}: {
+ data?: EpochAssetsRewardsQuery | undefined;
+ epochId: number;
+ page?: number;
+ size?: number;
+}) => {
const epochRewardSummaries = removePaginationWrapper(
- epochData?.epochRewardSummaries?.edges
+ data?.epochRewardSummaries?.edges
);
- const assets = removePaginationWrapper(epochData?.assetsConnection?.edges);
+ const assets = removePaginationWrapper(data?.assetsConnection?.edges);
- // Because the epochRewardSummaries don't have the asset name, we need to find it in the assets list
- const epochSummariesWithNamedReward: EpochSummaryWithNamedReward[] =
- epochRewardSummaries.map((epochReward) => ({
- ...epochReward,
- name:
- assets.find((asset) => asset.id === epochReward.assetId)?.name || '',
- }));
+ const map: Map = new Map();
+ const { fromEpoch, toEpoch } = calculateEpochOffset({ epochId, page, size });
- // Aggregating the epoch summaries by epoch number
- const aggregatedEpochSummariesByEpochNumber =
- epochSummariesWithNamedReward.reduce((acc, epochReward) => {
- const epoch = epochReward.epoch;
- const epochSummaryIndex = acc.findIndex(
- (epochSummary) => epochSummary[0].epoch === epoch
- );
-
- if (epochSummaryIndex === -1) {
- acc.push([epochReward]);
- } else {
- acc[epochSummaryIndex].push(epochReward);
- }
-
- return acc;
- }, [] as EpochSummaryWithNamedReward[][]);
-
- // Now aggregate the array of arrays of epoch summaries by asset rewards.
- const epochTotalRewards: EpochTotalSummary[] =
- aggregatedEpochSummariesByEpochNumber.map((epochSummaries) => {
- const assetRewards = epochSummaries.reduce((acc, epochSummary) => {
- const assetRewardIndex = acc.findIndex(
- (assetReward) =>
- assetReward.assetId === epochSummary.assetId &&
- assetReward.name === epochSummary.name
- );
-
- if (assetRewardIndex === -1) {
- acc.push({
- assetId: epochSummary.assetId,
- name: epochSummary.name,
- rewards: [
- ...emptyRowAccountTypes.map((emptyRowAccountType) => {
- if (
- emptyRowAccountType.rewardType === epochSummary.rewardType
- ) {
- return {
- rewardType: epochSummary.rewardType,
- amount: epochSummary.amount,
- };
- } else {
- return emptyRowAccountType;
- }
- }),
- ],
- totalAmount: epochSummary.amount,
- });
- } else {
- acc[assetRewardIndex].rewards = acc[assetRewardIndex].rewards.map(
- (reward) => {
- if (reward.rewardType === epochSummary.rewardType) {
- return {
- rewardType: epochSummary.rewardType,
- amount: (
- Number(reward.amount) + Number(epochSummary.amount)
- ).toString(),
- };
- } else {
- return reward;
- }
- }
- );
- acc[assetRewardIndex].totalAmount = (
- Number(acc[assetRewardIndex].totalAmount) +
- Number(epochSummary.amount)
- ).toString();
- }
-
- return acc;
- }, [] as AggregatedEpochRewardSummary[]);
-
- return {
- epoch: epochSummaries[0].epoch,
- assetRewards: assetRewards.sort((a, b) => {
- return new BigNumber(b.totalAmount).comparedTo(a.totalAmount);
- }),
- };
+ for (let i = toEpoch; i >= fromEpoch; i--) {
+ map.set(i.toString(), {
+ epoch: i,
+ assetRewards: new Map(),
});
+ }
- return epochTotalRewards;
+ return epochRewardSummaries.reduce((acc, reward) => {
+ const epoch = acc.get(reward.epoch.toString());
+
+ if (epoch) {
+ const matchingAsset = assets.find((asset) => asset.id === reward.assetId);
+ const assetWithRewards = epoch.assetRewards.get(reward.assetId);
+
+ const rewards =
+ assetWithRewards?.rewards || new Map(emptyRowAccountTypes);
+ const rewardItem = rewards?.get(reward.rewardType);
+ const amount = (
+ (Number(rewardItem?.amount) || 0) + Number(reward.amount)
+ ).toString();
+
+ rewards?.set(reward.rewardType, {
+ rewardType: reward.rewardType,
+ amount,
+ });
+
+ epoch.assetRewards.set(reward.assetId, {
+ assetId: reward.assetId,
+ name: matchingAsset?.name || '',
+ rewards: rewards || new Map(emptyRowAccountTypes),
+ totalAmount: (
+ Number(reward.amount) + Number(assetWithRewards?.totalAmount || 0)
+ ).toString(),
+ });
+ }
+ return acc;
+ }, map);
};
diff --git a/apps/governance/src/routes/rewards/home/Rewards.graphql b/apps/governance/src/routes/rewards/home/Rewards.graphql
index 083231b4d..cb0b3b5cb 100644
--- a/apps/governance/src/routes/rewards/home/Rewards.graphql
+++ b/apps/governance/src/routes/rewards/home/Rewards.graphql
@@ -23,12 +23,18 @@ fragment DelegationFields on Delegation {
query Rewards(
$partyId: ID!
- $delegationsPagination: Pagination
+ $fromEpoch: Int
+ $toEpoch: Int
$rewardsPagination: Pagination
+ $delegationsPagination: Pagination
) {
party(id: $partyId) {
id
- rewardsConnection(pagination: $rewardsPagination) {
+ rewardsConnection(
+ fromEpoch: $fromEpoch
+ toEpoch: $toEpoch
+ pagination: $rewardsPagination
+ ) {
edges {
node {
...RewardFields
@@ -43,14 +49,6 @@ query Rewards(
}
}
}
- epoch {
- id
- timestamps {
- start
- end
- expiry
- }
- }
}
fragment EpochRewardSummaryFields on EpochRewardSummary {
@@ -60,7 +58,10 @@ fragment EpochRewardSummaryFields on EpochRewardSummary {
rewardType
}
-query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
+query EpochAssetsRewards(
+ $epochRewardSummariesFilter: RewardSummaryFilter
+ $epochRewardSummariesPagination: Pagination
+) {
assetsConnection {
edges {
node {
@@ -69,18 +70,16 @@ query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
}
}
}
- epochRewardSummaries(pagination: $epochRewardSummariesPagination) {
+ epochRewardSummaries(
+ filter: $epochRewardSummariesFilter
+ pagination: $epochRewardSummariesPagination
+ ) {
edges {
node {
...EpochRewardSummaryFields
}
}
}
- epoch {
- timestamps {
- expiry
- }
- }
}
fragment EpochFields on Epoch {
diff --git a/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts b/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts
index 42f28ba2c..d3caf2905 100644
--- a/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts
+++ b/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts
@@ -9,21 +9,24 @@ export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: stri
export type RewardsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
- delegationsPagination?: Types.InputMaybe;
+ fromEpoch?: Types.InputMaybe;
+ toEpoch?: Types.InputMaybe;
rewardsPagination?: Types.InputMaybe;
+ delegationsPagination?: Types.InputMaybe;
}>;
-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, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: 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, epoch: number } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | 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, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: 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, epoch: number } } | null> | null } | null } | null };
export type EpochRewardSummaryFieldsFragment = { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType };
export type EpochAssetsRewardsQueryVariables = Types.Exact<{
+ epochRewardSummariesFilter?: Types.InputMaybe;
epochRewardSummariesPagination?: Types.InputMaybe;
}>;
-export type EpochAssetsRewardsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string } } | null> | null } | null, epochRewardSummaries?: { __typename?: 'EpochRewardSummaryConnection', edges?: Array<{ __typename?: 'EpochRewardSummaryEdge', node: { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType } } | null> | null } | null, epoch: { __typename?: 'Epoch', timestamps: { __typename?: 'EpochTimestamps', expiry?: any | null } } };
+export type EpochAssetsRewardsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string } } | null> | null } | null, epochRewardSummaries?: { __typename?: 'EpochRewardSummaryConnection', edges?: Array<{ __typename?: 'EpochRewardSummaryEdge', node: { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType } } | null> | null } | null };
export type EpochFieldsFragment = { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } };
@@ -76,10 +79,14 @@ export const EpochFieldsFragmentDoc = gql`
}
`;
export const RewardsDocument = gql`
- query Rewards($partyId: ID!, $delegationsPagination: Pagination, $rewardsPagination: Pagination) {
+ query Rewards($partyId: ID!, $fromEpoch: Int, $toEpoch: Int, $rewardsPagination: Pagination, $delegationsPagination: Pagination) {
party(id: $partyId) {
id
- rewardsConnection(pagination: $rewardsPagination) {
+ rewardsConnection(
+ fromEpoch: $fromEpoch
+ toEpoch: $toEpoch
+ pagination: $rewardsPagination
+ ) {
edges {
node {
...RewardFields
@@ -94,14 +101,6 @@ export const RewardsDocument = gql`
}
}
}
- epoch {
- id
- timestamps {
- start
- end
- expiry
- }
- }
}
${RewardFieldsFragmentDoc}
${DelegationFieldsFragmentDoc}`;
@@ -119,8 +118,10 @@ ${DelegationFieldsFragmentDoc}`;
* const { data, loading, error } = useRewardsQuery({
* variables: {
* partyId: // value for 'partyId'
- * delegationsPagination: // value for 'delegationsPagination'
+ * fromEpoch: // value for 'fromEpoch'
+ * toEpoch: // value for 'toEpoch'
* rewardsPagination: // value for 'rewardsPagination'
+ * delegationsPagination: // value for 'delegationsPagination'
* },
* });
*/
@@ -136,7 +137,7 @@ export type RewardsQueryHookResult = ReturnType;
export type RewardsLazyQueryHookResult = ReturnType;
export type RewardsQueryResult = Apollo.QueryResult;
export const EpochAssetsRewardsDocument = gql`
- query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
+ query EpochAssetsRewards($epochRewardSummariesFilter: RewardSummaryFilter, $epochRewardSummariesPagination: Pagination) {
assetsConnection {
edges {
node {
@@ -145,18 +146,16 @@ export const EpochAssetsRewardsDocument = gql`
}
}
}
- epochRewardSummaries(pagination: $epochRewardSummariesPagination) {
+ epochRewardSummaries(
+ filter: $epochRewardSummariesFilter
+ pagination: $epochRewardSummariesPagination
+ ) {
edges {
node {
...EpochRewardSummaryFields
}
}
}
- epoch {
- timestamps {
- expiry
- }
- }
}
${EpochRewardSummaryFieldsFragmentDoc}`;
@@ -172,6 +171,7 @@ export const EpochAssetsRewardsDocument = gql`
* @example
* const { data, loading, error } = useEpochAssetsRewardsQuery({
* variables: {
+ * epochRewardSummariesFilter: // value for 'epochRewardSummariesFilter'
* epochRewardSummariesPagination: // value for 'epochRewardSummariesPagination'
* },
* });
diff --git a/apps/governance/src/routes/rewards/home/rewards-page.tsx b/apps/governance/src/routes/rewards/home/rewards-page.tsx
index 3d79167a7..d0e92bdc5 100644
--- a/apps/governance/src/routes/rewards/home/rewards-page.tsx
+++ b/apps/governance/src/routes/rewards/home/rewards-page.tsx
@@ -136,11 +136,15 @@ export const RewardsPage = () => {
{toggleRewardsView === 'total' ? (
-
+ epochData?.epoch ? (
+
+ ) : null
) : (
{pubKey && pubKeys?.length ? (
-
+ epochData?.epoch ? (
+
+ ) : null
) : (
)}
diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts
index 897d3ee59..a62a1eba3 100644
--- a/libs/ui-toolkit/src/components/index.ts
+++ b/libs/ui-toolkit/src/components/index.ts
@@ -25,6 +25,7 @@ export * from './nav-dropdown';
export * from './nav';
export * from './navigation';
export * from './notification';
+export * from './pagination';
export * from './popover';
export * from './progress-bar';
export * from './radio-group';
diff --git a/libs/ui-toolkit/src/components/pagination/index.tsx b/libs/ui-toolkit/src/components/pagination/index.tsx
new file mode 100644
index 000000000..cb727655b
--- /dev/null
+++ b/libs/ui-toolkit/src/components/pagination/index.tsx
@@ -0,0 +1 @@
+export * from './pagination';
diff --git a/libs/ui-toolkit/src/components/pagination/pagination.stories.tsx b/libs/ui-toolkit/src/components/pagination/pagination.stories.tsx
new file mode 100644
index 000000000..3d85cabb7
--- /dev/null
+++ b/libs/ui-toolkit/src/components/pagination/pagination.stories.tsx
@@ -0,0 +1,28 @@
+import { useState } from 'react';
+import { Pagination } from './pagination';
+
+import type { ComponentStory, ComponentMeta } from '@storybook/react';
+export default {
+ title: 'Pagination',
+ component: Pagination,
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => {
+ const MAX_PAGE = 3;
+ const [page, setPage] = useState(1);
+
+ return (
+
+
setPage(Math.max(1, page - 1))}
+ onNext={() => setPage(Math.min(MAX_PAGE, page + 1))}
+ >
+ Page {page}
+
+
+ );
+};
+
+export const Default = Template.bind({});
diff --git a/libs/ui-toolkit/src/components/pagination/pagination.tsx b/libs/ui-toolkit/src/components/pagination/pagination.tsx
new file mode 100644
index 000000000..cf824a626
--- /dev/null
+++ b/libs/ui-toolkit/src/components/pagination/pagination.tsx
@@ -0,0 +1,73 @@
+import type { ReactNode } from 'react';
+import { Button } from '../button';
+import { Icon } from '../icon';
+
+export type PaginationProps = {
+ hasPrevPage: boolean;
+ hasNextPage: boolean;
+ isLoading?: boolean;
+ children?: ReactNode;
+ onBack: () => void;
+ onNext: () => void;
+ onFirst?: () => void;
+ onLast?: () => void;
+};
+
+const buttonClass = 'rounded-full w-[34px] h-[34px]';
+
+export const Pagination = ({
+ hasPrevPage,
+ hasNextPage,
+ isLoading,
+ children,
+ onBack,
+ onNext,
+ onFirst,
+ onLast,
+}: PaginationProps) => {
+ return (
+
+ {onFirst && (
+
+ )}
+
+ {children}
+
+ {onLast && (
+
+ )}
+
+ );
+};