diff --git a/apps/trading/client-pages/competitions/competitions-home.tsx b/apps/trading/client-pages/competitions/competitions-home.tsx index 253b94894..41f1f6f9a 100644 --- a/apps/trading/client-pages/competitions/competitions-home.tsx +++ b/apps/trading/client-pages/competitions/competitions-home.tsx @@ -3,8 +3,8 @@ import { ErrorBoundary } from '@sentry/react'; import { CompetitionsHeader } from '../../components/competitions/competitions-header'; import { Intent, Loader, TradingButton } from '@vegaprotocol/ui-toolkit'; -import { useGames } from '../../lib/hooks/use-games'; -import { useCurrentEpochInfoQuery } from '../referrals/hooks/__generated__/Epoch'; +import { useGameCards } from '../../lib/hooks/use-game-cards'; +import { useCurrentEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch'; import { Link, useNavigate } from 'react-router-dom'; import { Links } from '../../lib/links'; import { @@ -28,7 +28,7 @@ export const CompetitionsHome = () => { const { data: epochData } = useCurrentEpochInfoQuery(); const currentEpoch = Number(epochData?.epoch.id); - const { data: gamesData, loading: gamesLoading } = useGames({ + const { data: gamesData, loading: gamesLoading } = useGameCards({ onlyActive: true, currentEpoch, }); diff --git a/apps/trading/client-pages/competitions/competitions-team.tsx b/apps/trading/client-pages/competitions/competitions-team.tsx index be3157636..1844fb291 100644 --- a/apps/trading/client-pages/competitions/competitions-team.tsx +++ b/apps/trading/client-pages/competitions/competitions-team.tsx @@ -12,7 +12,6 @@ import { type TeamStats as ITeamStats, type Team as TeamType, type Member, - type TeamGame, } from '../../lib/hooks/use-team'; import { DApp, EXPLORER_PARTIES, useLinks } from '@vegaprotocol/environment'; import { TeamAvatar } from '../../components/competitions/team-avatar'; @@ -23,6 +22,11 @@ import { LayoutWithGradient } from '../../components/layouts-inner'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { JoinTeam } from './join-team'; import { UpdateTeamButton } from './update-team-button'; +import { + type TeamGame, + useGames, + areTeamGames, +} from '../../lib/hooks/use-games'; export const CompetitionsTeam = () => { const t = useT(); @@ -38,8 +42,12 @@ export const CompetitionsTeam = () => { const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => { const t = useT(); const { pubKey } = useVegaWallet(); - const { data, team, partyTeam, stats, members, games, loading, refetch } = - useTeam(teamId, pubKey || undefined); + const { data, team, partyTeam, stats, members, loading, refetch } = useTeam( + teamId, + pubKey || undefined + ); + + const { data: games, loading: gamesLoading } = useGames(teamId); // only show spinner on first load so when users join teams its smoother if (!data && loading) { @@ -64,7 +72,8 @@ const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => { partyTeam={partyTeam} stats={stats} members={members} - games={games} + games={areTeamGames(games) ? games : undefined} + gamesLoading={gamesLoading} refetch={refetch} /> ); @@ -76,6 +85,7 @@ const TeamPage = ({ stats, members, games, + gamesLoading, refetch, }: { team: TeamType; @@ -83,6 +93,7 @@ const TeamPage = ({ stats?: ITeamStats; members?: Member[]; games?: TeamGame[]; + gamesLoading?: boolean; refetch: () => void; }) => { const t = useT(); @@ -113,7 +124,11 @@ const TeamPage = ({ onClick={() => setShowGames(true)} data-testid="games-toggle" > - {t('Games ({{count}})', { count: games ? games.length : 0 })} + {t('Games {{games}}', { + replace: { + games: gamesLoading ? '' : games ? `(${games.length})` : '(0)', + }, + })} - {showGames ? : } + {showGames ? ( + + ) : ( + + )} ); }; -const Games = ({ games }: { games?: TeamGame[] }) => { +const Games = ({ + games, + gamesLoading, +}: { + games?: TeamGame[]; + gamesLoading?: boolean; +}) => { const t = useT(); + if (gamesLoading) { + return ( +
+ +
+ ); + } + if (!games?.length) { return

{t('No games')}

; } diff --git a/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx b/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx index f4e86ed56..90d460c85 100644 --- a/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx +++ b/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx @@ -11,7 +11,7 @@ import { useEffect } from 'react'; import { useT } from '../../../lib/use-t'; import { matchPath, useLocation, useNavigate } from 'react-router-dom'; import { Routes } from '../../../lib/links'; -import { useCurrentEpochInfoQuery } from './__generated__/Epoch'; +import { useCurrentEpochInfoQuery } from '../../../lib/hooks/__generated__/Epoch'; const REFETCH_INTERVAL = 60 * 60 * 1000; // 1h const NON_ELIGIBLE_REFERRAL_SET_TOAST_ID = 'non-eligible-referral-set'; diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index 38cc15b5b..30272d9f8 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -34,9 +34,10 @@ import { } from './hooks/use-referral'; import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form'; import { useReferralProgram } from './hooks/use-referral-program'; -import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch'; +import { useCurrentEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch'; import { QUSDTooltip } from './qusd-tooltip'; import { CodeTile, StatTile, Tile } from './tile'; +import { areTeamGames, useGames } from '../../lib/hooks/use-games'; export const ReferralStatistics = () => { const { pubKey } = useVegaWallet(); @@ -576,7 +577,8 @@ export const RefereesTable = ({ }; const Team = ({ teamId }: { teamId?: string }) => { - const { team, games, members } = useTeam(teamId); + const { team, members } = useTeam(teamId); + const { data: games } = useGames(teamId); if (!team) return null; @@ -585,7 +587,10 @@ const Team = ({ teamId }: { teamId?: string }) => {

