Compare commits

..
Author SHA1 Message Date
bwallacee 59a83bab13 chore(trading): re enable parralel run 2024-02-14 09:18:48 +00:00
bwallacee 4db5f19c91 chore(trading): fix teams test and update vega-sim 2024-02-14 08:21:28 +00:00
Dariusz Majcherczyk 0e60c88937 chore(trading): update vega sim 2024-02-14 07:57:40 +00:00
Dariusz Majcherczyk db8314f865 chore(trading): update vega version for sim tests 2024-02-14 07:57:40 +00:00
JonRay15 9195bf8c91 Merge pull request #5789 from vegaprotocol/fix/5677-gov-rewards-table
fix(governance): dont show all reward types in gov rewards table
2024-02-13 16:18:02 +00:00
Jeremy Letang cd481512f3 Merge pull request #5771 from vegaprotocol/feat/4306-transfer-status
feat(explorer): transfer status and games overview
2024-02-13 16:17:14 +00:00
Matthew Russell 8fe6f3f5f2 chore(trading): misc fixes (#5785) 2024-02-13 16:17:00 +00:00
Art 1c2389dee5 fix(trading): game cards colour, missing market info, other tweaks (#5791) 2024-02-13 13:38:01 +00:00
Jeremy Letang 1e6a2debfc Merge pull request #5783 from vegaprotocol/5781-update-EstimatePosition-argument
feat(deal-ticket): rename EstimatePosition argument
2024-02-13 10:26:02 +00:00
Matthew Russell fc2773d748 fix: make sure asset is not added to list if it doesn't container correct reward types 2024-02-12 13:42:49 -08:00
Matthew Russell b2860121e5 fix: dont show unrelated rewards in gov rewards table 2024-02-12 13:32:45 -08:00
Matthew Russell db5e5ee782 chore(trading, governance, explorer): update snap to 1.0.1 (#5768) 2024-02-12 18:38:31 +00:00
Art 0d3bcf05a1 chore(trading): team page refactor, games with specified epoch from (#5772) 2024-02-12 15:03:21 +01:00
Matthew RussellandMadalina Raicu c7dd5e846a fix(trading): validate whitespace team names, show empty in list (#5727)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2024-02-12 13:23:12 +00:00
Dariusz Majcherczyk 61e5941751 fix(deal-ticket): margin test 2024-02-09 21:14:36 +01:00
Bartłomiej Głownia 8db286fa93 feat(deal-ticket): rename EstimatePosition argument 2024-02-09 15:02:09 +01:00
Edd e5635cae61 test(explorer): add tests for games display 2024-02-08 12:26:32 +00:00
Edd b01c67ced5 chore(explorer): update transfer status query to match latest preview 2024-02-08 10:54:00 +00:00
Edd 636b1f98db chore(explorer): relayout transfers 2024-02-08 10:51:10 +00:00
Edd c31a927526 chore(explorer): progress on rank table 2024-02-08 10:51:10 +00:00
Edd 5803d6e890 feat(explorer): complete transfer status 2024-02-08 10:51:10 +00:00
Edd cb0dd17839 chore(explorer): more fields for rewards 2024-02-08 10:51:10 +00:00
Edd 496b0b5a90 feat(explorer): extend transfers view 2024-02-08 10:51:10 +00:00
55 changed files with 990 additions and 292 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ on:
inputs:
console-test-branch:
type: choice
description: 'main: v0.73.5, develop: v0.73.5'
description: 'main: v0.73.13, develop: v0.74.0'
options:
- main
- develop
@@ -64,7 +64,9 @@ export const ProposalSummary = ({
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
{id && <ProposalStatusIcon id={id} />}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
{rationale?.title && (
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
)}
{rationale?.description && (
<div className="pt-2 text-sm leading-tight">
<ReactMarkdown
@@ -0,0 +1,18 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
@@ -0,0 +1,61 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export const ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
`;
/**
* __useExplorerTransferStatusQuery__
*
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerTransferStatusQuery` 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 } = useExplorerTransferStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
@@ -111,7 +111,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-vega-light-100 dark:fill-black"
className="fill-white dark:fill-black"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -120,7 +120,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-vega-light-100 dark:fill-vega-dark-200"
className="fill-vega-light-200 dark:fill-vega-dark-200"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -1,97 +1,223 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink } from '../../../../links';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer';
import type { Recurring } from '../transfer-details';
import { DispatchMetricLabels } from '@vegaprotocol/types';
import {
DispatchMetricLabels,
DistributionStrategy,
} from '@vegaprotocol/types';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy'];
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
const metricLabels: Record<Metric, string> = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels,
};
// Maps the two (non-null) values of entityScope to the icon that represents it
const entityScopeIcons: Record<
string,
typeof VegaIconNames[keyof typeof VegaIconNames]
> = {
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
};
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
};
interface TransferRewardsProps {
recurring: Recurring;
}
/**
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
* Renders recurring transfers/game details in a way that is, perhaps, easy to understand
*
* @param transfer A recurring transfer object
*/
export function TransferRewards({ recurring }: TransferRewardsProps) {
const metric =
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
if (!recurring || !recurring.dispatchStrategy) {
return null;
}
// Destructure to make things a bit more readable
const {
entityScope,
individualScope,
teamScope,
distributionStrategy,
lockPeriod,
markets,
stakingRequirement,
windowLength,
notionalTimeWeightedAveragePositionRequirement,
rankTable,
nTopPerformers,
} = recurring.dispatchStrategy;
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
<ul className="relative block rounded-lg py-6 text-center p-6">
{recurring.dispatchStrategy.assetForMetric ? (
<h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
<ul className="relative block rounded-lg py-6 text-left p-6">
{entityScope && entityScopeIcons[entityScope] ? (
<li>
<strong>{t('Asset')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
<strong>{t('Scope')}</strong>:{' '}
<VegaIcon name={entityScopeIcons[entityScope]} />
&nbsp;
{individualScope ? individualScopeLabels[individualScope] : null}
{getScopeLabel(entityScope, teamScope)}
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
</li>
{recurring.dispatchStrategy.markets &&
recurring.dispatchStrategy.markets.length > 0 ? (
{recurring.dispatchStrategy &&
recurring.dispatchStrategy.assetForMetric && (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
)}
{recurring.dispatchStrategy.metric &&
metricLabels[recurring.dispatchStrategy.metric] && (
<li>
<strong>{t('Metric')}</strong>:{' '}
{metricLabels[recurring.dispatchStrategy.metric]}
</li>
)}
{lockPeriod && (
<li>
<strong>{t('Reward lock')}</strong>:&nbsp;
{recurring.dispatchStrategy.lockPeriod}{' '}
{recurring.dispatchStrategy.lockPeriod === '1'
? t('epoch')
: t('epochs')}
</li>
)}
{markets && markets.length > 0 ? (
<li>
<strong>{t('Markets in scope')}</strong>:
<ul>
{recurring.dispatchStrategy.markets.map((m) => (
<li key={m}>
<ul className="inline-block ml-1">
{markets.map((m) => (
<li key={m} className="inline-block mr-2">
<MarketLink id={m} />
</li>
))}
</ul>
</li>
) : null}
<li>
<strong>{t('Factor')}</strong>: {recurring.factor}
</li>
{stakingRequirement && stakingRequirement !== '0' ? (
<li>
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
</li>
) : null}
{windowLength && windowLength !== '0' ? (
<li>
<strong>{t('Window length')}</strong>:{' '}
{recurring.dispatchStrategy.windowLength}{' '}
{recurring.dispatchStrategy.windowLength === '1'
? t('epoch')
: t('epochs')}
</li>
) : null}
{notionalTimeWeightedAveragePositionRequirement &&
notionalTimeWeightedAveragePositionRequirement !== '' ? (
<li>
<strong>{t('Notional TWAP')}</strong>:{' '}
{notionalTimeWeightedAveragePositionRequirement}
</li>
) : null}
{nTopPerformers && (
<li>
<strong>{t('Elligible team members:')}</strong> top{' '}
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
</li>
)}
{distributionStrategy &&
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
<li>
<strong>{t('Distribution strategy')}</strong>:{' '}
{distributionStrategyLabel[distributionStrategy]}
</li>
)}
</ul>
<div className="px-6 pt-1 pb-5">
{rankTable && rankTable.length > 0 ? (
<table className="border-collapse border border-gray-400 ">
<thead>
<tr>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Start rank')}</strong>
</th>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Share of reward pool')}</strong>
</th>
</tr>
</thead>
<tbody>
{rankTable.map((row, i) => {
return (
<tr key={`rank-${i}`}>
<td className="border border-slate-300 text-center">
{row.startRank}
</td>
<td className="border border-slate-300 text-center">
{row.shareRatio}
</td>
</tr>
);
})}
</tbody>
</table>
) : null}
</div>
</div>
);
}
interface TransferRecurringStrategyProps {
strategy: Strategy;
}
/**
* Simple renderer for a dispatch strategy in a recurring transfer
*
* @param strategy Dispatch strategy object
*/
export function TransferRecurringStrategy({
strategy,
}: TransferRecurringStrategyProps) {
if (!strategy) {
return null;
export function getScopeLabel(
scope: components['schemas']['vegaEntityScope'] | undefined,
teamScope: readonly string[] | undefined
): string {
if (scope === 'ENTITY_SCOPE_TEAMS') {
if (teamScope && teamScope.length !== 0) {
return ` ${teamScope.length} teams`;
} else {
return t('All teams');
}
} else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
return t('Individuals');
} else {
return '';
}
return (
<>
{strategy.assetForMetric ? (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={strategy.assetForMetric} />
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {strategy.metric}
</li>
</>
);
}
export function getRewardTitle(
scope?: components['schemas']['vegaEntityScope']
) {
if (scope === 'ENTITY_SCOPE_TEAMS') {
return t('Game');
}
return t('Reward metrics');
}
const individualScopeLabels: Record<
components['schemas']['vegaIndividualScope'],
string
> = {
// Unspecified and All are not rendered
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
INDIVIDUAL_SCOPE_ALL: '',
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
};
@@ -0,0 +1,84 @@
import { t } from '@vegaprotocol/i18n';
import { headerClasses, wrapperClasses } from '../transfer-details';
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import type { ApolloError } from '@apollo/client';
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
import { IconNames } from '@blueprintjs/icons';
interface TransferStatusProps {
status: TransferStatus | undefined;
error: ApolloError | undefined;
loading: boolean;
}
/**
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferStatusView({ status, loading }: TransferStatusProps) {
if (!status) {
status = TransferStatus.STATUS_PENDING;
}
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Status')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
{loading ? (
<div className="leading-10 mt-12">
<Loader size={'small'} />
</div>
) : (
<>
<p className="leading-10 my-2">
<Icon
name={getIconForStatus(status)}
className={getColourForStatus(status)}
/>
</p>
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
</>
)}
</div>
</div>
);
}
/**
* Simple mapping from status to icon name
* @param status TransferStatus
* @returns IconName
*/
export function getIconForStatus(status: TransferStatus): IconName {
switch (status) {
case TransferStatus.STATUS_PENDING:
return IconNames.TIME;
case TransferStatus.STATUS_DONE:
return IconNames.TICK;
case TransferStatus.STATUS_REJECTED:
return IconNames.CROSS;
default:
return IconNames.TIME;
}
}
/**
* Simple mapping from status to colour
* @param status TransferStatus
* @returns string Tailwind classname
*/
export function getColourForStatus(status: TransferStatus): string {
switch (status) {
case TransferStatus.STATUS_PENDING:
return 'text-yellow-500';
case TransferStatus.STATUS_DONE:
return 'text-green-500';
case TransferStatus.STATUS_REJECTED:
return 'text-red-500';
default:
return 'text-yellow-500';
}
}
@@ -2,12 +2,15 @@ import type { components } from '../../../../../types/explorer';
import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants';
import { useExplorerTransferStatusQuery } from './__generated__/Transfer';
import { TransferStatusView } from './blocks/transfer-status';
import { TransferStatus } from '@vegaprotocol/types';
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses =
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
@@ -16,6 +19,7 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
interface TransferDetailsProps {
transfer: Transfer;
from: string;
id: string;
}
/**
@@ -24,13 +28,24 @@ interface TransferDetailsProps {
*
* @param transfer A recurring transfer object
*/
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
const recurring = transfer.recurring;
// Currently all this is passed in to TransferStatus, but the extra details
// may be useful in the future.
const { data, error, loading } = useExplorerTransferStatusQuery({
variables: { id },
});
const status = error
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
return (
<div className="flex gap-5 flex-wrap">
<div className="flex flex-wrap">
<TransferParticipants from={from} transfer={transfer} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? (
<TransferRewards recurring={transfer.recurring} />
) : null}
@@ -0,0 +1,172 @@
import {
getScopeLabel,
getRewardTitle,
TransferRewards,
} from './blocks/transfer-rewards';
import { render } from '@testing-library/react';
import type { components } from '../../../../../types/explorer';
import type { Recurring } from './transfer-details';
import {
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getScopeLabel', () => {
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = ['team1', 'team2', 'team3'];
const expectedLabel = ' 3 teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = undefined;
const expectedLabel = 'All teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const teamScope = undefined;
const expectedLabel = 'Individuals';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return an empty string for unknown scope', () => {
const scope = 'UNKNOWN_SCOPE';
const teamScope = undefined;
const expectedLabel = '';
const result = getScopeLabel(
scope as unknown as components['schemas']['vegaEntityScope'],
teamScope
);
expect(result).toEqual(expectedLabel);
});
});
describe('getRewardTitle', () => {
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const expectedTitle = 'Game';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
it('should return the correct title for other scopes', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const expectedTitle = 'Reward metrics';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
});
describe('TransferRewards', () => {
it('should render nothing if recurring dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={null as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={{} as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render the reward details correctly', () => {
const recurring = {
dispatchStrategy: {
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
assetForMetric: '123',
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
teamScope: [],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: '1',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
rankTable: [
{ startRank: 1, shareRatio: 0.2 },
{ startRank: 2, shareRatio: 0.3 },
],
nTopPerformers: 'nTopPerformers',
},
};
const { getByText } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(getByText('Game')).toBeInTheDocument();
expect(getByText('Scope')).toBeInTheDocument();
expect(getByText('Asset for metric')).toBeInTheDocument();
expect(getByText('Metric')).toBeInTheDocument();
expect(getByText('Reward lock')).toBeInTheDocument();
expect(getByText('Markets in scope')).toBeInTheDocument();
expect(getByText('Staking requirement')).toBeInTheDocument();
expect(getByText('Window length')).toBeInTheDocument();
expect(getByText('Notional TWAP')).toBeInTheDocument();
expect(getByText('Elligible team members:')).toBeInTheDocument();
expect(getByText('Distribution strategy')).toBeInTheDocument();
expect(getByText('Start rank')).toBeInTheDocument();
expect(getByText('Share of reward pool')).toBeInTheDocument();
});
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
const recurring = {
dispatchStrategy: {
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: ['team1', 'team2', 'team3'],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: 'stakingRequirement',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
nTopPerformers: 'nTopPerformers',
},
};
const { container } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(container.querySelector('table')).toBeNull();
});
});
@@ -12,6 +12,8 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
import { MarketLink } from '../../links';
import { formatNumber } from '@vegaprotocol/utils';
import { TransferDetails } from './transfer/transfer-details';
import { proposalToTransfer } from '../lib/proposal-to-transfer';
export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
@@ -104,6 +106,12 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
? ProposalSignatureBundleNewAsset
: ProposalSignatureBundleUpdateAsset;
let transfer, from;
if (proposal.terms?.newTransfer?.changes) {
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
from = proposal.terms.newTransfer.changes.source;
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -149,14 +157,26 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
</>
) : null}
</TableWithTbody>
<ProposalSummary
id={deterministicId}
rationale={proposal.rationale}
terms={proposal?.terms}
/>
{proposalRequiresSignatureBundle(proposal) && (
<SignatureBundleComponent id={deterministicId} tx={tx} />
)}
{transfer && (
<div className="mt-8">
<TransferDetails
transfer={transfer}
from={from || ''}
id={deterministicId}
/>
</div>
)}
</>
);
};
@@ -13,6 +13,7 @@ import {
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import Hash from '../../links/hash';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -60,7 +61,7 @@ export const TxDetailsTransfer = ({
}
const from = txData.submitter;
const id = txSignatureToDeterministicId(txData.signature.value);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -71,7 +72,7 @@ export const TxDetailsTransfer = ({
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
<Hash text={id} />
</TableCell>
</TableRow>
<TxDetailsShared
@@ -105,7 +106,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
</TableWithTbody>
<TransferDetails from={from} transfer={transfer} />
<TransferDetails from={from} transfer={transfer} id={id} />
</>
);
};
@@ -0,0 +1,29 @@
import type { components } from '../../../../types/explorer';
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
type ActualTransfer = components['schemas']['commandsv1Transfer'];
/**
* Converts a governance proposal for a transfer in to a transfer command that the
* TransferDetails component can then render. The types are very similar, but do not
* map precisely to each other due to some missing fields and some different field
* names.
*
* @param proposal Governance proposal for a transfer
* @returns transfer a Transfer object as if it had been submitted
*/
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
return {
amount: proposal.amount,
asset: proposal.asset,
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
// fromAccountType does exist and is just named differently on the proposal
fromAccountType: proposal.sourceType,
oneOff: proposal.oneOff,
recurring: proposal.recurring,
// There is no reference applied on governance initiated transfers
reference: '',
to: proposal.destination,
toAccountType: proposal.destinationType,
};
}
@@ -241,6 +241,16 @@ describe('generateEpochAssetRewardsList', () => {
amount: '5',
},
},
{
// This should not be included in the result
node: {
epoch: 2,
assetId: '3',
decimals: 18,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY,
amount: '5',
},
},
],
},
epoch: {
@@ -83,6 +83,12 @@ export const generateEpochTotalRewardsList = ({
(Number(rewardItem?.amount) || 0) + Number(reward.amount)
).toString();
// only RowAccountTypes are relevant for this table, others should
// be discarded
if (!Object.keys(RowAccountTypes).includes(reward.rewardType)) {
return acc;
}
rewards?.set(reward.rewardType, {
rewardType: reward.rewardType,
amount,
@@ -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,
});
@@ -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('Results {{games}}', {
replace: {
games: gamesLoading ? '' : games ? `(${games.length})` : '(0)',
},
})}
</ToggleButton>
<ToggleButton
active={!showGames}
@@ -125,17 +140,35 @@ const TeamPage = ({
})}
</ToggleButton>
</div>
{showGames ? <Games games={games} /> : <Members members={members} />}
{showGames ? (
<Games games={games} gamesLoading={gamesLoading} />
) : (
<Members members={members} />
)}
</section>
</LayoutWithGradient>
);
};
const Games = ({ games }: { games?: TeamGame[] }) => {
const Games = ({
games,
gamesLoading,
}: {
games?: TeamGame[];
gamesLoading?: boolean;
}) => {
const t = useT();
if (gamesLoading) {
return (
<div className="w-[15px]">
<Loader size="small" />
</div>
);
}
if (!games?.length) {
return <p>{t('No games')}</p>;
return <p>{t('No game results available')}</p>;
}
return (
@@ -46,7 +46,7 @@ const prepareTransaction = (
createReferralSet: {
isTeam: true,
team: {
name: fields.name,
name: fields.name.trim(),
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
@@ -62,7 +62,7 @@ const prepareTransaction = (
id: fields.id,
isTeam: true,
team: {
name: fields.name,
name: fields.name.trim(),
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
@@ -116,7 +116,17 @@ export const TeamForm = ({
<input type="hidden" {...register('id')} />
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput
{...register('name', { required: t('Required') })}
{...register('name', {
required: t('Required'),
validate: {
notEmpty: (value) => {
if (/^\s*$/.test(value)) {
return t('Team name cannot be empty');
}
return true;
},
},
})}
data-testid="team-name-input"
/>
{errors.name?.message && (
+6 -13
View File
@@ -1,27 +1,20 @@
import { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
import { ErrorBoundary } from '../../components/error-boundary';
import { FeesContainer } from '../../components/fees-container';
import { useT } from '../../lib/use-t';
import { usePageTitleStore } from '../../stores';
import { usePageTitle } from '../../lib/hooks/use-page-title';
export const Fees = () => {
const t = useT();
const title = t('Fees');
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
useEffect(() => {
updateTitle(titlefy([title]));
}, [updateTitle, title]);
usePageTitle(title);
return (
<ErrorBoundary feature="fees">
<div className="container p-4 mx-auto">
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
<TinyScroll className="p-4 max-h-full overflow-auto">
<h1 className="md:px-4 pb-4 text-2xl">{title}</h1>
<FeesContainer />
</div>
</TinyScroll>
</ErrorBoundary>
);
};
@@ -1,5 +1,3 @@
import React, { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import {
LocalStoragePersistTabs as Tabs,
Tab,
@@ -7,7 +5,6 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { OpenMarkets } from './open-markets';
import { Proposed } from './proposed';
import { usePageTitleStore } from '../../stores';
import { Closed } from './closed';
import {
DApp,
@@ -17,19 +14,14 @@ import {
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
import { MarketsSettings } from './markets-settings';
import { usePageTitle } from '../../lib/hooks/use-page-title';
export const MarketsPage = () => {
const t = useT();
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
const governanceLink = useLinks(DApp.Governance);
const externalLink = governanceLink(TOKEN_NEW_MARKET_PROPOSAL);
useEffect(() => {
updateTitle(titlefy([t('Markets')]));
}, [updateTitle, t]);
usePageTitle(t('Markets'));
return (
<div className="h-full pt-0.5 pb-3 px-1.5">
@@ -1,10 +1,8 @@
import { useEffect } from 'react';
import type { ReactNode } from 'react';
import { LayoutPriority } from 'allotment';
import { titlefy } from '@vegaprotocol/utils';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import {
AccountsContainer,
AccountsSettings,
@@ -41,6 +39,7 @@ import { WithdrawalsMenu } from '../../components/withdrawals-menu';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
import { usePageTitle } from '../../lib/hooks/use-page-title';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -69,14 +68,7 @@ const SidebarViewInitializer = () => {
export const Portfolio = () => {
const t = useT();
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
useEffect(() => {
updateTitle(titlefy([t('Portfolio')]));
}, [updateTitle, t]);
usePageTitle(t('Portfolio'));
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
@@ -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';
@@ -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 }) => {
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<div className="flex flex-col items-start gap-1 lg:gap-3">
<h1 className="calt text-2xl lg:text-3xl xl:text-5xl">{team.name}</h1>
<TeamStats members={members} games={games} />
<TeamStats
members={members}
games={areTeamGames(games) ? games : undefined}
/>
</div>
</Tile>
);
+5 -11
View File
@@ -1,24 +1,18 @@
import { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { RewardsContainer } from '../../components/rewards-container';
import { usePageTitleStore } from '../../stores';
import { ErrorBoundary } from '../../components/error-boundary';
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
import { usePageTitle } from '../../lib/hooks/use-page-title';
export const Rewards = () => {
const t = useT();
const title = t('Rewards');
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
useEffect(() => {
updateTitle(titlefy([title]));
}, [updateTitle, title]);
usePageTitle(title);
return (
<ErrorBoundary feature="rewards">
<TinyScroll className="p-4 max-h-full overflow-auto">
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
<h1 className="md:px-4 pb-4 text-2xl">{title}</h1>
<RewardsContainer />
</TinyScroll>
</ErrorBoundary>
@@ -153,5 +153,8 @@ const cacheConfig: InMemoryCacheConfig = {
OrderUpdate: {
keyFields: false,
},
Game: {
keyFields: false,
},
},
};
@@ -56,7 +56,10 @@ export const CompetitionsLeaderboard = ({
className="hover:underline"
to={Links.COMPETITIONS_TEAM(td.teamId)}
>
{td.name}
{
// Its possible for a tx to be submitted with an empty space as team name
td.name.trim() !== '' ? td.name : t('[empty]')
}
</Link>
),
earned: num(td.totalQuantumRewards),
@@ -68,6 +68,7 @@ export const GamesContainer = ({
transferNode={game}
currentEpoch={currentEpoch}
kind={transfer.kind}
allMarkets={markets || undefined}
/>
);
})}
@@ -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,
@@ -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,
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.0-preview.10
VEGA_VERSION=v0.74.1
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.74.0-preview.6
VEGA_VERSION=v0.74.1
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.10
VEGA_VERSION=v0.73.13
LOCAL_SERVER=false
+1 -1
View File
@@ -1161,7 +1161,7 @@ profile = ["pytest-profiling", "snakeviz"]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "HEAD"
resolved_reference = "026976549c21e59f6f9c48f06ab15a210c5a5bf3"
resolved_reference = "a8afded34874a01cfd1bb771052aa12a062960b9"
[[package]]
name = "websocket-client"
@@ -15,7 +15,8 @@ deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega_instance:
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
request.addfinalizer(lambda: cleanup_container(
vega_instance)) # Register the cleanup function
yield vega_instance
@@ -24,6 +25,7 @@ def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.skip("tbd - issue only on the sim, should work in vega 0.74.0")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
+15 -11
View File
@@ -129,7 +129,8 @@ def setup_teams_and_games(vega: VegaServiceNull):
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.recurring_transfer(
# this recurring transfer has been commented out as there appears to be a bug where individual rewards earned are showing on the teams page
""" vega.recurring_transfer(
from_key_name=PARTY_C.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
@@ -143,7 +144,7 @@ def setup_teams_and_games(vega: VegaServiceNull):
amount=100,
factor=1.0,
window_length=15
)
) """
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity")
@@ -212,15 +213,17 @@ def create_team(vega: VegaServiceNull):
def test_team_page_games_table(team_page: Page):
team_page.pause()
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("rank-0")).to_have_text("2")
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Results (10)")
expect(team_page.get_by_test_id("rank-0")).to_have_text("1")
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")
#TODO skipped as the amount is wrong
#expect(team_page.get_by_test_id("amount-0")).to_have_text("74") # 50,000,000 on 74.1
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")
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("3")
def test_team_page_members_table(team_page: Page):
@@ -237,12 +240,12 @@ def test_team_page_headline(team_page: Page, setup_teams_and_games):
expect(team_page.get_by_test_id("team-name")).to_have_text(team_name)
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text("1")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text("2")
# TODO this still seems wrong as its always 0
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text("0")
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("78")
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("1.2k")
def test_switch_teams(team_page: Page, vega: VegaServiceNull):
@@ -259,6 +262,7 @@ def test_switch_teams(team_page: Page, vega: VegaServiceNull):
def test_leaderboard(competitions_page: Page, setup_teams_and_games):
team_name = setup_teams_and_games["team_name"]
competitions_page.reload()
competitions_page.pause()
expect(
competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")
).to_have_count(1)
@@ -266,15 +270,15 @@ def test_leaderboard(competitions_page: Page, setup_teams_and_games):
competitions_page.get_by_test_id(
"rank-1").locator(".text-vega-clight-500")
).to_have_count(1)
expect(competitions_page.get_by_test_id("team-1")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("team-0")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-1")).to_have_text("Open")
# FIXME: the numbers are different we need to clarify this with the backend
# expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160")
expect(competitions_page.get_by_test_id("games-1")).to_have_text("1")
expect(competitions_page.get_by_test_id("games-1")).to_have_text("2")
# TODO still odd that this is 0
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-")
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("0")
def test_game_card(competitions_page: Page):
+35
View File
@@ -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
}
}
}
}
-29
View File
@@ -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
+83
View File
@@ -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<Types.Scalars['Int']>;
}>;
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<GamesQuery, GamesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GamesQuery, GamesQueryVariables>(GamesDocument, options);
}
export function useGamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GamesQuery, GamesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GamesQuery, GamesQueryVariables>(GamesDocument, options);
}
export type GamesQueryHookResult = ReturnType<typeof useGamesQuery>;
export type GamesLazyQueryHookResult = ReturnType<typeof useGamesLazyQuery>;
export type GamesQueryResult = Apollo.QueryResult<GamesQuery, GamesQueryVariables>;
+1 -37
View File
@@ -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<string> } }> } | 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<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, 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<string> } }> } | 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<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, 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}`;
/**
+48
View File
@@ -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,
};
};
+69 -37
View File
@@ -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<Game['team']> };
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,
};
};
+4 -2
View File
@@ -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 };
};
-27
View File
@@ -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<typeof useTeam>['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,
};
};
+10 -1
View File
@@ -3,10 +3,19 @@ import { useMemo } from 'react';
import { useTeamsQuery } from './__generated__/Teams';
import { useTeamsStatisticsQuery } from './__generated__/TeamsStatistics';
import compact from 'lodash/compact';
import { type TeamStatsFieldsFragment } from './__generated__/Team';
// 192
export const DEFAULT_AGGREGATION_EPOCHS = 192;
const EMPTY_STATS: Partial<TeamStatsFieldsFragment> = {
totalQuantumVolume: '0',
totalQuantumRewards: '0',
totalGamesPlayed: 0,
gamesPlayed: [],
quantumRewards: [],
};
export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
const {
data: teamsData,
@@ -33,7 +42,7 @@ export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
const data = useMemo(() => {
const data = teams.map((t) => ({
...t,
...stats.find((s) => s.teamId === t.teamId),
...(stats.find((s) => s.teamId === t.teamId) || EMPTY_STATS),
}));
return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc').map(
+3
View File
@@ -48,5 +48,8 @@ export const DEFAULT_CACHE_CONFIG: InMemoryCacheConfig = {
statistics: {
keyFields: false,
},
Game: {
keyFields: false,
},
},
};
@@ -281,7 +281,7 @@ export const DealTicket = ({
marginFactor: margin?.marginFactor || '1',
marginMode:
margin?.marginMode || Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
includeCollateralIncreaseInAvailableCollateral: true,
includeRequiredPositionMarginInAvailableCollateral: true,
},
!normalizedOrder ||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
@@ -89,7 +89,7 @@ export const MarginChange = ({
openVolume,
marketId,
orderMarginAccountBalance: orderMarginAccountBalance || '0',
includeCollateralIncreaseInAvailableCollateral: true,
includeRequiredPositionMarginInAvailableCollateral: true,
orders,
},
skip
+4
View File
@@ -90,6 +90,7 @@
"Earned by me": "Earned by me",
"Eligible teams": "Eligible teams",
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
"[empty]": "[empty]",
"Ends in": "Ends in",
"Entity scope": "Entity scope",
"{{entity}} scope": "{{entity}} scope",
@@ -122,6 +123,7 @@
"Funding rate": "Funding rate",
"Futures": "Futures",
"Games ({{count}})": "Games ({{count}})",
"Results ({{count}})": "Results ({{count}})",
"Generate a referral code to share with your friends and start earning commission.": "Generate a referral code to share with your friends and start earning commission.",
"Generate code": "Generate code",
"Get rewards for providing liquidity.": "Get rewards for providing liquidity.",
@@ -198,6 +200,7 @@
"No funding history data": "No funding history data",
"No future markets.": "No future markets.",
"No games": "No games",
"No game results available": "No game results available",
"No ledger entries to export": "No ledger entries to export",
"No market": "No market",
"No markets": "No markets",
@@ -317,6 +320,7 @@
"Target stake": "Target stake",
"Team": "Team",
"Team name": "Team name",
"Team name cannot be empty": "Team name cannot be empty",
"Team creation transaction successful": "Team creation transaction successful",
"Team joined": "Team joined",
"Team switch successful. You will switch team at the end of the epoch.": "Team switch successful. You will switch team at the end of the epoch.",
+2 -2
View File
@@ -48,7 +48,7 @@ query EstimatePosition(
$orderMarginAccountBalance: String!
$marginMode: MarginMode!
$marginFactor: String
$includeCollateralIncreaseInAvailableCollateral: Boolean
$includeRequiredPositionMarginInAvailableCollateral: Boolean
) {
estimatePosition(
marketId: $marketId
@@ -60,7 +60,7 @@ query EstimatePosition(
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
includeRequiredPositionMarginInAvailableCollateral: $includeRequiredPositionMarginInAvailableCollateral
# Everywhere in the codebase we expect price values of the underlying to have the right
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
# query will return a full value requiring formatting using asset.decimals. For consistency
+4 -4
View File
@@ -29,7 +29,7 @@ export type EstimatePositionQueryVariables = Types.Exact<{
orderMarginAccountBalance: Types.Scalars['String'];
marginMode: Types.MarginMode;
marginFactor?: Types.InputMaybe<Types.Scalars['String']>;
includeCollateralIncreaseInAvailableCollateral?: Types.InputMaybe<Types.Scalars['Boolean']>;
includeRequiredPositionMarginInAvailableCollateral?: Types.InputMaybe<Types.Scalars['Boolean']>;
}>;
@@ -130,7 +130,7 @@ export function usePositionsSubscriptionSubscription(baseOptions: Apollo.Subscri
export type PositionsSubscriptionSubscriptionHookResult = ReturnType<typeof usePositionsSubscriptionSubscription>;
export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<PositionsSubscriptionSubscription>;
export const EstimatePositionDocument = gql`
query EstimatePosition($marketId: ID!, $openVolume: String!, $averageEntryPrice: String!, $orders: [OrderInfo!], $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String, $includeCollateralIncreaseInAvailableCollateral: Boolean) {
query EstimatePosition($marketId: ID!, $openVolume: String!, $averageEntryPrice: String!, $orders: [OrderInfo!], $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String, $includeRequiredPositionMarginInAvailableCollateral: Boolean) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
@@ -141,7 +141,7 @@ export const EstimatePositionDocument = gql`
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
includeRequiredPositionMarginInAvailableCollateral: $includeRequiredPositionMarginInAvailableCollateral
scaleLiquidationPriceToMarketDecimals: true
) {
collateralIncreaseEstimate {
@@ -185,7 +185,7 @@ export const EstimatePositionDocument = gql`
* orderMarginAccountBalance: // value for 'orderMarginAccountBalance'
* marginMode: // value for 'marginMode'
* marginFactor: // value for 'marginFactor'
* includeCollateralIncreaseInAvailableCollateral: // value for 'includeCollateralIncreaseInAvailableCollateral'
* includeRequiredPositionMarginInAvailableCollateral: // value for 'includeRequiredPositionMarginInAvailableCollateral'
* },
* });
*/
@@ -4,7 +4,6 @@ import {
ExternalLink,
Intent,
NotificationBanner,
SHORT,
} from '@vegaprotocol/ui-toolkit';
import type { StoredNextProtocolUpgradeData } from '../lib';
import {
@@ -70,7 +69,7 @@ export const ProtocolUpgradeInProgressNotification = () => {
if (!upgradeInProgress) return null;
return (
<NotificationBanner intent={Intent.Danger} className={SHORT}>
<NotificationBanner intent={Intent.Danger}>
<div className="uppercase">
{t('The network is being upgraded to {{vegaReleaseTag}}', {
vegaReleaseTag,
+1 -1
View File
@@ -5067,7 +5067,7 @@ export type QueryestimateOrderArgs = {
export type QueryestimatePositionArgs = {
averageEntryPrice: Scalars['String'];
generalAccountBalance: Scalars['String'];
includeCollateralIncreaseInAvailableCollateral?: InputMaybe<Scalars['Boolean']>;
includeRequiredPositionMarginInAvailableCollateral?: InputMaybe<Scalars['Boolean']>;
marginAccountBalance: Scalars['String'];
marginFactor?: InputMaybe<Scalars['String']>;
marginMode: MarginMode;
@@ -4,8 +4,6 @@ import { Intent } from '../../utils/intent';
import { Icon, VegaIcon, VegaIconNames } from '../icon';
import type { HTMLAttributes } from 'react';
export const SHORT = '!px-1 !py-1 min-h-fit';
interface NotificationBannerProps {
intent?: Intent;
children?: React.ReactNode;
@@ -23,7 +21,7 @@ export const NotificationBanner = ({
return (
<div
className={classNames(
'flex items-center border-b px-2',
'flex items-center border-b pl-3 pr-2',
'text-xs leading-tight font-normal',
{
'bg-vega-light-100 dark:bg-vega-dark-100 ': intent === Intent.None,
@@ -1,4 +1,4 @@
import { NotificationBanner, SHORT } from '../notification-banner';
import { NotificationBanner } from '../notification-banner';
import { Intent } from '../../utils/intent';
import { TradingButton } from '../trading-button';
import { useT } from '../../use-t';
@@ -23,7 +23,7 @@ export const ViewingAsBanner = ({
}: ViewingAsBannerProps) => {
const t = useT();
return (
<NotificationBanner intent={Intent.None} className={SHORT}>
<NotificationBanner>
<div className="flex items-baseline justify-between">
<span data-testid="view-banner">
{t('Viewing as Vega user: {{pubKey}}', {