fix(trading): game results (#6083)
This commit is contained in:
parent
0ada3a2f8d
commit
91d00a0520
@ -283,6 +283,7 @@ const Games = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-sm">
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
@ -325,7 +326,8 @@ const Games = ({
|
||||
? t.transfer.kind.endEpoch >= game.epoch
|
||||
: true;
|
||||
|
||||
const rejected = t.transfer.status === TransferStatus.STATUS_REJECTED;
|
||||
const rejected =
|
||||
t.transfer.status === TransferStatus.STATUS_REJECTED;
|
||||
|
||||
return idMatch && metricMatch && start && end && !rejected;
|
||||
});
|
||||
@ -378,6 +380,7 @@ const Games = ({
|
||||
})}
|
||||
noCollapse={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -419,6 +422,7 @@ const Members = ({ members }: { members?: Member[] }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="text-sm">
|
||||
<Table
|
||||
columns={[
|
||||
{ name: 'referee', displayName: t('Member') },
|
||||
@ -437,6 +441,7 @@ const Members = ({ members }: { members?: Member[] }) => {
|
||||
data={data}
|
||||
noCollapse={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -503,7 +508,7 @@ const EndTimeCell = ({ epoch }: { epoch?: number }) => {
|
||||
variables: {
|
||||
epochId: epoch ? epoch.toString() : undefined,
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
if (loading) return <Loader size="small" />;
|
||||
|
@ -3,7 +3,10 @@ import { type EnrichedRewardTransfer } from '../../lib/hooks/use-rewards';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useStakeAvailable } from '../../lib/hooks/use-stake-available';
|
||||
import { useMyTeam } from '../../lib/hooks/use-my-team';
|
||||
import { ActiveRewardCard } from '../rewards-container/reward-card';
|
||||
import {
|
||||
ActiveRewardCard,
|
||||
areAllMarketsSettled,
|
||||
} from '../rewards-container/reward-card';
|
||||
import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
@ -127,6 +130,9 @@ export const GamesContainer = ({
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{data
|
||||
.filter((n) => applyFilter(n, filter))
|
||||
// filter out the cards (rewards) for which all of the markets
|
||||
// are settled
|
||||
.filter((n) => !areAllMarketsSettled(n))
|
||||
.map((game, i) => {
|
||||
// TODO: Remove `kind` prop from ActiveRewardCard
|
||||
const { transfer } = game;
|
||||
|
@ -18,7 +18,7 @@ import { useRewards } from '../../lib/hooks/use-rewards';
|
||||
import { useMyTeam } from '../../lib/hooks/use-my-team';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useStakeAvailable } from '../../lib/hooks/use-stake-available';
|
||||
import { ActiveRewardCard } from './reward-card';
|
||||
import { ActiveRewardCard, areAllMarketsSettled } from './reward-card';
|
||||
|
||||
export type Filter = {
|
||||
searchTerm: string;
|
||||
@ -122,6 +122,9 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
|
||||
<div className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(335px,_1fr))] pr-2">
|
||||
{data
|
||||
.filter((n) => applyFilter(n, filter))
|
||||
// filter out the cards (rewards) for which all of the markets
|
||||
// are settled
|
||||
.filter((n) => !areAllMarketsSettled(n))
|
||||
.map((node, i) => (
|
||||
<ActiveRewardCard
|
||||
key={i}
|
||||
|
@ -925,17 +925,8 @@ const EntityIcon = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const ActiveRewardCard = ({
|
||||
transferNode,
|
||||
currentEpoch,
|
||||
requirements,
|
||||
}: {
|
||||
transferNode: EnrichedRewardTransfer;
|
||||
currentEpoch: number;
|
||||
requirements?: Requirements;
|
||||
}) => {
|
||||
// don't display the cards that are scoped to not trading markets
|
||||
const marketSettled = transferNode.markets?.filter(
|
||||
export const areAllMarketsSettled = (transferNode: EnrichedRewardTransfer) => {
|
||||
const settledMarkets = transferNode.markets?.filter(
|
||||
(m) =>
|
||||
m?.state &&
|
||||
[
|
||||
@ -946,20 +937,40 @@ export const ActiveRewardCard = ({
|
||||
].includes(m.state)
|
||||
);
|
||||
|
||||
return (
|
||||
settledMarkets?.length === transferNode.markets?.length &&
|
||||
Boolean(transferNode.markets && transferNode.markets.length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
export const areAllMarketsSuspended = (
|
||||
transferNode: EnrichedRewardTransfer
|
||||
) => {
|
||||
return (
|
||||
transferNode.markets?.filter(
|
||||
(m) =>
|
||||
m?.state === MarketState.STATE_SUSPENDED ||
|
||||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||
).length === transferNode.markets?.length &&
|
||||
Boolean(transferNode.markets && transferNode.markets.length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
export const ActiveRewardCard = ({
|
||||
transferNode,
|
||||
currentEpoch,
|
||||
requirements,
|
||||
}: {
|
||||
transferNode: EnrichedRewardTransfer;
|
||||
currentEpoch: number;
|
||||
requirements?: Requirements;
|
||||
}) => {
|
||||
const startsIn = transferNode.transfer.kind.startEpoch - currentEpoch;
|
||||
const endsIn =
|
||||
transferNode.transfer.kind.endEpoch != null
|
||||
? transferNode.transfer.kind.endEpoch - currentEpoch
|
||||
: undefined;
|
||||
|
||||
// hide the card if all of the markets are being marked as e.g. settled
|
||||
if (
|
||||
marketSettled?.length === transferNode.markets?.length &&
|
||||
Boolean(transferNode.markets && transferNode.markets.length > 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!transferNode.transfer.kind.dispatchStrategy &&
|
||||
transferNode.transfer.toAccountType ===
|
||||
@ -987,17 +998,21 @@ export const ActiveRewardCard = ({
|
||||
transferNode.transfer.kind.dispatchStrategy.dispatchMetric
|
||||
];
|
||||
|
||||
// grey out of any of the markets is suspended or
|
||||
// if the asset is not currently traded on any of the active markets
|
||||
const marketSuspended =
|
||||
transferNode.markets?.filter(
|
||||
(m) =>
|
||||
m?.state === MarketState.STATE_SUSPENDED ||
|
||||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||
).length === transferNode.markets?.length &&
|
||||
Boolean(transferNode.markets && transferNode.markets.length > 0);
|
||||
|
||||
if (marketSuspended || !transferNode.isAssetTraded || startsIn > 0) {
|
||||
/**
|
||||
* Display the card as grey if any of the condition is `true`:
|
||||
*
|
||||
* - all markets scoped to the reward are settled
|
||||
* - all markets scoped to the reward are suspended
|
||||
* - the reward's asset is not actively traded on any of the active markets
|
||||
* - it start in the future
|
||||
*
|
||||
*/
|
||||
if (
|
||||
areAllMarketsSettled(transferNode) ||
|
||||
areAllMarketsSuspended(transferNode) ||
|
||||
!transferNode.isAssetTraded ||
|
||||
startsIn > 0
|
||||
) {
|
||||
colour = CardColour.GREY;
|
||||
}
|
||||
|
||||
|
4
apps/trading/e2e/poetry.lock
generated
4
apps/trading/e2e/poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
@ -1166,7 +1166,7 @@ profile = ["pytest-profiling", "snakeviz"]
|
||||
type = "git"
|
||||
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "e48b43322286b94680c4b0b94d5fe3bb2589c50c"
|
||||
resolved_reference = "c7685cf9eacd829dceab34f9ebe79c125b127a97"
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
|
@ -25,9 +25,10 @@ fragment GameFields on Game {
|
||||
}
|
||||
}
|
||||
|
||||
query Games($epochFrom: Int, $teamId: ID) {
|
||||
query Games($epochFrom: Int, $epochTo: Int, $teamId: ID) {
|
||||
games(
|
||||
epochFrom: $epochFrom
|
||||
epochTo: $epochTo
|
||||
teamId: $teamId
|
||||
entityScope: ENTITY_SCOPE_TEAMS
|
||||
) {
|
||||
|
11
apps/trading/lib/hooks/__generated__/Games.ts
generated
11
apps/trading/lib/hooks/__generated__/Games.ts
generated
@ -9,6 +9,7 @@ export type GameFieldsFragment = { __typename?: 'Game', id: string, epoch: numbe
|
||||
|
||||
export type GamesQueryVariables = Types.Exact<{
|
||||
epochFrom?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||
epochTo?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||
teamId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
@ -45,8 +46,13 @@ export const GameFieldsFragmentDoc = gql`
|
||||
}
|
||||
${TeamEntityFragmentDoc}`;
|
||||
export const GamesDocument = gql`
|
||||
query Games($epochFrom: Int, $teamId: ID) {
|
||||
games(epochFrom: $epochFrom, teamId: $teamId, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
query Games($epochFrom: Int, $epochTo: Int, $teamId: ID) {
|
||||
games(
|
||||
epochFrom: $epochFrom
|
||||
epochTo: $epochTo
|
||||
teamId: $teamId
|
||||
entityScope: ENTITY_SCOPE_TEAMS
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...GameFields
|
||||
@ -69,6 +75,7 @@ export const GamesDocument = gql`
|
||||
* const { data, loading, error } = useGamesQuery({
|
||||
* variables: {
|
||||
* epochFrom: // value for 'epochFrom'
|
||||
* epochTo: // value for 'epochTo'
|
||||
* teamId: // value for 'teamId'
|
||||
* },
|
||||
* });
|
||||
|
@ -1,13 +1,15 @@
|
||||
import {
|
||||
useGamesQuery,
|
||||
type GameFieldsFragment,
|
||||
type TeamEntityFragment,
|
||||
GamesDocument,
|
||||
type GamesQuery,
|
||||
} from './__generated__/Games';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { isSafeInteger, removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useEpochInfoQuery } from './__generated__/Epoch';
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import { useApolloClient, type ApolloError } from '@apollo/client';
|
||||
import { TEAMS_STATS_EPOCHS } from './constants';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
const findTeam = (entities: GameFieldsFragment['entities'], teamId: string) => {
|
||||
const team = entities.find(
|
||||
@ -30,10 +32,68 @@ export const areTeamGames = (games?: Game[]): games is TeamGame[] =>
|
||||
type GamesData = {
|
||||
data?: Game[];
|
||||
loading: boolean;
|
||||
error?: ApolloError;
|
||||
error?: Error | ApolloError;
|
||||
};
|
||||
|
||||
export const useGames = (teamId?: string, epochFrom?: number): GamesData => {
|
||||
const MAX_EPOCHS = 30;
|
||||
/**
|
||||
* Converts the given variables (`teamId`, `epochFrom`, `epochTo`) of
|
||||
* `GamesQuery` into chunks so that the maximum difference between given
|
||||
* `epochFrom` and `epochTo` is not greater than the limit of `MAX_EPOCHS`.
|
||||
*
|
||||
* Example: When `epochFrom == 1` and `epochTo == 59` this function should
|
||||
* produce an array of variables consisting of two entries where:
|
||||
* - 1st chunk: `epochFrom == 1` and `epochTo == 31`
|
||||
* - 2nd chunk: `epochFrom == 32` and `epochTo == 59`
|
||||
*/
|
||||
const prepareVariables = (
|
||||
teamId?: string,
|
||||
epochFrom?: number,
|
||||
epochTo?: number
|
||||
) => {
|
||||
let from = epochFrom;
|
||||
const to = epochTo;
|
||||
|
||||
if (isSafeInteger(from) && from < 1) from = 1; // make sure it's not negative
|
||||
|
||||
let variables = [
|
||||
{
|
||||
teamId,
|
||||
epochFrom: from,
|
||||
epochTo: to,
|
||||
},
|
||||
];
|
||||
|
||||
if (isSafeInteger(from) && isSafeInteger(to)) {
|
||||
// if the difference between "from" and "to" is greater than MAX_EPOCHS
|
||||
// then we need to divide the variables into N chunks.
|
||||
if (to - from > MAX_EPOCHS) {
|
||||
const N = Math.ceil((to - from) / MAX_EPOCHS);
|
||||
variables = Array(N)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
const segmentFrom = Number(from) + MAX_EPOCHS * i;
|
||||
let segmentTo = Number(from) + MAX_EPOCHS * (i + 1) - 1;
|
||||
if (segmentTo > to) segmentTo = to;
|
||||
return {
|
||||
teamId,
|
||||
epochFrom: segmentFrom,
|
||||
epochTo: segmentTo,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
};
|
||||
|
||||
export const useGames = (
|
||||
teamId?: string,
|
||||
epochFrom?: number,
|
||||
epochTo?: number
|
||||
): GamesData => {
|
||||
const client = useApolloClient();
|
||||
|
||||
const {
|
||||
data: epochData,
|
||||
loading: epochLoading,
|
||||
@ -42,22 +102,58 @@ export const useGames = (teamId?: string, epochFrom?: number): GamesData => {
|
||||
skip: Boolean(epochFrom),
|
||||
});
|
||||
|
||||
const [games, setGames] = useState<Game[] | undefined>(undefined);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | ApolloError | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const variables = useMemo(() => {
|
||||
let from = epochFrom;
|
||||
if (!from && epochData) {
|
||||
from = Number(epochData.epoch.id) - TEAMS_STATS_EPOCHS;
|
||||
if (from < 1) from = 1; // make sure it's not negative
|
||||
let to = epochTo;
|
||||
|
||||
if (epochData?.epoch.id && !epochFrom) {
|
||||
const currentEpoch = Number(epochData.epoch.id);
|
||||
from = currentEpoch - TEAMS_STATS_EPOCHS;
|
||||
to = currentEpoch;
|
||||
}
|
||||
|
||||
const { data, loading, error } = useGamesQuery({
|
||||
variables: {
|
||||
epochFrom: from,
|
||||
teamId: teamId,
|
||||
},
|
||||
skip: !from,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
context: { isEnlargedTimeout: true },
|
||||
});
|
||||
if (!from) return [];
|
||||
return prepareVariables(teamId, from, to);
|
||||
}, [epochData?.epoch.id, epochFrom, epochTo, teamId]);
|
||||
|
||||
/**
|
||||
* Because of the games API limitation to alway return max up to 30 epochs
|
||||
* worth of data (regardless of the actual span of given variables
|
||||
* `epochFrom` and `epochTo`) we need to do a trick of asking for longer
|
||||
* periods in a way of chunks that are then combined into one `games`.
|
||||
*
|
||||
* The code below uses the direct reference to the `ApolloClient` and runs
|
||||
* N queries (see `prepareVariables` function) in order to obtain the whole
|
||||
* set of data.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (loading || games || variables.length === 0) return;
|
||||
if (!loading) setLoading(true);
|
||||
const processChunks = async () => {
|
||||
const chunks = variables.map((v) =>
|
||||
client
|
||||
.query<GamesQuery>({
|
||||
query: GamesDocument,
|
||||
variables: v,
|
||||
context: { isEnlargedTimeout: true },
|
||||
})
|
||||
.then(({ data, loading, error }) => ({ data, loading, error }))
|
||||
.catch(() => {
|
||||
/* NOOP */
|
||||
})
|
||||
);
|
||||
try {
|
||||
const results = await Promise.allSettled(chunks);
|
||||
const games = results.reduce((all, r) => {
|
||||
if (r.status === 'fulfilled' && r.value) {
|
||||
const { data, error } = r.value;
|
||||
if (error) setError(error);
|
||||
const allGames = removePaginationWrapper(data?.games.edges);
|
||||
const allOrScoped = allGames
|
||||
.map((g) => ({
|
||||
@ -69,8 +165,18 @@ export const useGames = (teamId?: string, epochFrom?: number): GamesData => {
|
||||
if (!teamId) return true;
|
||||
return isTeamGame(g);
|
||||
});
|
||||
return [...all, ...allOrScoped];
|
||||
}
|
||||
return all;
|
||||
}, [] as Game[]);
|
||||
|
||||
const games = orderBy(allOrScoped, 'epoch', 'desc');
|
||||
if (games.length > 0) setGames(orderBy(games, 'epoch', 'desc'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
processChunks();
|
||||
}, [client, games, loading, teamId, variables]);
|
||||
|
||||
return {
|
||||
data: games,
|
||||
|
10
libs/types/src/__generated__/types.ts
generated
10
libs/types/src/__generated__/types.ts
generated
@ -7187,14 +7187,6 @@ export type UpdateReferralProgram = {
|
||||
windowLength: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type UpdateSpotInstrumentConfiguration = {
|
||||
__typename?: 'UpdateSpotInstrumentConfiguration';
|
||||
/** Instrument code, human-readable shortcode used to describe the instrument. */
|
||||
code: Scalars['String'];
|
||||
/** Instrument name */
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Update an existing spot market on Vega */
|
||||
export type UpdateSpotMarket = {
|
||||
__typename?: 'UpdateSpotMarket';
|
||||
@ -7206,8 +7198,6 @@ export type UpdateSpotMarket = {
|
||||
|
||||
export type UpdateSpotMarketConfiguration = {
|
||||
__typename?: 'UpdateSpotMarketConfiguration';
|
||||
/** Updated spot market instrument configuration. */
|
||||
instrument: UpdateSpotInstrumentConfiguration;
|
||||
/** Specifies how the liquidity fee for the market will be calculated */
|
||||
liquidityFeeSettings?: Maybe<LiquidityFeeSettings>;
|
||||
/** Specifies the liquidity provision SLA parameters */
|
||||
|
@ -302,3 +302,7 @@ export const toQUSD = (
|
||||
const qUSD = value.dividedBy(q);
|
||||
return qUSD;
|
||||
};
|
||||
|
||||
export const isSafeInteger = (x: unknown): x is number => {
|
||||
return x != null && typeof x === 'number' && Number.isSafeInteger(x);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user