diff --git a/apps/trading/components/rewards-container/active-rewards.spec.tsx b/apps/trading/components/rewards-container/active-rewards.spec.tsx index d4b506d33..4fa205a55 100644 --- a/apps/trading/components/rewards-container/active-rewards.spec.tsx +++ b/apps/trading/components/rewards-container/active-rewards.spec.tsx @@ -98,6 +98,7 @@ describe('ActiveRewards', () => { transferNode={mockTransferNode} currentEpoch={1} kind={mockRecurringTransfer} + allMarkets={{}} /> ); diff --git a/apps/trading/components/rewards-container/active-rewards.tsx b/apps/trading/components/rewards-container/active-rewards.tsx index 693913de8..199fa42b4 100644 --- a/apps/trading/components/rewards-container/active-rewards.tsx +++ b/apps/trading/components/rewards-container/active-rewards.tsx @@ -1,48 +1,46 @@ -import { - useActiveRewardsQuery, - useMarketForRewardsQuery, -} from './__generated__/Rewards'; +import { useActiveRewardsQuery } from './__generated__/Rewards'; import { useT } from '../../lib/use-t'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import classNames from 'classnames'; import { - Icon, type IconName, + type VegaIconSize, + Icon, Intent, Tooltip, VegaIcon, VegaIconNames, - type VegaIconSize, TradingInput, TinyScroll, } from '@vegaprotocol/ui-toolkit'; import { IconNames } from '@blueprintjs/icons'; import { + type Maybe, + type Transfer, + type TransferNode, + type RecurringTransfer, DistributionStrategyDescriptionMapping, DistributionStrategyMapping, EntityScope, EntityScopeMapping, - type Maybe, - type Transfer, - type TransferNode, TransferStatus, TransferStatusMapping, DispatchMetric, DispatchMetricDescription, DispatchMetricLabels, - type RecurringTransfer, EntityScopeLabelMapping, + MarketState, } from '@vegaprotocol/types'; import { Card } from '../card/card'; import { useMemo, useState } from 'react'; import { type AssetFieldsFragment, - useAssetDataProvider, useAssetsMapProvider, } from '@vegaprotocol/assets'; import { type MarketFieldsFragment, useMarketsMapProvider, + getAsset, } from '@vegaprotocol/markets'; export type Filter = { @@ -74,7 +72,7 @@ export const isActiveReward = (node: TransferNode, currentEpoch: number) => { export const applyFilter = ( node: TransferNode & { asset?: AssetFieldsFragment | null; - marketIds?: (MarketFieldsFragment | null)[]; + markets?: (MarketFieldsFragment | null)[]; }, filter: Filter ) => { @@ -85,6 +83,7 @@ export const applyFilter = ( ) { return false; } + if ( DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric] .toLowerCase() @@ -98,7 +97,7 @@ export const applyFilter = ( node.asset?.name .toLocaleLowerCase() .includes(filter.searchTerm.toLowerCase()) || - node.marketIds?.some((m) => + node.markets?.some((m) => m?.tradableInstrument?.instrument?.name .toLocaleLowerCase() .includes(filter.searchTerm.toLowerCase()) @@ -124,7 +123,7 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { const { data: assets } = useAssetsMapProvider(); const { data: markets } = useMarketsMapProvider(); - const transfers = activeRewardsData?.transfersConnection?.edges + const enrichedTransfers = activeRewardsData?.transfersConnection?.edges ?.map((e) => e?.node as TransferNode) .filter((node) => isActiveReward(node, currentEpoch)) .map((node) => { @@ -138,19 +137,19 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || '' ]; - const marketIds = + const marketsInScope = node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map( (id) => markets && markets[id] ); - return { ...node, asset, marketIds }; + return { ...node, asset, markets: marketsInScope }; }); - if (!transfers || !transfers.length) return null; + if (!enrichedTransfers || !enrichedTransfers.length) return null; return ( - {transfers.length > 1 && ( + {enrichedTransfers.length > 1 && ( setFilter((curr) => ({ ...curr, searchTerm: e.target.value })) @@ -166,7 +165,7 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { /> )} - {transfers + {enrichedTransfers .filter((n) => applyFilter(n, filter)) .map((node, i) => { const { transfer } = node; @@ -184,6 +183,7 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { transferNode={node} kind={transfer.kind} currentEpoch={currentEpoch} + allMarkets={markets || {}} /> ) ); @@ -207,14 +207,8 @@ const StatusIndicator = ({ switch (status) { case TransferStatus.STATUS_DONE: return { icon: IconNames.TICK_CIRCLE, intent: Intent.Success }; - case TransferStatus.STATUS_CANCELLED: - return { icon: IconNames.MOON, intent: Intent.None }; - case TransferStatus.STATUS_PENDING: - return { icon: IconNames.HELP, intent: Intent.Primary }; case TransferStatus.STATUS_REJECTED: return { icon: IconNames.ERROR, intent: Intent.Danger }; - case TransferStatus.STATUS_STOPPED: - return { icon: IconNames.ERROR, intent: Intent.Danger }; default: return { icon: IconNames.HELP, intent: Intent.Primary }; } @@ -253,49 +247,117 @@ export const ActiveRewardCard = ({ transferNode, currentEpoch, kind, + allMarkets, }: { - transferNode: TransferNode; + transferNode: TransferNode & { + asset?: AssetFieldsFragment | null; + markets?: (MarketFieldsFragment | null)[]; + }; currentEpoch: number; kind: RecurringTransfer; + allMarkets?: Record; }) => { const t = useT(); const { transfer } = transferNode; const { dispatchStrategy } = kind; - const marketIds = dispatchStrategy?.marketIdsInScope; - const { data: marketNameData } = useMarketForRewardsQuery({ - variables: { - marketId: marketIds ? marketIds[0] : '', - }, - }); + const marketIdsInScope = dispatchStrategy?.marketIdsInScope; + const firstMarketData = transferNode.markets?.[0]; - const marketName = useMemo(() => { - if (marketNameData && marketIds && marketIds.length > 1) { - return 'Specific markets'; - } else if ( - marketNameData && - marketIds && - marketNameData && - marketIds.length === 1 + const specificMarkets = useMemo(() => { + if ( + !firstMarketData || + !marketIdsInScope || + marketIdsInScope.length === 0 ) { - return marketNameData?.market?.tradableInstrument?.instrument?.name || ''; + return null; } - return ''; - }, [marketIds, marketNameData]); + if (marketIdsInScope.length > 1) { + const marketNames = + allMarkets && + marketIdsInScope + .map((id) => allMarkets[id]?.tradableInstrument?.instrument?.name) + .join(', '); - const { data: dispatchAsset } = useAssetDataProvider( - dispatchStrategy?.dispatchMetricAssetId || '' - ); + return ( + + Specific markets + + ); + } + return ( + {firstMarketData?.tradableInstrument?.instrument?.name || ''} + ); + }, [firstMarketData, marketIdsInScope, allMarkets]); + + const dispatchAsset = transferNode.asset; if (!dispatchStrategy) { return null; } - const { gradientClassName, mainClassName } = getGradientClasses( - dispatchStrategy.dispatchMetric + // Gray out/hide the cards that are related to not trading markets + const marketSettled = transferNode.markets?.some( + (m) => + m?.state && + [ + MarketState.STATE_TRADING_TERMINATED, + MarketState.STATE_SETTLED, + MarketState.STATE_CANCELLED, + MarketState.STATE_CLOSED, + ].includes(m.state) ); + const assetInSettledMarket = + allMarkets && + Object.values(allMarkets).some((m: MarketFieldsFragment | null) => { + if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) { + return ( + m?.state && + [ + MarketState.STATE_TRADING_TERMINATED, + MarketState.STATE_SETTLED, + MarketState.STATE_CANCELLED, + MarketState.STATE_CLOSED, + ].includes(m.state) + ); + } + return false; + }); + + if (marketSettled) { + return null; + } + + // Gray out the cards that are related to suspended markets + const suspended = transferNode.markets?.some( + (m) => + m?.state === MarketState.STATE_SUSPENDED || + m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE + ); + + const assetInSuspendedMarket = + allMarkets && + Object.values(allMarkets).some((m: MarketFieldsFragment | null) => { + if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) { + return ( + m?.state === MarketState.STATE_SUSPENDED || + m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE + ); + } + return false; + }); + + // Gray out the cards that are related to suspended markets + const { gradientClassName, mainClassName } = + suspended || assetInSuspendedMarket || assetInSettledMarket + ? { + gradientClassName: 'from-vega-cdark-500 to-vega-clight-400', + mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%', + } + : getGradientClasses(dispatchStrategy.dispatchMetric); + const entityScope = dispatchStrategy.entityScope; return (
@@ -373,8 +435,20 @@ export const ActiveRewardCard = ({ - {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} - {marketName ? ` • ${marketName}` : ` • ${dispatchAsset?.name}`} + {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} •{' '} + + {specificMarkets || dispatchAsset?.name} +
diff --git a/apps/trading/e2e/tests/rewards/test_filtered_cards.py b/apps/trading/e2e/tests/rewards/test_filtered_cards.py new file mode 100644 index 000000000..e05346a0c --- /dev/null +++ b/apps/trading/e2e/tests/rewards/test_filtered_cards.py @@ -0,0 +1,67 @@ +import pytest +import vega_sim.proto.vega as vega_protos +from playwright.sync_api import Page, expect +from vega_sim.null_service import VegaServiceNull +from actions.utils import next_epoch +from wallet_config import MM_WALLET, PARTY_A, PARTY_B +from vega_sim.service import MarketStateUpdateType +import vega_sim.api.governance as governance + + +@pytest.mark.usefixtures("risk_accepted", "auth") +def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page): + tDAI_asset_id = vega.find_asset_id(symbol="tDAI") + vega.update_network_parameter( + MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id + ) + vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000) + vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000) + next_epoch(vega=vega) + vega.recurring_transfer( + from_key_name=PARTY_A.name, + from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL, + to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + asset=tDAI_asset_id, + reference="reward", + markets=[continuous_market], + asset_for_metric=tDAI_asset_id, + metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID, + lock_period=5, + amount=100, + factor=1.0, + ) + vega.submit_order( + trading_key=PARTY_B.name, + market_id=continuous_market, + order_type="TYPE_MARKET", + time_in_force="TIME_IN_FORCE_IOC", + side="SIDE_BUY", + volume=1, + ) + vega.submit_order( + trading_key=PARTY_A.name, + market_id=continuous_market, + order_type="TYPE_MARKET", + time_in_force="TIME_IN_FORCE_IOC", + side="SIDE_BUY", + volume=1, + ) + next_epoch(vega=vega) + + vega.update_market_state( + market_id=continuous_market, + proposal_key=MM_WALLET.name, + market_state=MarketStateUpdateType.Suspend, + forward_time_to_enactment=True, + ) + next_epoch(vega=vega) + page.goto("/#/rewards") + expect(page.locator(".from-vega-cdark-400")).to_be_visible() + governance.submit_oracle_data( + wallet=vega.wallet, + payload={"trading.terminated": "true"}, + key_name="FJMKnwfZdd48C8NqvYrG", + ) + next_epoch(vega=vega) + page.reload() + expect(page.locator(".from-vega-cdark-400")).not_to_be_in_viewport()