{team.name}

- +
); diff --git a/apps/trading/components/bootstrapper/bootstrapper.tsx b/apps/trading/components/bootstrapper/bootstrapper.tsx index 305542299..516ac34a6 100644 --- a/apps/trading/components/bootstrapper/bootstrapper.tsx +++ b/apps/trading/components/bootstrapper/bootstrapper.tsx @@ -153,5 +153,8 @@ const cacheConfig: InMemoryCacheConfig = { OrderUpdate: { keyFields: false, }, + Game: { + keyFields: false, + }, }, }; diff --git a/apps/trading/components/competitions/team-card.tsx b/apps/trading/components/competitions/team-card.tsx index 69307dfc6..980c895b9 100644 --- a/apps/trading/components/competitions/team-card.tsx +++ b/apps/trading/components/competitions/team-card.tsx @@ -1,4 +1,4 @@ -import { type TeamGame, type TeamStats } from '../../lib/hooks/use-team'; +import { type TeamStats } from '../../lib/hooks/use-team'; import { type TeamsFieldsFragment } from '../../lib/hooks/__generated__/Teams'; import { TeamAvatar, getFallbackAvatar } from './team-avatar'; import { FavoriteGame, Stat } from './team-stats'; @@ -13,6 +13,7 @@ import { take } from 'lodash'; import { DispatchMetricLabels } from '@vegaprotocol/types'; import classNames from 'classnames'; import { UpdateTeamButton } from '../../client-pages/competitions/update-team-button'; +import { type TeamGame } from '../../lib/hooks/use-games'; export const TeamCard = ({ rank, diff --git a/apps/trading/components/competitions/team-stats.tsx b/apps/trading/components/competitions/team-stats.tsx index ced4ad18c..3687ec649 100644 --- a/apps/trading/components/competitions/team-stats.tsx +++ b/apps/trading/components/competitions/team-stats.tsx @@ -11,11 +11,11 @@ import { formatNumberRounded } from '@vegaprotocol/utils'; import { type TeamStats as ITeamStats, type Member, - type TeamGame, } from '../../lib/hooks/use-team'; import { useT } from '../../lib/use-t'; import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types'; import classNames from 'classnames'; +import { type TeamGame } from '../../lib/hooks/use-games'; export const TeamStats = ({ stats, diff --git a/apps/trading/e2e/tests/teams/test_teams.py b/apps/trading/e2e/tests/teams/test_teams.py index 073660f4e..816b0e472 100644 --- a/apps/trading/e2e/tests/teams/test_teams.py +++ b/apps/trading/e2e/tests/teams/test_teams.py @@ -213,12 +213,12 @@ def create_team(vega: VegaServiceNull): def test_team_page_games_table(team_page: Page): team_page.get_by_test_id("games-toggle").click() - expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)") + expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (10)") expect(team_page.get_by_test_id("rank-0")).to_have_text("2") expect(team_page.get_by_test_id("epoch-0")).to_have_text("19") expect(team_page.get_by_test_id("type-0") ).to_have_text("Price maker fees paid") - expect(team_page.get_by_test_id("amount-0")).to_have_text("74") + expect(team_page.get_by_test_id("amount-0")).to_have_text("74") # 7,438,330 on preview.11 expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text("2") expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("4") diff --git a/apps/trading/client-pages/referrals/hooks/Epoch.graphql b/apps/trading/lib/hooks/Epoch.graphql similarity index 100% rename from apps/trading/client-pages/referrals/hooks/Epoch.graphql rename to apps/trading/lib/hooks/Epoch.graphql diff --git a/apps/trading/lib/hooks/Games.graphql b/apps/trading/lib/hooks/Games.graphql new file mode 100644 index 000000000..cf1c11798 --- /dev/null +++ b/apps/trading/lib/hooks/Games.graphql @@ -0,0 +1,35 @@ +fragment TeamEntity on TeamGameEntity { + rank + volume + rewardMetric + rewardEarned + totalRewardsEarned + team { + teamId + membersParticipating { + individual + rank + } + } +} + +fragment GameFields on Game { + id + epoch + numberOfParticipants + entities { + ... on TeamGameEntity { + ...TeamEntity + } + } +} + +query Games($epochFrom: Int) { + games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) { + edges { + node { + ...GameFields + } + } + } +} diff --git a/apps/trading/lib/hooks/Team.graphql b/apps/trading/lib/hooks/Team.graphql index e880f51f5..1b73f41ad 100644 --- a/apps/trading/lib/hooks/Team.graphql +++ b/apps/trading/lib/hooks/Team.graphql @@ -29,28 +29,6 @@ fragment TeamRefereeFields on TeamReferee { joinedAtEpoch } -fragment TeamEntity on TeamGameEntity { - rank - volume - rewardMetric - rewardEarned - totalRewardsEarned - team { - teamId - } -} - -fragment TeamGameFields on Game { - id - epoch - numberOfParticipants - entities { - ... on TeamGameEntity { - ...TeamEntity - } - } -} - fragment TeamMemberStatsFields on TeamMemberStatistics { partyId totalQuantumVolume @@ -87,13 +65,6 @@ query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) { } } } - games(entityScope: ENTITY_SCOPE_TEAMS) { - edges { - node { - ...TeamGameFields - } - } - } teamMembersStatistics( teamId: $teamId aggregationEpochs: $aggregationEpochs diff --git a/apps/trading/client-pages/referrals/hooks/__generated__/Epoch.ts b/apps/trading/lib/hooks/__generated__/Epoch.ts similarity index 100% rename from apps/trading/client-pages/referrals/hooks/__generated__/Epoch.ts rename to apps/trading/lib/hooks/__generated__/Epoch.ts diff --git a/apps/trading/lib/hooks/__generated__/Games.ts b/apps/trading/lib/hooks/__generated__/Games.ts new file mode 100644 index 000000000..eaa18b294 --- /dev/null +++ b/apps/trading/lib/hooks/__generated__/Games.ts @@ -0,0 +1,83 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } }; + +export type GameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } }> }; + +export type GamesQueryVariables = Types.Exact<{ + epochFrom?: Types.InputMaybe; +}>; + + +export type GamesQuery = { __typename?: 'Query', games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } }> } } | null> | null } }; + +export const TeamEntityFragmentDoc = gql` + fragment TeamEntity on TeamGameEntity { + rank + volume + rewardMetric + rewardEarned + totalRewardsEarned + team { + teamId + membersParticipating { + individual + rank + } + } +} + `; +export const GameFieldsFragmentDoc = gql` + fragment GameFields on Game { + id + epoch + numberOfParticipants + entities { + ... on TeamGameEntity { + ...TeamEntity + } + } +} + ${TeamEntityFragmentDoc}`; +export const GamesDocument = gql` + query Games($epochFrom: Int) { + games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) { + edges { + node { + ...GameFields + } + } + } +} + ${GameFieldsFragmentDoc}`; + +/** + * __useGamesQuery__ + * + * To run a query within a React component, call `useGamesQuery` and pass it any options that fit your needs. + * When your component renders, `useGamesQuery` 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 } = useGamesQuery({ + * variables: { + * epochFrom: // value for 'epochFrom' + * }, + * }); + */ +export function useGamesQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GamesDocument, options); + } +export function useGamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GamesDocument, options); + } +export type GamesQueryHookResult = ReturnType; +export type GamesLazyQueryHookResult = ReturnType; +export type GamesQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/trading/lib/hooks/__generated__/Team.ts b/apps/trading/lib/hooks/__generated__/Team.ts index dc5e4de3a..680ab82c7 100644 --- a/apps/trading/lib/hooks/__generated__/Team.ts +++ b/apps/trading/lib/hooks/__generated__/Team.ts @@ -9,10 +9,6 @@ export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: s export type TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number }; -export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }; - -export type TeamGameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> }; - export type TeamMemberStatsFieldsFragment = { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number }; export type TeamQueryVariables = Types.Exact<{ @@ -22,7 +18,7 @@ export type TeamQueryVariables = Types.Exact<{ }>; -export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null }, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null }; +export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null }; export const TeamFieldsFragmentDoc = gql` fragment TeamFields on Team { @@ -58,30 +54,6 @@ export const TeamRefereeFieldsFragmentDoc = gql` joinedAtEpoch } `; -export const TeamEntityFragmentDoc = gql` - fragment TeamEntity on TeamGameEntity { - rank - volume - rewardMetric - rewardEarned - totalRewardsEarned - team { - teamId - } -} - `; -export const TeamGameFieldsFragmentDoc = gql` - fragment TeamGameFields on Game { - id - epoch - numberOfParticipants - entities { - ... on TeamGameEntity { - ...TeamEntity - } - } -} - ${TeamEntityFragmentDoc}`; export const TeamMemberStatsFieldsFragmentDoc = gql` fragment TeamMemberStatsFields on TeamMemberStatistics { partyId @@ -120,13 +92,6 @@ export const TeamDocument = gql` } } } - games(entityScope: ENTITY_SCOPE_TEAMS) { - edges { - node { - ...TeamGameFields - } - } - } teamMembersStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) { edges { node { @@ -138,7 +103,6 @@ export const TeamDocument = gql` ${TeamFieldsFragmentDoc} ${TeamStatsFieldsFragmentDoc} ${TeamRefereeFieldsFragmentDoc} -${TeamGameFieldsFragmentDoc} ${TeamMemberStatsFieldsFragmentDoc}`; /** diff --git a/apps/trading/lib/hooks/use-game-cards.ts b/apps/trading/lib/hooks/use-game-cards.ts new file mode 100644 index 000000000..2de8db05a --- /dev/null +++ b/apps/trading/lib/hooks/use-game-cards.ts @@ -0,0 +1,48 @@ +import compact from 'lodash/compact'; +import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards'; +import { isActiveReward } from '../../components/rewards-container/active-rewards'; +import { + EntityScope, + IndividualScope, + type TransferNode, +} from '@vegaprotocol/types'; + +const isScopedToTeams = (node: TransferNode) => + node.transfer.kind.__typename === 'RecurringTransfer' && + // scoped to teams + (node.transfer.kind.dispatchStrategy?.entityScope === + EntityScope.ENTITY_SCOPE_TEAMS || + // or to individuals + (node.transfer.kind.dispatchStrategy?.entityScope === + EntityScope.ENTITY_SCOPE_INDIVIDUALS && + // but they have to be in a team + node.transfer.kind.dispatchStrategy.individualScope === + IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM)); + +export const useGameCards = ({ + currentEpoch, + onlyActive, +}: { + currentEpoch: number; + onlyActive: boolean; +}) => { + const { data, loading, error } = useActiveRewardsQuery({ + variables: { + isReward: true, + }, + fetchPolicy: 'cache-and-network', + }); + + const games = compact(data?.transfersConnection?.edges?.map((n) => n?.node)) + .map((n) => n as TransferNode) + .filter((node) => { + const active = onlyActive ? isActiveReward(node, currentEpoch) : true; + return active && isScopedToTeams(node); + }); + + return { + data: games, + loading, + error, + }; +}; diff --git a/apps/trading/lib/hooks/use-games.ts b/apps/trading/lib/hooks/use-games.ts index aec13a2e5..d5ffbe2ec 100644 --- a/apps/trading/lib/hooks/use-games.ts +++ b/apps/trading/lib/hooks/use-games.ts @@ -1,48 +1,80 @@ -import compact from 'lodash/compact'; -import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards'; -import { isActiveReward } from '../../components/rewards-container/active-rewards'; import { - EntityScope, - IndividualScope, - type TransferNode, -} from '@vegaprotocol/types'; + useGamesQuery, + type GameFieldsFragment, + type TeamEntityFragment, +} from './__generated__/Games'; +import orderBy from 'lodash/orderBy'; +import { removePaginationWrapper } from '@vegaprotocol/utils'; +import { useCurrentEpochInfoQuery } from './__generated__/Epoch'; +import { type ApolloError } from '@apollo/client'; -const isScopedToTeams = (node: TransferNode) => - node.transfer.kind.__typename === 'RecurringTransfer' && - // scoped to teams - (node.transfer.kind.dispatchStrategy?.entityScope === - EntityScope.ENTITY_SCOPE_TEAMS || - // or to individuals - (node.transfer.kind.dispatchStrategy?.entityScope === - EntityScope.ENTITY_SCOPE_INDIVIDUALS && - // but they have to be in a team - node.transfer.kind.dispatchStrategy.individualScope === - IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM)); +const TAKE_EPOCHS = 30; // TODO: should this be DEFAULT_AGGREGATION_EPOCHS? -export const useGames = ({ - currentEpoch, - onlyActive, -}: { - currentEpoch: number; - onlyActive: boolean; -}) => { - const { data, loading, error } = useActiveRewardsQuery({ - variables: { - isReward: true, - }, - fetchPolicy: 'cache-and-network', +const findTeam = (entities: GameFieldsFragment['entities'], teamId: string) => { + const team = entities.find( + (ent) => ent.__typename === 'TeamGameEntity' && ent.team.teamId === teamId + ); + if (team?.__typename === 'TeamGameEntity') return team; // drops __typename === 'IndividualGameEntity' from team object + return undefined; +}; + +export type Game = GameFieldsFragment & { + /** The team entity data accessible only if scoped to particular team. */ + team?: TeamEntityFragment; +}; +export type TeamGame = Game & { team: NonNullable }; + +const isTeamGame = (game: Game): game is TeamGame => game.team !== undefined; +export const areTeamGames = (games?: Game[]): games is TeamGame[] => + Boolean(games && games.filter((g) => isTeamGame(g)).length > 0); + +type GamesData = { + data?: Game[]; + loading: boolean; + error?: ApolloError; +}; + +export const useGames = (teamId?: string, epochFrom?: number): GamesData => { + const { + data: epochData, + loading: epochLoading, + error: epochError, + } = useCurrentEpochInfoQuery({ + skip: Boolean(epochFrom), }); - const games = compact(data?.transfersConnection?.edges?.map((n) => n?.node)) - .map((n) => n as TransferNode) - .filter((node) => { - const active = onlyActive ? isActiveReward(node, currentEpoch) : true; - return active && isScopedToTeams(node); + let from = epochFrom; + if (!from && epochData) { + from = Number(epochData.epoch.id) - TAKE_EPOCHS; + if (from < 1) from = 1; // make sure it's not negative + } + + const { data, loading, error } = useGamesQuery({ + variables: { + epochFrom: from, + }, + skip: !from, + fetchPolicy: 'cache-and-network', + context: { isEnlargedTimeout: true }, + }); + + const allGames = removePaginationWrapper(data?.games.edges); + const allOrScoped = allGames + .map((g) => ({ + ...g, + team: teamId ? findTeam(g.entities, teamId) : undefined, + })) + .filter((g) => { + // passthrough if not scoped to particular team + if (!teamId) return true; + return isTeamGame(g); }); + const games = orderBy(allOrScoped, 'epoch', 'desc'); + return { data: games, - loading, - error, + loading: loading || epochLoading, + error: error || epochError, }; }; diff --git a/apps/trading/lib/hooks/use-my-team.ts b/apps/trading/lib/hooks/use-my-team.ts index dac0ab6f2..d394d9052 100644 --- a/apps/trading/lib/hooks/use-my-team.ts +++ b/apps/trading/lib/hooks/use-my-team.ts @@ -4,6 +4,7 @@ import first from 'lodash/first'; import { useTeamsQuery } from './__generated__/Teams'; import { useTeam } from './use-team'; import { useTeams } from './use-teams'; +import { areTeamGames, useGames } from './use-games'; export const useMyTeam = () => { const { pubKey } = useVegaWallet(); @@ -19,7 +20,8 @@ export const useMyTeam = () => { const team = first(compact(maybeMyTeam?.teams?.edges.map((n) => n.node))); const rank = teams.findIndex((t) => t.teamId === team?.teamId) + 1; - const { games, stats } = useTeam(team?.teamId); + const { stats } = useTeam(team?.teamId); + const { data: games } = useGames(team?.teamId); - return { team, stats, games, rank }; + return { team, stats, games: areTeamGames(games) ? games : undefined, rank }; }; diff --git a/apps/trading/lib/hooks/use-team.ts b/apps/trading/lib/hooks/use-team.ts index f44a3b944..4d42e4075 100644 --- a/apps/trading/lib/hooks/use-team.ts +++ b/apps/trading/lib/hooks/use-team.ts @@ -1,11 +1,8 @@ -import compact from 'lodash/compact'; -import orderBy from 'lodash/orderBy'; import { useTeamQuery, type TeamFieldsFragment, type TeamStatsFieldsFragment, type TeamRefereeFieldsFragment, - type TeamEntityFragment, type TeamMemberStatsFieldsFragment, } from './__generated__/Team'; import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams'; @@ -18,8 +15,6 @@ export type Member = TeamRefereeFieldsFragment & { totalQuantumVolume: string; totalQuantumRewards: string; }; -export type TeamEntity = TeamEntityFragment; -export type TeamGame = ReturnType['games'][number]; export type MemberStats = TeamMemberStatsFieldsFragment; export const useTeam = (teamId?: string, partyId?: string) => { @@ -80,33 +75,11 @@ export const useTeam = (teamId?: string, partyId?: string) => { }); } - // Find games where the current team participated in - const gamesWithTeam = compact(data?.games.edges).map((edge) => { - const team = edge.node.entities.find((e) => { - if (e.__typename !== 'TeamGameEntity') return false; - if (e.team.teamId !== teamId) return false; - return true; - }); - - if (!team) return null; - - return { - id: edge.node.id, - epoch: edge.node.epoch, - numberOfParticipants: edge.node.numberOfParticipants, - entities: edge.node.entities, - team: team as TeamEntity, // TS can't infer that all the game entities are teams - }; - }); - - const games = orderBy(compact(gamesWithTeam), 'epoch', 'desc'); - return { ...queryResult, stats: teamStatsEdge?.node, team, members, - games, partyTeam, }; }; diff --git a/libs/apollo-client/src/cache-config.ts b/libs/apollo-client/src/cache-config.ts index 789d2096a..bbf30f135 100644 --- a/libs/apollo-client/src/cache-config.ts +++ b/libs/apollo-client/src/cache-config.ts @@ -48,5 +48,8 @@ export const DEFAULT_CACHE_CONFIG: InMemoryCacheConfig = { statistics: { keyFields: false, }, + Game: { + keyFields: false, + }, }, };